From 7367c73a37a795f1baf3e8be0f33da51ca97dbd7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jan 2020 10:50:02 +0000 Subject: [PATCH 001/282] Searchbox Enter is to clear, tabbing to clear button doesn't work, remove it Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/SearchBox.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 9090152de8..6bf7c754f0 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -133,9 +133,11 @@ module.exports = createReactClass({ return null; } const clearButton = (!this.state.blurred || this.state.searchTerm) ? - ( {this._clearSearch("button"); } }> + ( {this._clearSearch("button"); } }> ) : undefined; // show a shorter placeholder when blurred, if requested From 5252cf4c454c8790ea7f5d12eb7e8ac426aa57a7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Jan 2020 02:44:22 +0000 Subject: [PATCH 002/282] Implement roving tab index context based magic thing and demo on LeftPanel Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile.scss | 5 +- src/components/structures/LeftPanel.js | 3 - src/components/structures/RoomSubList.js | 36 +++- .../views/groups/GroupInviteTile.js | 17 +- src/components/views/rooms/RoomList.js | 5 +- src/components/views/rooms/RoomTile.js | 8 +- src/contexts/RovingTabIndexContext.js | 193 ++++++++++++++++++ 7 files changed, 242 insertions(+), 25 deletions(-) create mode 100644 src/contexts/RovingTabIndexContext.js diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index cb1137bb2f..db2c09f6f1 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -142,10 +142,11 @@ limitations under the License. } } -// toggle menuButton and badge on hover/menu displayed +// toggle menuButton and badge on menu displayed .mx_RoomTile_menuDisplayed, // or on keyboard focus of room tile -.mx_RoomTile.focus-visible:focus-within, +.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:focus-within, +// or on pointer hover .mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover { .mx_RoomTile_menuButton { display: block; diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 796840a625..3444225d06 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -129,9 +129,6 @@ const LeftPanel = createReactClass({ if (!this.focusedElement) return; switch (ev.key) { - case Key.TAB: - this._onMoveFocus(ev, ev.shiftKey); - break; case Key.ARROW_UP: this._onMoveFocus(ev, true, true); break; diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 123ed7c4e1..915a952e79 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -31,6 +31,7 @@ import PropTypes from 'prop-types'; import RoomTile from "../views/rooms/RoomTile"; import LazyRenderList from "../views/elements/LazyRenderList"; import {_t} from "../../languageHandler"; +import {RovingTabIndex, RovingTabIndexGroup} from "../../contexts/RovingTabIndexContext"; // turn this on for drop & drag console debugging galore const debug = false; @@ -272,20 +273,32 @@ export default class RoomSubList extends React.PureComponent { // Wrap the contents in a div and apply styles to the child div so that the browser default outline works if (subListNotifCount > 0) { badge = ( - +
{ FormattingUtils.formatCount(subListNotifCount) }
-
+ ); } else if (this.props.isInvite && this.props.list.length) { // no notifications but highlight anyway because this is an invite badge badge = ( - +
{ this.props.list.length }
-
+ ); } } @@ -308,7 +321,9 @@ export default class RoomSubList extends React.PureComponent { let addRoomButton; if (this.props.onAddRoom) { addRoomButton = ( - ); } - return ( + return
- {this.props.label} { incomingCall } - + { badge } { addRoomButton }
- ); +
; } checkOverflow = () => { diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index c0d0d9eafe..e7ccbdf40b 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -26,6 +26,7 @@ import classNames from 'classnames'; import MatrixClientPeg from "../../../MatrixClientPeg"; import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {RovingTabIndex, RovingTabIndexGroup} from "../../../contexts/RovingTabIndexContext"; // XXX this class copies a lot from RoomTile.js export default createReactClass({ @@ -138,14 +139,16 @@ export default createReactClass({ const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!'; const badge = ( - { badgeContent } - + ); let tooltip; @@ -170,8 +173,10 @@ export default createReactClass({ ); } - return - + { tooltip } - + { contextMenu } - ; + ; }, }); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 35a5ca9e66..277aedb65e 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -41,6 +41,7 @@ import ResizeHandle from '../elements/ResizeHandle'; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; +import {RovingTabIndexContextWrapper} from "../../../contexts/RovingTabIndexContext"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; const HOVER_MOVE_TIMEOUT = 1000; @@ -788,7 +789,9 @@ module.exports = createReactClass({ onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave} > - { subListComponents } + + { subListComponents } + ); }, diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 4dbcc7ca03..6358564042 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -32,6 +32,7 @@ import ActiveRoomObserver from '../../../ActiveRoomObserver'; import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; +import {RovingTabIndex} from "../../../contexts/RovingTabIndexContext"; module.exports = createReactClass({ displayName: 'RoomTile', @@ -432,8 +433,9 @@ module.exports = createReactClass({ } return - { /* { incomingCallBox } */ } { tooltip } - + { contextMenu } ; diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js new file mode 100644 index 0000000000..a571bd2eae --- /dev/null +++ b/src/contexts/RovingTabIndexContext.js @@ -0,0 +1,193 @@ +/* + * + * Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> + * + * 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, { + createContext, + useCallback, + useContext, + useLayoutEffect, + useMemo, + useRef, + useReducer, +} from "react"; +import PropTypes from "prop-types"; +import {Key} from "../Keyboard"; + +const DOCUMENT_POSITION_PRECEDING = 2; +const ANY = Symbol(); + +const RovingTabIndexContext = createContext({ + state: { + activeRef: null, + refs: [], + }, + dispatch: () => {}, +}); +RovingTabIndexContext.displayName = "RovingTabIndexContext"; + +// TODO use a TypeScript type here +const types = { + REGISTER: "REGISTER", + UNREGISTER: "UNREGISTER", + SET_FOCUS: "SET_FOCUS", +}; + +const reducer = (state, action) => { + switch (action.type) { + case types.REGISTER: { + if (state.refs.length === 0) { + return { + ...state, + activeRef: action.payload.ref, + refs: [action.payload.ref], + }; + } + + if (state.refs.includes(action.payload.ref)) { + return state; // already in refs, this should not happen + } + + let newIndex = state.refs.findIndex(ref => { + return ref.current.compareDocumentPosition(action.payload.ref.current) & DOCUMENT_POSITION_PRECEDING; + }); + + if (newIndex < 0) { + newIndex = state.refs.length; // append to the end + } + + return { + ...state, + refs: [ + ...state.refs.slice(0, newIndex), + action.payload.ref, + ...state.refs.slice(newIndex), + ], + }; + } + case types.UNREGISTER: { + const refs = state.refs.filter(r => r !== action.payload.ref); // keep all other refs + + if (refs.length === state.refs.length) { + return state; // already removed, this should not happen + } + + if (state.activeRef === action.payload.ref) { // we just removed the active ref, need to replace it + const oldIndex = state.refs.findIndex(r => r === action.payload.ref); + return { + ...state, + activeRef: oldIndex >= refs.length ? refs[refs.length - 1] : refs[oldIndex], + refs, + }; + } + + return { + ...state, + refs, + }; + } + case types.SET_FOCUS: { + return { + ...state, + activeRef: action.payload.ref, + }; + } + default: + return state; + } +}; + +export const RovingTabIndexContextWrapper = ({children}) => { + const [state, dispatch] = useReducer(reducer, { + activeRef: null, + refs: [], + }); + + const context = useMemo(() => ({state, dispatch}), [state]); + + return + {children} + ; +}; + +export const useRovingTabIndex = () => { + const ref = useRef(null); + const context = useContext(RovingTabIndexContext); + + // setup/teardown + // add ref to the context + useLayoutEffect(() => { + context.dispatch({ + type: types.REGISTER, + payload: {ref}, + }); + return () => { + context.dispatch({ + type: types.UNREGISTER, + payload: {ref}, + }); + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const onFocus = useCallback(() => { + context.dispatch({ + type: types.SET_FOCUS, + payload: {ref}, + }); + }, [ref, context]); + const isActive = context.state.activeRef === ref || context.state.activeRef === ANY; + return [onFocus, isActive, ref]; +}; + +export const RovingTabIndexGroup = ({children}) => { + const [onFocus, isActive, ref] = useRovingTabIndex(); + + // fake reducer dispatch to catch SET_FOCUS calls and pass them to parent as a focus of the group + const dispatch = useCallback(({type}) => { + if (type === types.SET_FOCUS) { + onFocus(); + } + }, [onFocus]); + + const context = useMemo(() => ({ + state: {activeRef: isActive ? ANY : undefined}, + dispatch, + }), [isActive, dispatch]); + + return
+ + {children} + +
; +}; + +// Wraps a given element to attach it to the roving context, props onFocus and tabIndex overridden +export const RovingTabIndex = ({component: E, useInputRef, ...props}) => { + const [onFocus, isActive, ref] = useRovingTabIndex(); + const refProps = {}; + if (useInputRef) { + refProps.inputRef = ref; + } else { + refProps.ref = ref; + } + return ; +}; +RovingTabIndex.propTypes = { + component: PropTypes.elementType.isRequired, + useInputRef: PropTypes.bool, // whether to pass inputRef instead of ref like for AccessibleButton +}; + From dedf1eab315347cd85ea45ed3d6a85d60f542104 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Jan 2020 11:37:14 +0000 Subject: [PATCH 003/282] Iterate to get rid of the magic group and just provide a generic functional render wrapper Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 149 +++++++++--------- .../views/groups/GroupInviteTile.js | 67 ++++---- src/components/views/rooms/RoomTile.js | 69 ++++---- src/contexts/RovingTabIndexContext.js | 81 +++++----- 4 files changed, 184 insertions(+), 182 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 915a952e79..98e69f6edb 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -31,7 +31,7 @@ import PropTypes from 'prop-types'; import RoomTile from "../views/rooms/RoomTile"; import LazyRenderList from "../views/elements/LazyRenderList"; import {_t} from "../../languageHandler"; -import {RovingTabIndex, RovingTabIndexGroup} from "../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../contexts/RovingTabIndexContext"; // turn this on for drop & drag console debugging galore const debug = false; @@ -264,45 +264,6 @@ export default class RoomSubList extends React.PureComponent { const subListNotifCount = subListNotifications.count; const subListNotifHighlight = subListNotifications.highlight; - let badge; - if (!this.props.collapsed) { - const badgeClasses = classNames({ - 'mx_RoomSubList_badge': true, - 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, - }); - // Wrap the contents in a div and apply styles to the child div so that the browser default outline works - if (subListNotifCount > 0) { - badge = ( - -
- { FormattingUtils.formatCount(subListNotifCount) } -
-
- ); - } else if (this.props.isInvite && this.props.list.length) { - // no notifications but highlight anyway because this is an invite badge - badge = ( - -
- { this.props.list.length } -
-
- ); - } - } - // When collapsed, allow a long hover on the header to show user // the full tag name and room count let title; @@ -318,19 +279,6 @@ export default class RoomSubList extends React.PureComponent { ; } - let addRoomButton; - if (this.props.onAddRoom) { - addRoomButton = ( - - ); - } - const len = this.props.list.length + this.props.extraTiles.length; let chevron; if (len) { @@ -342,26 +290,81 @@ export default class RoomSubList extends React.PureComponent { chevron = (
); } - return -
- - { chevron } - {this.props.label} - { incomingCall } - - { badge } - { addRoomButton } -
-
; + return + {({onFocus, isActive, ref}) => { + const tabIndex = isActive ? 0 : -1; + + let badge; + if (!this.props.collapsed) { + const badgeClasses = classNames({ + 'mx_RoomSubList_badge': true, + 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, + }); + // Wrap the contents in a div and apply styles to the child div so that the browser default outline works + if (subListNotifCount > 0) { + badge = ( + +
+ { FormattingUtils.formatCount(subListNotifCount) } +
+
+ ); + } else if (this.props.isInvite && this.props.list.length) { + // no notifications but highlight anyway because this is an invite badge + badge = ( + +
+ { this.props.list.length } +
+
+ ); + } + } + + let addRoomButton; + if (this.props.onAddRoom) { + addRoomButton = ( + + ); + } + + return ( +
+ + { chevron } + {this.props.label} + { incomingCall } + + { badge } + { addRoomButton } +
+ ); + } } +
; } checkOverflow = () => { diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index e7ccbdf40b..70baeb1e78 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -26,7 +26,7 @@ import classNames from 'classnames'; import MatrixClientPeg from "../../../MatrixClientPeg"; import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import {RovingTabIndex, RovingTabIndexGroup} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext"; // XXX this class copies a lot from RoomTile.js export default createReactClass({ @@ -138,18 +138,6 @@ export default createReactClass({ }); const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!'; - const badge = ( - - { badgeContent } - - ); let tooltip; if (this.props.collapsed && this.state.hover) { @@ -173,27 +161,40 @@ export default createReactClass({ ); } - return - -
- { av } -
-
- { label } - { badge } -
- { tooltip } -
+ return + + {({onFocus, isActive, ref}) => + +
+ { av } +
+
+ { label } + + { badgeContent } + +
+ { tooltip } +
+ } +
{ contextMenu } -
; + ; }, }); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 6358564042..001baf0b96 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -32,7 +32,7 @@ import ActiveRoomObserver from '../../../ActiveRoomObserver'; import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; -import {RovingTabIndex} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext"; module.exports = createReactClass({ displayName: 'RoomTile', @@ -433,37 +433,42 @@ module.exports = createReactClass({ } return - -
-
- - { dmIndicator } -
-
- { privateIcon } -
-
- { label } - { subtextLabel } -
- { dmOnline } - { contextMenuButton } - { badge } -
- { /* { incomingCallBox } */ } - { tooltip } -
+ + {({onFocus, isActive, ref}) => + +
+
+ + { dmIndicator } +
+
+ { privateIcon } +
+
+ { label } + { subtextLabel } +
+ { dmOnline } + { contextMenuButton } + { badge } +
+ { /* { incomingCallBox } */ } + { tooltip } +
+ } +
{ contextMenu }
; diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js index a571bd2eae..f5001d28cc 100644 --- a/src/contexts/RovingTabIndexContext.js +++ b/src/contexts/RovingTabIndexContext.js @@ -25,11 +25,9 @@ import React, { useRef, useReducer, } from "react"; -import PropTypes from "prop-types"; import {Key} from "../Keyboard"; const DOCUMENT_POSITION_PRECEDING = 2; -const ANY = Symbol(); const RovingTabIndexContext = createContext({ state: { @@ -119,15 +117,42 @@ export const RovingTabIndexContextWrapper = ({children}) => { const context = useMemo(() => ({state, dispatch}), [state]); - return - {children} - ; + const onKeyDown = useCallback((ev) => { + if (state.refs.length <= 0) return; + + let handled = true; + switch (ev.key) { + case Key.HOME: + setImmediate(() => state.refs[0].current.focus()); + break; + case Key.END: + state.refs[state.refs.length - 1].current.focus(); + break; + default: + handled = false; + } + + if (handled) { + ev.preventDefault(); + ev.stopPropagation(); + } + }, [state]); + + return
+ + {children} + +
; }; -export const useRovingTabIndex = () => { - const ref = useRef(null); +export const useRovingTabIndex = (inputRef) => { + let ref = useRef(null); const context = useContext(RovingTabIndexContext); + if (inputRef) { + ref = inputRef; + } + // setup/teardown // add ref to the context useLayoutEffect(() => { @@ -149,45 +174,13 @@ export const useRovingTabIndex = () => { payload: {ref}, }); }, [ref, context]); - const isActive = context.state.activeRef === ref || context.state.activeRef === ANY; + + const isActive = context.state.activeRef === ref; return [onFocus, isActive, ref]; }; -export const RovingTabIndexGroup = ({children}) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); - - // fake reducer dispatch to catch SET_FOCUS calls and pass them to parent as a focus of the group - const dispatch = useCallback(({type}) => { - if (type === types.SET_FOCUS) { - onFocus(); - } - }, [onFocus]); - - const context = useMemo(() => ({ - state: {activeRef: isActive ? ANY : undefined}, - dispatch, - }), [isActive, dispatch]); - - return
- - {children} - -
; -}; - -// Wraps a given element to attach it to the roving context, props onFocus and tabIndex overridden -export const RovingTabIndex = ({component: E, useInputRef, ...props}) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); - const refProps = {}; - if (useInputRef) { - refProps.inputRef = ref; - } else { - refProps.ref = ref; - } - return ; -}; -RovingTabIndex.propTypes = { - component: PropTypes.elementType.isRequired, - useInputRef: PropTypes.bool, // whether to pass inputRef instead of ref like for AccessibleButton +export const RovingTabIndexWrapper = ({children, inputRef}) => { + const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); + return children({onFocus, isActive, ref}); }; From 2b37fe76242fe1ae328d435abb8d82ca454ec13d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 00:40:08 +0000 Subject: [PATCH 004/282] do some renaming Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomList.js | 6 +++--- src/contexts/RovingTabIndexContext.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 45ff940c22..4441b4d539 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -39,7 +39,7 @@ import * as sdk from "../../../index"; import * as Receipt from "../../../utils/Receipt"; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; -import {RovingTabIndexContextWrapper} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexContextProvider} from "../../../contexts/RovingTabIndexContext"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -788,9 +788,9 @@ export default createReactClass({ onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave} > - + { subListComponents } - +
); }, diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js index f5001d28cc..182a7f5504 100644 --- a/src/contexts/RovingTabIndexContext.js +++ b/src/contexts/RovingTabIndexContext.js @@ -109,7 +109,7 @@ const reducer = (state, action) => { } }; -export const RovingTabIndexContextWrapper = ({children}) => { +export const RovingTabIndexContextProvider = ({children}) => { const [state, dispatch] = useReducer(reducer, { activeRef: null, refs: [], From 8c1fdf4cabfb0984f5d34d4656bc3e48d345d7d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 01:25:44 +0000 Subject: [PATCH 005/282] tidy up Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/contexts/RovingTabIndexContext.js | 78 ++++++++++++++++++--------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js index 182a7f5504..2e8439d2a4 100644 --- a/src/contexts/RovingTabIndexContext.js +++ b/src/contexts/RovingTabIndexContext.js @@ -1,20 +1,18 @@ /* - * - * Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> - * - * 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. - * / - */ +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, { createContext, @@ -27,12 +25,25 @@ import React, { } from "react"; import {Key} from "../Keyboard"; +/** + * Module to simplify implementing the Roving TabIndex accessibility technique + * + * Wrap the Widget in an RovingTabIndexContextProvider + * and then for all buttons make use of useRovingTabIndex or RovingTabIndexWrapper. + * The code will keep track of which tabIndex was most recently focused and expose that information as `isActive` which + * can then be used to only set the tabIndex to 0 as expected by the roving tabindex technique. + * When the active button gets unmounted the closest button will be chosen as expected. + * Initially the first button to mount will be given active state. + * + * https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#Technique_1_Roving_tabindex + */ + const DOCUMENT_POSITION_PRECEDING = 2; const RovingTabIndexContext = createContext({ state: { activeRef: null, - refs: [], + refs: [], // list of refs in DOM order }, dispatch: () => {}, }); @@ -49,6 +60,7 @@ const reducer = (state, action) => { switch (action.type) { case types.REGISTER: { if (state.refs.length === 0) { + // Our list of refs was empty, set activeRef to this first item return { ...state, activeRef: action.payload.ref, @@ -60,6 +72,7 @@ const reducer = (state, action) => { return state; // already in refs, this should not happen } + // find the index of the first ref which is not preceding this one in DOM order let newIndex = state.refs.findIndex(ref => { return ref.current.compareDocumentPosition(action.payload.ref.current) & DOCUMENT_POSITION_PRECEDING; }); @@ -68,6 +81,7 @@ const reducer = (state, action) => { newIndex = state.refs.length; // append to the end } + // update the refs list return { ...state, refs: [ @@ -78,13 +92,16 @@ const reducer = (state, action) => { }; } case types.UNREGISTER: { - const refs = state.refs.filter(r => r !== action.payload.ref); // keep all other refs + // filter out the ref which we are removing + const refs = state.refs.filter(r => r !== action.payload.ref); if (refs.length === state.refs.length) { return state; // already removed, this should not happen } - if (state.activeRef === action.payload.ref) { // we just removed the active ref, need to replace it + if (state.activeRef === action.payload.ref) { + // we just removed the active ref, need to replace it + // pick the ref which is now in the index the old ref was in const oldIndex = state.refs.findIndex(r => r === action.payload.ref); return { ...state, @@ -93,12 +110,14 @@ const reducer = (state, action) => { }; } + // update the refs list return { ...state, refs, }; } case types.SET_FOCUS: { + // update active ref return { ...state, activeRef: action.payload.ref, @@ -115,17 +134,18 @@ export const RovingTabIndexContextProvider = ({children}) => { refs: [], }); - const context = useMemo(() => ({state, dispatch}), [state]); - const onKeyDown = useCallback((ev) => { + // check if we actually have any items if (state.refs.length <= 0) return; let handled = true; switch (ev.key) { case Key.HOME: + // move focus to first item setImmediate(() => state.refs[0].current.focus()); break; case Key.END: + // move focus to last item state.refs[state.refs.length - 1].current.focus(); break; default: @@ -138,6 +158,9 @@ export const RovingTabIndexContextProvider = ({children}) => { } }, [state]); + const context = useMemo(() => ({state, dispatch}), [state]); + + // wrap in a div with key-down handling for HOME/END keys return
{children} @@ -145,21 +168,27 @@ export const RovingTabIndexContextProvider = ({children}) => {
; }; +// Hook to register a roving tab index +// inputRef parameter specifies the ref to use +// onFocus should be called when the index gained focus in any manner +// isActive should be used to set tabIndex in a manner such as `tabIndex={isActive ? 0 : -1}` +// ref should be passed to a DOM node which will be used for DOM compareDocumentPosition export const useRovingTabIndex = (inputRef) => { - let ref = useRef(null); const context = useContext(RovingTabIndexContext); + let ref = useRef(null); if (inputRef) { + // if we are given a ref, use it instead of ours ref = inputRef; } - // setup/teardown - // add ref to the context + // setup (after refs) useLayoutEffect(() => { context.dispatch({ type: types.REGISTER, payload: {ref}, }); + // teardown return () => { context.dispatch({ type: types.UNREGISTER, @@ -179,6 +208,7 @@ export const useRovingTabIndex = (inputRef) => { return [onFocus, isActive, ref]; }; +// Wrapper to allow use of useRovingTabIndex outside of React Functional Components. export const RovingTabIndexWrapper = ({children, inputRef}) => { const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); return children({onFocus, isActive, ref}); From 781db63fa639e286622542bc7cba4e29c3c17e5f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 01:35:42 +0000 Subject: [PATCH 006/282] split out home/end handling into a helper as not all roving-tab-index widgets want it Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomList.js | 6 ++++-- src/contexts/RovingTabIndexContext.js | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 4441b4d539..21cd1ed719 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -39,7 +39,7 @@ import * as sdk from "../../../index"; import * as Receipt from "../../../utils/Receipt"; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; -import {RovingTabIndexContextProvider} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexContextProvider, RovingTabIndexHomeEndHelper} from "../../../contexts/RovingTabIndexContext"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -789,7 +789,9 @@ export default createReactClass({ onMouseLeave={this.onMouseLeave} > - { subListComponents } + + { subListComponents } + ); diff --git a/src/contexts/RovingTabIndexContext.js b/src/contexts/RovingTabIndexContext.js index 2e8439d2a4..55ac19e40f 100644 --- a/src/contexts/RovingTabIndexContext.js +++ b/src/contexts/RovingTabIndexContext.js @@ -134,19 +134,30 @@ export const RovingTabIndexContextProvider = ({children}) => { refs: [], }); + const context = useMemo(() => ({state, dispatch}), [state]); + + return + {children} + ; +}; + +// Helper to handle Home/End to jump to first/last roving-tab-index for widgets such as treeview +export const RovingTabIndexHomeEndHelper = ({children}) => { + const context = useContext(RovingTabIndexContext); + const onKeyDown = useCallback((ev) => { // check if we actually have any items - if (state.refs.length <= 0) return; + if (context.state.refs.length <= 0) return; let handled = true; switch (ev.key) { case Key.HOME: // move focus to first item - setImmediate(() => state.refs[0].current.focus()); + setImmediate(() => context.state.refs[0].current.focus()); break; case Key.END: // move focus to last item - state.refs[state.refs.length - 1].current.focus(); + context.state.refs[context.state.refs.length - 1].current.focus(); break; default: handled = false; @@ -156,15 +167,10 @@ export const RovingTabIndexContextProvider = ({children}) => { ev.preventDefault(); ev.stopPropagation(); } - }, [state]); + }, [context.state]); - const context = useMemo(() => ({state, dispatch}), [state]); - - // wrap in a div with key-down handling for HOME/END keys return
- - {children} - + { children }
; }; From 2230b7732a703215c49a356697a7acd9cd969383 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 01:45:16 +0000 Subject: [PATCH 007/282] rearrange Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../RovingTabIndex.js} | 2 +- src/components/structures/RoomSubList.js | 2 +- src/components/views/groups/GroupInviteTile.js | 2 +- src/components/views/rooms/RoomList.js | 6 +++--- src/components/views/rooms/RoomTile.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename src/{contexts/RovingTabIndexContext.js => accessibility/RovingTabIndex.js} (99%) diff --git a/src/contexts/RovingTabIndexContext.js b/src/accessibility/RovingTabIndex.js similarity index 99% rename from src/contexts/RovingTabIndexContext.js rename to src/accessibility/RovingTabIndex.js index 55ac19e40f..ad2051f1f1 100644 --- a/src/contexts/RovingTabIndexContext.js +++ b/src/accessibility/RovingTabIndex.js @@ -128,7 +128,7 @@ const reducer = (state, action) => { } }; -export const RovingTabIndexContextProvider = ({children}) => { +export const RovingTabIndexProvider = ({children}) => { const [state, dispatch] = useReducer(reducer, { activeRef: null, refs: [], diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 775b6b69ce..2d41abf902 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -31,7 +31,7 @@ import PropTypes from 'prop-types'; import RoomTile from "../views/rooms/RoomTile"; import LazyRenderList from "../views/elements/LazyRenderList"; import {_t} from "../../languageHandler"; -import {RovingTabIndexWrapper} from "../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex"; // turn this on for drop & drag console debugging galore const debug = false; diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index 488c1e20cf..3b15c6ff41 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -26,7 +26,7 @@ import classNames from 'classnames'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; // XXX this class copies a lot from RoomTile.js export default createReactClass({ diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 21cd1ed719..a137a36c60 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -39,7 +39,7 @@ import * as sdk from "../../../index"; import * as Receipt from "../../../utils/Receipt"; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; -import {RovingTabIndexContextProvider, RovingTabIndexHomeEndHelper} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexProvider, RovingTabIndexHomeEndHelper} from "../../../accessibility/RovingTabIndex"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -788,11 +788,11 @@ export default createReactClass({ onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave} > - + { subListComponents } - + ); }, diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 8701c3d287..3b13001225 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -32,7 +32,7 @@ import ActiveRoomObserver from '../../../ActiveRoomObserver'; import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; -import {RovingTabIndexWrapper} from "../../../contexts/RovingTabIndexContext"; +import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; export default createReactClass({ displayName: 'RoomTile', From 4504d9b790c1fdbaa2ab2545cf9c7de2a601f6c8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 03:15:52 +0000 Subject: [PATCH 008/282] add tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/accessibility/RovingTabIndex.js | 2 +- test/accessibility/RovingTabIndex-test.js | 117 ++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/accessibility/RovingTabIndex-test.js diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.js index ad2051f1f1..85aa133aa4 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.js @@ -153,7 +153,7 @@ export const RovingTabIndexHomeEndHelper = ({children}) => { switch (ev.key) { case Key.HOME: // move focus to first item - setImmediate(() => context.state.refs[0].current.focus()); + context.state.refs[0].current.focus(); break; case Key.END: // move focus to last item diff --git a/test/accessibility/RovingTabIndex-test.js b/test/accessibility/RovingTabIndex-test.js new file mode 100644 index 0000000000..2b55d1420c --- /dev/null +++ b/test/accessibility/RovingTabIndex-test.js @@ -0,0 +1,117 @@ +/* +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 Adapter from "enzyme-adapter-react-16"; +import { configure, mount } from "enzyme"; + +import { + RovingTabIndexProvider, + RovingTabIndexWrapper, + useRovingTabIndex, +} from "../../src/accessibility/RovingTabIndex"; + +configure({ adapter: new Adapter() }); + +const Button = (props) => { + const [onFocus, isActive, ref] = useRovingTabIndex(); + return ; +const button2 = ; +const button3 = ; +const button4 = ; + +describe("RovingTabIndex", () => { + it("RovingTabIndexProvider renders children as expected", () => { + const wrapper = mount( +
Test
+
); + expect(wrapper.text()).toBe("Test"); + expect(wrapper.html()).toBe('
Test
'); + }); + + it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => { + const wrapper = mount( + { button1 } + { button2 } + { button3 } + ); + + // should begin with 0th being active + checkTabIndexes(wrapper.find("button"), [0, -1, -1]); + + // focus on 2nd button and test it is the only active one + wrapper.find("button").at(2).simulate("focus"); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, -1, 0]); + + // focus on 1st button and test it is the only active one + wrapper.find("button").at(1).simulate("focus"); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, 0, -1]); + + // check that the active button does not change even on an explicit blur event + wrapper.find("button").at(1).simulate("blur"); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, 0, -1]); + + // update the children, it should remain on the same button + wrapper.setProps({ + children: [button1, button4, button2, button3], + }); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, -1, 0, -1]); + + // update the children, remove the active button, it should move to the next one + wrapper.setProps({ + children: [button1, button4, button3], + }); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, -1, 0]); + }); + + it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => { + const wrapper = mount( + { button1 } + { button2 } + + {({onFocus, isActive, ref}) => + + } + + ); + + // should begin with 0th being active + checkTabIndexes(wrapper.find("button"), [0, -1, -1]); + + // focus on 2nd button and test it is the only active one + wrapper.find("button").at(2).simulate("focus"); + wrapper.update(); + checkTabIndexes(wrapper.find("button"), [-1, -1, 0]); + }); +}); + + From d30c46a641a061c6f54694ef642d469dcf175f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 14 Jan 2020 11:20:56 +0100 Subject: [PATCH 009/282] FilePanel: Refactor out the file panel and convert the methods to async ones. --- src/components/structures/FilePanel.js | 62 +++++++++++++++----------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 61b3d2d4b9..6728472a6c 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -40,42 +40,50 @@ const FilePanel = createReactClass({ }; }, - componentDidMount: function() { - this.updateTimelineSet(this.props.roomId); + async componentDidMount() { + await this.updateTimelineSet(this.props.roomId); }, - updateTimelineSet: function(roomId) { + async fetchFileEventsServer(room) { + const client = MatrixClientPeg.get(); + + const filter = new Matrix.Filter(client.credentials.userId); + filter.setDefinition( + { + "room": { + "timeline": { + "contains_url": true, + "types": [ + "m.room.message", + ], + }, + }, + }, + ); + + // FIXME: we shouldn't be doing this every time we change room - see comment above. + // TODO: Remove this stale comment? Which comment above? + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + filter.filterId = filterId; + const timelineSet = room.getOrCreateFilteredTimelineSet(filter); + + return timelineSet; + }, + + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); this.noRoom = !room; if (room) { - const filter = new Matrix.Filter(client.credentials.userId); - filter.setDefinition( - { - "room": { - "timeline": { - "contains_url": true, - "types": [ - "m.room.message", - ], - }, - }, - }, - ); + try { + let timelineSet = await this.fetchFileEventsServer(room) + this.setState({ timelineSet: timelineSet }); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then( - (filterId)=>{ - filter.filterId = filterId; - const timelineSet = room.getOrCreateFilteredTimelineSet(filter); - this.setState({ timelineSet: timelineSet }); - }, - (error)=>{ - console.error("Failed to get or create file panel filter", error); - }, - ); + } catch (error) { + console.error("Failed to get or create file panel filter", error); + } } else { console.error("Failed to add filtered timelineSet for FilePanel as no room!"); } From 4f63b10465c28db5f9abb6e5d04fd241b03e74f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 10:41:33 +0100 Subject: [PATCH 010/282] EventIndex: Live events can be unencrypted as well. --- src/indexing/EventIndex.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c912e31fa5..0980413eb5 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -170,7 +170,12 @@ export default class EventIndex { return; } - const e = ev.toJSON().decrypted; + const jsonEvent = ev.toJSON(); + + let e; + if (ev.isEncrypted()) e = jsonEvent.decrypted; + else e = jsonEvent; + const profile = { displayname: ev.sender.rawDisplayName, avatar_url: ev.sender.getMxcAvatarUrl(), From 263370c9ae6d13038157417e71220a9c5e5cfd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:04:27 +0100 Subject: [PATCH 011/282] BaseEventIndexManager: Add a method to load file events of a room. --- src/indexing/BaseEventIndexManager.js | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index 5e8ca668ad..d7b322bb1a 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -62,11 +62,18 @@ export interface SearchArgs { room_id: ?string; } -export interface HistoricEvent { +export interface EventAndProfile { event: MatrixEvent; profile: MatrixProfile; } +export interface LoadArgs { + roomId: string; + limit: number; + fromEvent: string; + direction: string; +} + /** * Base class for classes that provide platform-specific event indexing. * @@ -145,7 +152,7 @@ export default class BaseEventIndexManager { * * This is used to add a batch of events to the index. * - * @param {[HistoricEvent]} events The list of events and profiles that + * @param {[EventAndProfile]} events The list of events and profiles that * should be added to the event index. * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that * should be stored in the index which should be used to continue crawling @@ -158,7 +165,7 @@ export default class BaseEventIndexManager { * were already added to the index, false otherwise. */ async addHistoricEvents( - events: [HistoricEvent], + events: [EventAndProfile], checkpoint: CrawlerCheckpoint | null, oldCheckpoint: CrawlerCheckpoint | null, ): Promise { @@ -201,6 +208,23 @@ export default class BaseEventIndexManager { throw new Error("Unimplemented"); } + /** Load events that contain an mxc URL to a file from the index. + * + * @param {object} args Arguments object for the method. + * @param {string} args.roomId The ID of the room for which the events + * should be loaded. + * @param {number} args.limit The maximum number of events to return. + * @param {string} args.fromEvent An event id of a previous event returned + * by this method. If set events that are older than the event with the + * given event ID will be returned. + * + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array + * of Matrix events that contain mxc URLs. + */ + async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { + throw new Error("Unimplemented"); + } + /** * close our event index. * From 8a17c73b79d13eddd31418de59716870a5b2cfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:02 +0100 Subject: [PATCH 012/282] EventIndex: Add a method to populate an event timeline with file events. --- src/indexing/EventIndex.js | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0980413eb5..501e21b29d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,6 +17,12 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; +import Matrix from 'matrix-js-sdk'; +import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; +import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; +import MatrixEvent from 'matrix-js-sdk/lib/models/event'; +import RoomMember from 'matrix-js-sdk/lib/models/room-member'; + /* * Event indexing class that wraps the platform specific event indexing. */ @@ -411,4 +417,66 @@ export default class EventIndex { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.searchEventIndex(searchArgs); } + + async populateFileTimeline(room, timelineSet) { + const client = MatrixClientPeg.get(); + const indexManager = PlatformPeg.get().getEventIndexingManager(); + + // Get our events from the event index. + const events = await indexManager.loadFileEvents( + { + roomId: room.roomId, + limit: 10 + } + ); + + let eventMapper = client.getEventMapper(); + + // Turn the events into MatrixEvent objects. + const matrixEvents = events.map(e => { + const matrixEvent = eventMapper(e.event); + + const member = new RoomMember(room.roomId, matrixEvent.getSender()); + + // We can't really reconstruct the whole room state from our + // EventIndex to calculate the correct display name. Use the + // disambiguated form always instead. + member.name = e.profile.displayname + " (" + matrixEvent.getSender() + ")"; + + // This is sets the avatar URL. + const memberEvent = eventMapper( + { + content: { + membership: "join", + avatar_url: e.profile.avatar_url, + displayname: e.profile.displayname, + }, + type: "m.room.member", + event_id: matrixEvent.getId() + ":eventIndex", + room_id: matrixEvent.getRoomId(), + sender: matrixEvent.getSender(), + origin_server_ts: matrixEvent.getTs(), + state_key: matrixEvent.getSender() + } + ); + + // We set this manually to avoid emitting RoomMember.membership and + // RoomMember.name events. + member.events.member = memberEvent; + matrixEvent.sender = member; + + return matrixEvent; + }); + + // Add the events to the live timeline of the file panel. + matrixEvents.forEach(e => { + if (!timelineSet.eventIdToTimeline(e.getId())) { + const liveTimeline = timelineSet.getLiveTimeline(); + timelineSet.addEventToTimeline(e, liveTimeline, true) + } + }); + + // Set the pagination token to the oldest event that we retrieved. + timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + } } From a1cbff3c8cefa3945c5ebedc85e31dd305d2b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:43 +0100 Subject: [PATCH 013/282] FilePanel: Use the event index in encrypted rooms to populate the panel. --- src/components/structures/FilePanel.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6728472a6c..56bc9dee64 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,6 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; +import {EventIndexPeg} from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* @@ -73,12 +74,20 @@ const FilePanel = createReactClass({ async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); + const eventIndex = EventIndexPeg.get(); this.noRoom = !room; if (room) { + let timelineSet; + try { - let timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room) + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + await eventIndex.populateFileTimeline(room, timelineSet); + } + this.setState({ timelineSet: timelineSet }); } catch (error) { From 7fb3645e940073a7c7db4e022e1655ca7facde7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:06:29 +0100 Subject: [PATCH 014/282] LifeCycle: Start the event index before the client. --- src/Lifecycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0796e326a0..aab7884b2e 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,8 +588,8 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { - await MatrixClientPeg.start(); await EventIndexPeg.init(); + await MatrixClientPeg.start(); } else { console.warn("Caller requested only auxiliary services be started"); await MatrixClientPeg.assign(); From 49c1dbe42133dd8103e1d0d8036d5bcc29c32c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:04:53 +0100 Subject: [PATCH 015/282] FilePanel: Implement pagination requesting using the EventIndex. --- src/components/structures/FilePanel.js | 18 +++- src/components/structures/TimelinePanel.js | 14 ++- src/indexing/EventIndex.js | 99 +++++++++++++++++++--- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 56bc9dee64..74b434cdbf 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -71,6 +71,20 @@ const FilePanel = createReactClass({ return timelineSet; }, + onPaginationRequest(timelineWindow, direction, limit) { + const client = MatrixClientPeg.get(); + const eventIndex = EventIndexPeg.get(); + const roomId = this.props.roomId; + + const room = client.getRoom(roomId); + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); + } else { + return timelineWindow.paginate(direction, limit); + } + }, + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -85,7 +99,8 @@ const FilePanel = createReactClass({ timelineSet = await this.fetchFileEventsServer(room) if (client.isRoomEncrypted(roomId) && eventIndex !== null) { - await eventIndex.populateFileTimeline(room, timelineSet); + const timeline = timelineSet.getLiveTimeline(); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); } this.setState({ timelineSet: timelineSet }); @@ -128,6 +143,7 @@ const FilePanel = createReactClass({ manageReadMarkers={false} timelineSet={this.state.timelineSet} showUrlPreview = {false} + onPaginationRequest={this.onPaginationRequest} tileShape="file_grid" resizeNotifier={this.props.resizeNotifier} empty={_t('There are no visible files in this room')} diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 30b02bfcca..41b1b6f675 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -94,6 +94,10 @@ const TimelinePanel = createReactClass({ // callback which is called when the read-up-to mark is updated. onReadMarkerUpdated: PropTypes.func, + // callback which is called when we wish to paginate the timeline + // window. + onPaginationRequest: PropTypes.func, + // maximum number of events to show in a timeline timelineCap: PropTypes.number, @@ -338,6 +342,14 @@ const TimelinePanel = createReactClass({ } }, + onPaginationRequest(timelineWindow, direction, size) { + if (this.props.onPaginationRequest) { + return this.props.onPaginationRequest(timelineWindow, direction, size); + } else { + return timelineWindow.paginate(direction, size); + } + }, + // set off a pagination request. onMessageListFillRequest: function(backwards) { if (!this._shouldPaginate()) return Promise.resolve(false); @@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({ debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards); this.setState({[paginatingKey]: true}); - return this._timelineWindow.paginate(dir, PAGINATE_SIZE).then((r) => { + return this.onPaginationRequest(this._timelineWindow, dir, PAGINATE_SIZE).then((r) => { if (this.unmounted) { return; } debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 501e21b29d..7263d8b2c4 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -418,17 +418,29 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } - async populateFileTimeline(room, timelineSet) { + async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); + let loadArgs = { + roomId: room.roomId, + limit: limit + } + + if (fromEvent) { + loadArgs.fromEvent = fromEvent; + loadArgs.direction = direction; + } + + let events + // Get our events from the event index. - const events = await indexManager.loadFileEvents( - { - roomId: room.roomId, - limit: 10 - } - ); + try { + events = await indexManager.loadFileEvents(loadArgs); + } catch (e) { + console.log("EventIndex: Error getting file events", e); + return [] + } let eventMapper = client.getEventMapper(); @@ -468,15 +480,82 @@ export default class EventIndex { return matrixEvent; }); + return matrixEvents; + } + + async populateFileTimeline(timelineSet, timeline, room, limit = 10, + fromEvent = null, direction = EventTimeline.BACKWARDS) { + let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - const liveTimeline = timelineSet.getLiveTimeline(); - timelineSet.addEventToTimeline(e, liveTimeline, true) + timelineSet.addEventToTimeline(e, timeline, + direction == EventTimeline.BACKWARDS) } }); // Set the pagination token to the oldest event that we retrieved. - timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + if (matrixEvents.length > 0) { + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), + EventTimeline.BACKWARDS); + return true; + } else { + timeline.setPaginationToken("", EventTimeline.BACKWARDS); + return false; + } + } + + paginateTimelineWindow(room, timelineWindow, direction, limit) { + let tl; + + // TODO this is from the js-sdk, this should probably be exposed to + // us through the js-sdk. + const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { + var count = (direction == EventTimeline.BACKWARDS) ? + timeline.retreat(limit) : timeline.advance(limit); + + if (count) { + timelineWindow._eventCount += count; + var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + + if (excess > 0) { + timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + } + return true; + } + + return false; + }; + + // TODO these private fields should be somehow exposed in the js-sdk. + if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; + else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + + if (!tl) return Promise.resolve(false); + if (tl.pendingPaginate) return tl.pendingPaginate; + + if (moveWindowCap(timelineWindow, tl, direction, limit)) { + return Promise.resolve(true); + } + + const paginationMethod = async (timelineWindow, timeline, room, direction, limit) => { + const timelineSet = timelineWindow._timelineSet; + const token = timeline.timeline.getPaginationToken(direction); + + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, + room, limit, token, direction); + + moveWindowCap(timelineWindow, timeline, direction, limit) + timeline.pendingPaginate = null; + + return ret; + }; + + const paginationPromise = paginationMethod(timelineWindow, tl, room, + direction, limit); + tl.pendingPaginate = paginationPromise; + + return paginationPromise; } } From 70d394e668615d431a0c9f9ed4f2c16cad1c2a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:35:33 +0100 Subject: [PATCH 016/282] EventIndex: Update the imports for the new build system. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 74b434cdbf..0ef9331338 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; -import {EventIndexPeg} from "../../indexing/EventIndexPeg"; +import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 7263d8b2c4..cb2f646d07 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,11 +17,9 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; -import Matrix from 'matrix-js-sdk'; -import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; -import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; -import MatrixEvent from 'matrix-js-sdk/lib/models/event'; -import RoomMember from 'matrix-js-sdk/lib/models/room-member'; +import * as Matrix from 'matrix-js-sdk'; +import {EventTimelineSet} from 'matrix-js-sdk'; +import {EventTimeline} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -448,7 +446,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new RoomMember(room.roomId, matrixEvent.getSender()); + const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 95b86b42d00b1b23cfaed2f1aa474b962d55fe2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:06:05 +0100 Subject: [PATCH 017/282] BaseEventIndexManager: Update the docs for the loadFileEvents method. --- src/indexing/BaseEventIndexManager.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index d7b322bb1a..c4758bcaa3 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -215,11 +215,14 @@ export default class BaseEventIndexManager { * should be loaded. * @param {number} args.limit The maximum number of events to return. * @param {string} args.fromEvent An event id of a previous event returned - * by this method. If set events that are older than the event with the - * given event ID will be returned. + * by this method. Passing this means that we are going to continue loading + * events from this point in the history. + * @param {string} args.direction The direction to which we should continue + * loading events from. This is used only if fromEvent is used as well. * - * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array - * of Matrix events that contain mxc URLs. + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an + * array of Matrix events that contain mxc URLs accompanied with the + * historic profile of the sender. */ async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { throw new Error("Unimplemented"); From ccfe3c7e70e9b9d5e8beddf7e8dc4c3e111bab56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:52:20 +0100 Subject: [PATCH 018/282] FilePanel/EventIndex: Fix lint errors. --- src/components/structures/FilePanel.js | 6 +++--- src/indexing/EventIndex.js | 27 +++++++++++++------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 0ef9331338..6faec27284 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -64,7 +64,8 @@ const FilePanel = createReactClass({ // FIXME: we shouldn't be doing this every time we change room - see comment above. // TODO: Remove this stale comment? Which comment above? - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, + filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); @@ -96,7 +97,7 @@ const FilePanel = createReactClass({ let timelineSet; try { - timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room); if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); @@ -104,7 +105,6 @@ const FilePanel = createReactClass({ } this.setState({ timelineSet: timelineSet }); - } catch (error) { console.error("Failed to get or create file panel filter", error); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb2f646d07..0e48af749c 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -18,7 +18,6 @@ import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; import * as Matrix from 'matrix-js-sdk'; -import {EventTimelineSet} from 'matrix-js-sdk'; import {EventTimeline} from 'matrix-js-sdk'; /* @@ -420,27 +419,27 @@ export default class EventIndex { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); - let loadArgs = { + const loadArgs = { roomId: room.roomId, - limit: limit - } + limit: limit, + }; if (fromEvent) { loadArgs.fromEvent = fromEvent; loadArgs.direction = direction; } - let events + let events; // Get our events from the event index. try { events = await indexManager.loadFileEvents(loadArgs); } catch (e) { console.log("EventIndex: Error getting file events", e); - return [] + return []; } - let eventMapper = client.getEventMapper(); + const eventMapper = client.getEventMapper(); // Turn the events into MatrixEvent objects. const matrixEvents = events.map(e => { @@ -466,8 +465,8 @@ export default class EventIndex { room_id: matrixEvent.getRoomId(), sender: matrixEvent.getSender(), origin_server_ts: matrixEvent.getTs(), - state_key: matrixEvent.getSender() - } + state_key: matrixEvent.getSender(), + }, ); // We set this manually to avoid emitting RoomMember.membership and @@ -483,13 +482,13 @@ export default class EventIndex { async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { - let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS) + direction == EventTimeline.BACKWARDS); } }); @@ -510,12 +509,12 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - var count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction == EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { timelineWindow._eventCount += count; - var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); @@ -544,7 +543,7 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit) + moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; return ret; From f324f676d37ae5d330beedc77268bbb954fe2cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:14:53 +0100 Subject: [PATCH 019/282] EventIndex: Add a method to get the current disk usage of the index. --- src/indexing/BaseEventIndexManager.js | 7 +++++++ src/indexing/EventIndex.js | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index 5e8ca668ad..733dc05dd6 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -117,6 +117,13 @@ export default class BaseEventIndexManager { throw new Error("Unimplemented"); } + /** + * Get the disk usage of the index + */ + async indexSize(): Promise { + throw new Error("Unimplemented"); + } + /** * Commit the previously queued up events to the index. * diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c912e31fa5..ae738e5d4d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -406,4 +406,9 @@ export default class EventIndex { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.searchEventIndex(searchArgs); } + + async indexSize() { + const indexManager = PlatformPeg.get().getEventIndexingManager(); + return indexManager.indexSize(); + } } From b7b66cfd9aad22f3a00e3b3126a708d1d81e0447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:15:55 +0100 Subject: [PATCH 020/282] EventIndex: Use the sleep method from our utils. --- src/indexing/EventIndex.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index ae738e5d4d..f034df888c 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -16,6 +16,7 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; +import {sleep} from "../utils/promise"; /* * Event indexing class that wraps the platform specific event indexing. @@ -180,12 +181,6 @@ export default class EventIndex { } async crawlerFunc() { - // TODO either put this in a better place or find a library provided - // method that does this. - const sleep = async (ms) => { - return new Promise(resolve => setTimeout(resolve, ms)); - }; - let cancelled = false; console.log("EventIndex: Started crawler function"); From 47156351a6d9ffe9ee2110c63c8667fc043c8936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:25:34 +0100 Subject: [PATCH 021/282] EventIndex: Use a setting for the crawler sleep time. --- src/indexing/EventIndex.js | 24 ++++++++++++++++++++---- src/settings/Settings.js | 5 +++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index f034df888c..1e15fcaa5a 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -16,6 +16,8 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; +import SettingsStore from '../settings/SettingsStore'; +import {SettingLevel} from "../settings/SettingsStore"; import {sleep} from "../utils/promise"; /* @@ -24,9 +26,9 @@ import {sleep} from "../utils/promise"; export default class EventIndex { constructor() { this.crawlerCheckpoints = []; - // The time that the crawler will wait between /rooms/{room_id}/messages - // requests - this._crawlerTimeout = 3000; + // The time in ms that the crawler will wait loop iterations if there + // have not been any checkpoints to consume in the last iteration. + this._crawlerIdleTime = 5000; // The maximum number of events our crawler should fetch in a single // crawl. this._eventsPerCrawl = 100; @@ -194,11 +196,22 @@ export default class EventIndex { cancelled = true; }; + let idle = false; + while (!cancelled) { // This is a low priority task and we don't want to spam our // homeserver with /messages requests so we set a hefty timeout // here. - await sleep(this._crawlerTimeout); + let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'); + + // Don't let the user configure a lower sleep time than 100 ms. + sleepTime = Math.max(sleepTime, 100); + + if (idle) { + sleepTime = this._crawlerIdleTime; + } + + await sleep(sleepTime); console.log("EventIndex: Running the crawler loop."); @@ -211,9 +224,12 @@ export default class EventIndex { /// There is no checkpoint available currently, one may appear if // a sync with limited room timelines happens, so go back to sleep. if (checkpoint === undefined) { + idle = true; continue; } + idle = false; + console.log("EventIndex: crawling using checkpoint", checkpoint); // We have a checkpoint, let us fetch some messages, again, very diff --git a/src/settings/Settings.js b/src/settings/Settings.js index eacf63e55d..e967becf98 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -486,4 +486,9 @@ export const SETTINGS = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: RIGHT_PANEL_PHASES.GroupMemberList, }, + "crawlerSleepTime": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + displayName: _td("How long should the crawler wait between requests"), + default: 3000, + } }; From 0132c3bbe3521ecd2af39bfd526ea41048791619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:31:16 +0100 Subject: [PATCH 022/282] EventIndex: Start the crawler only if it's configured to start. --- src/indexing/EventIndex.js | 4 +++- src/settings/Settings.js | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 1e15fcaa5a..c907e769b5 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -120,7 +120,9 @@ export default class EventIndex { if (eventIndexWasEmpty) await addInitialCheckpoints(); // Start our crawler. - this.startCrawler(); + if (SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling')) { + this.startCrawler(); + } return; } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index e967becf98..1c65c38167 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -490,5 +490,10 @@ export const SETTINGS = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("How long should the crawler wait between requests"), default: 3000, + }, + "enableCrawling" : { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + displayName: _td("How long should the crawler wait between requests"), + default: true, } }; From 4fe7752f3cd9d52b97d212afc49fc46c60d318d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:37:07 +0100 Subject: [PATCH 023/282] EventIndex: Add a method to gather the currently crawled rooms. --- src/indexing/EventIndex.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c907e769b5..1b17cc6d87 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -33,6 +33,7 @@ export default class EventIndex { // crawl. this._eventsPerCrawl = 100; this._crawler = null; + this._currentCheckpoint = null; this.liveEventsForIndex = new Set(); } @@ -213,6 +214,8 @@ export default class EventIndex { sleepTime = this._crawlerIdleTime; } + this._currentCheckpoint = null; + await sleep(sleepTime); console.log("EventIndex: Running the crawler loop."); @@ -230,6 +233,8 @@ export default class EventIndex { continue; } + this._currentCheckpoint = checkpoint; + idle = false; console.log("EventIndex: crawling using checkpoint", checkpoint); @@ -424,4 +429,31 @@ export default class EventIndex { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.indexSize(); } + + currentlyCrawledRooms() { + let crawlingRooms = new Set(); + let totalRooms = new Set(); + + this.crawlerCheckpoints.forEach((checkpoint, index) => { + crawlingRooms.add(checkpoint.roomId); + }); + + if (this._currentCheckpoint !== null) { + crawlingRooms.add(this._currentCheckpoint.roomId); + } + + const client = MatrixClientPeg.get(); + const rooms = client.getRooms(); + + const isRoomEncrypted = (room) => { + return client.isRoomEncrypted(room.roomId); + }; + + const encryptedRooms = rooms.filter(isRoomEncrypted); + encryptedRooms.forEach((room, index) => { + totalRooms.add(room.roomId); + }); + + return {crawlingRooms, totalRooms} + } } From 928bb69b116fbfb3041d6b2538080b37f503e7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:37:53 +0100 Subject: [PATCH 024/282] EventIndexPeg: Add a helper method to easily start the crawler. --- src/indexing/EventIndexPeg.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index 3746591b1f..a63756ab4e 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -69,6 +69,11 @@ class EventIndexPeg { return this.index; } + start() { + if (this.index === null) return; + this.index.startCrawler(); + } + stop() { if (this.index === null) return; this.index.stopCrawler(); From 2fe36037371fc723d4d7219e884b8fb77298a8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:39:45 +0100 Subject: [PATCH 025/282] utils: Add an utility function to format bytes. --- src/utils/FormattingUtils.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utils/FormattingUtils.js b/src/utils/FormattingUtils.js index 1fd7d00feb..9016d62cfb 100644 --- a/src/utils/FormattingUtils.js +++ b/src/utils/FormattingUtils.js @@ -30,6 +30,22 @@ export function formatCount(count) { return (count / 1000000000).toFixed(1) + "B"; // 10B is enough for anyone, right? :S } +/** + * format a size in bytes into a human readable form + * e.g: 1024 -> 1.00 KB + */ +export function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + /** * format a key into groups of 4 characters, for easier visual inspection * From c397de18bdea962ca8dca8f2f1a98c26aa29941d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 13:43:47 +0100 Subject: [PATCH 026/282] PreferencesUserSettingsTab: Add initial event indexing preferences. --- .../tabs/user/PreferencesUserSettingsTab.js | 91 +++++++++++++++++++ src/i18n/strings/en_EN.json | 9 ++ 2 files changed, 100 insertions(+) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index db5b95cb4c..d1fed21a1f 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,6 +23,8 @@ import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; +import EventIndexPeg from "../../../../../indexing/EventIndexPeg"; +import {formatBytes} from "../../../../../utils/FormattingUtils"; export default class PreferencesUserSettingsTab extends React.Component { static COMPOSER_SETTINGS = [ @@ -70,6 +72,13 @@ export default class PreferencesUserSettingsTab extends React.Component { alwaysShowMenuBarSupported: false, minimizeToTray: true, minimizeToTraySupported: false, + eventIndexSize: 0, + crawlingRooms: 0, + totalCrawlingRooms: 0, + eventIndexingEnabled: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), + crawlerSleepTime: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay').toString(10), readMarkerInViewThresholdMs: @@ -100,6 +109,19 @@ export default class PreferencesUserSettingsTab extends React.Component { minimizeToTray = await platform.getMinimizeToTrayEnabled(); } + let eventIndexSize = 0; + let crawlingRooms = 0; + let totalCrawlingRooms = 0; + + let eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndexSize = await eventIndex.indexSize(); + let crawledRooms = eventIndex.currentlyCrawledRooms(); + crawlingRooms = crawledRooms.crawlingRooms.size; + totalCrawlingRooms = crawledRooms.totalRooms.size; + } + this.setState({ autoLaunch, autoLaunchSupported, @@ -107,6 +129,9 @@ export default class PreferencesUserSettingsTab extends React.Component { alwaysShowMenuBar, minimizeToTraySupported, minimizeToTray, + eventIndexSize, + crawlingRooms, + totalCrawlingRooms, }); } @@ -137,6 +162,20 @@ export default class PreferencesUserSettingsTab extends React.Component { SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); }; + _onEventIndexingEnabledChange = (checked) => { + SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); + + if (checked) EventIndexPeg.start(); + else EventIndexPeg.stop(); + + this.setState({eventIndexingEnabled: checked}); + } + + _onCrawlerSleepTimeChange = (e) => { + this.setState({crawlerSleepTime: e.target.value}); + SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); + } + _renderGroup(settingIds) { const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return settingIds.map(i => ); @@ -167,9 +206,61 @@ export default class PreferencesUserSettingsTab extends React.Component { label={_t('Show tray icon and minimize window to it on close')} />; } + let eventIndexingSettings = null; + let crawlerState; + + if (!this.state.eventIndexingEnabled) { + crawlerState =
{_t("Message downloader is stopped.")}
; + } + else if (this.state.crawlingRooms === 0) { + crawlerState =
{_t("Message downloader is currently idle.")}
; + } else { + crawlerState = ( +
{_t( + "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", + { crawlingRooms: this.state.crawlingRooms, + totalRooms: this.state.totalCrawlingRooms, + })} +
+ ); + } + + if (EventIndexPeg.get() !== null) { + eventIndexingSettings = ( +
+ {_t("Encrypted search")} + { + _t( "To enable search in encrypted rooms, Riot needs to run " + + "a background process to download historical messages " + + "from those rooms to your computer." + ) + } +
+ {_t("Message disk usage:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {crawlerState}
+
+ + + + +
+ ); + } + return (
{_t("Preferences")}
+ + {eventIndexingSettings} +
{_t("Composer")} {this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f8b17db7c5..aa8583cf8d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -415,6 +415,7 @@ "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", "Show previews/thumbnails for images": "Show previews/thumbnails for images", + "How long should the crawler wait between requests": "How long should the crawler wait between requests", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", @@ -731,6 +732,14 @@ "Start automatically after system login": "Start automatically after system login", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", + "Message downloader is stopped.": "Message downloader is stopped.", + "Message downloader is currently idle.": "Message downloader is currently idle.", + "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.": "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", + "Encrypted search": "Encrypted search", + "To enable search in encrypted rooms, Riot needs to run a background process to download historical messages from those rooms to your computer.": "To enable search in encrypted rooms, Riot needs to run a background process to download historical messages from those rooms to your computer.", + "Message disk usage:": "Message disk usage:", + "Enable message downloading": "Enable message downloading", + "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", "Preferences": "Preferences", "Composer": "Composer", "Timeline": "Timeline", From 3c46a563914f0347ebdfed3f80161ea86b4e841b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Nov 2019 15:06:04 +0100 Subject: [PATCH 027/282] EventIndex: Fix some lint errors. --- .../settings/tabs/user/PreferencesUserSettingsTab.js | 11 +++++------ src/indexing/EventIndex.js | 6 +++--- src/settings/Settings.js | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d1fed21a1f..e47f591dcb 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -113,11 +113,11 @@ export default class PreferencesUserSettingsTab extends React.Component { let crawlingRooms = 0; let totalCrawlingRooms = 0; - let eventIndex = EventIndexPeg.get(); + const eventIndex = EventIndexPeg.get(); if (eventIndex !== null) { eventIndexSize = await eventIndex.indexSize(); - let crawledRooms = eventIndex.currentlyCrawledRooms(); + const crawledRooms = eventIndex.currentlyCrawledRooms(); crawlingRooms = crawledRooms.crawlingRooms.size; totalCrawlingRooms = crawledRooms.totalRooms.size; } @@ -211,8 +211,7 @@ export default class PreferencesUserSettingsTab extends React.Component { if (!this.state.eventIndexingEnabled) { crawlerState =
{_t("Message downloader is stopped.")}
; - } - else if (this.state.crawlingRooms === 0) { + } else if (this.state.crawlingRooms === 0) { crawlerState =
{_t("Message downloader is currently idle.")}
; } else { crawlerState = ( @@ -231,8 +230,8 @@ export default class PreferencesUserSettingsTab extends React.Component { {_t("Encrypted search")} { _t( "To enable search in encrypted rooms, Riot needs to run " + - "a background process to download historical messages " + - "from those rooms to your computer." + "a background process to download historical messages " + + "from those rooms to your computer.", ) }
diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 1b17cc6d87..0d7f43b839 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -431,8 +431,8 @@ export default class EventIndex { } currentlyCrawledRooms() { - let crawlingRooms = new Set(); - let totalRooms = new Set(); + const crawlingRooms = new Set(); + const totalRooms = new Set(); this.crawlerCheckpoints.forEach((checkpoint, index) => { crawlingRooms.add(checkpoint.roomId); @@ -454,6 +454,6 @@ export default class EventIndex { totalRooms.add(room.roomId); }); - return {crawlingRooms, totalRooms} + return {crawlingRooms, totalRooms}; } } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 1c65c38167..817adcfc4d 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -491,9 +491,9 @@ export const SETTINGS = { displayName: _td("How long should the crawler wait between requests"), default: 3000, }, - "enableCrawling" : { + "enableCrawling": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("How long should the crawler wait between requests"), default: true, - } + }, }; From 3b99f7565dbbf453eb615f9034db2dfd574c9128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 17:10:59 +0100 Subject: [PATCH 028/282] PreferencesUserSettingsTab: Move the event index UI into a separate component. --- .../views/settings/EventIndexPanel.js | 131 ++++++++++++++++++ .../tabs/user/PreferencesUserSettingsTab.js | 90 +----------- 2 files changed, 134 insertions(+), 87 deletions(-) create mode 100644 src/components/views/settings/EventIndexPanel.js diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js new file mode 100644 index 0000000000..98ba83f62b --- /dev/null +++ b/src/components/views/settings/EventIndexPanel.js @@ -0,0 +1,131 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import * as sdk from '../../../index'; +import { _t } from '../../../languageHandler'; +import Modal from '../../../Modal'; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; +import Field from "../elements/Field"; +import {formatBytes} from "../../../utils/FormattingUtils"; +import EventIndexPeg from "../../../indexing/EventIndexPeg"; + +export default class EventIndexPanel extends React.Component { + constructor() { + super(); + + this.state = { + eventIndexSize: 0, + crawlingRooms: 0, + totalCrawlingRooms: 0, + eventIndexingEnabled: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), + crawlerSleepTime: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), + }; + } + + async componentWillMount(): void { + let eventIndexSize = 0; + let crawlingRooms = 0; + let totalCrawlingRooms = 0; + + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndexSize = await eventIndex.indexSize(); + const crawledRooms = eventIndex.currentlyCrawledRooms(); + crawlingRooms = crawledRooms.crawlingRooms.size; + totalCrawlingRooms = crawledRooms.totalRooms.size; + } + + this.setState({ + eventIndexSize, + crawlingRooms, + totalCrawlingRooms, + }); + } + + _onEventIndexingEnabledChange = (checked) => { + SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); + + if (checked) EventIndexPeg.start(); + else EventIndexPeg.stop(); + + this.setState({eventIndexingEnabled: checked}); + } + + _onCrawlerSleepTimeChange = (e) => { + this.setState({crawlerSleepTime: e.target.value}); + SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); + } + + render() { + let eventIndexingSettings = null; + let crawlerState; + + if (!this.state.eventIndexingEnabled) { + crawlerState =
{_t("Message downloader is stopped.")}
; + } else if (this.state.crawlingRooms === 0) { + crawlerState =
{_t("Message downloader is currently idle.")}
; + } else { + crawlerState = ( +
{_t( + "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", + { crawlingRooms: this.state.crawlingRooms, + totalRooms: this.state.totalCrawlingRooms, + })} +
+ ); + } + + if (EventIndexPeg.get() !== null) { + eventIndexingSettings = ( +
+ {_t("Encrypted search")} + { + _t( "To enable search in encrypted rooms, Riot needs to run " + + "a background process to download historical messages " + + "from those rooms to your computer.", + ) + } +
+ {_t("Message disk usage:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {crawlerState}
+
+ + + + +
+ ); + } + + return eventIndexingSettings; + } +} diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index e47f591dcb..5ecafcc5ae 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,7 +23,6 @@ import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; -import EventIndexPeg from "../../../../../indexing/EventIndexPeg"; import {formatBytes} from "../../../../../utils/FormattingUtils"; export default class PreferencesUserSettingsTab extends React.Component { @@ -72,13 +71,6 @@ export default class PreferencesUserSettingsTab extends React.Component { alwaysShowMenuBarSupported: false, minimizeToTray: true, minimizeToTraySupported: false, - eventIndexSize: 0, - crawlingRooms: 0, - totalCrawlingRooms: 0, - eventIndexingEnabled: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), - crawlerSleepTime: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay').toString(10), readMarkerInViewThresholdMs: @@ -109,19 +101,6 @@ export default class PreferencesUserSettingsTab extends React.Component { minimizeToTray = await platform.getMinimizeToTrayEnabled(); } - let eventIndexSize = 0; - let crawlingRooms = 0; - let totalCrawlingRooms = 0; - - const eventIndex = EventIndexPeg.get(); - - if (eventIndex !== null) { - eventIndexSize = await eventIndex.indexSize(); - const crawledRooms = eventIndex.currentlyCrawledRooms(); - crawlingRooms = crawledRooms.crawlingRooms.size; - totalCrawlingRooms = crawledRooms.totalRooms.size; - } - this.setState({ autoLaunch, autoLaunchSupported, @@ -129,9 +108,6 @@ export default class PreferencesUserSettingsTab extends React.Component { alwaysShowMenuBar, minimizeToTraySupported, minimizeToTray, - eventIndexSize, - crawlingRooms, - totalCrawlingRooms, }); } @@ -162,26 +138,14 @@ export default class PreferencesUserSettingsTab extends React.Component { SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); }; - _onEventIndexingEnabledChange = (checked) => { - SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); - - if (checked) EventIndexPeg.start(); - else EventIndexPeg.stop(); - - this.setState({eventIndexingEnabled: checked}); - } - - _onCrawlerSleepTimeChange = (e) => { - this.setState({crawlerSleepTime: e.target.value}); - SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); - } - _renderGroup(settingIds) { const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return settingIds.map(i => ); } render() { + const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel'); + let autoLaunchOption = null; if (this.state.autoLaunchSupported) { autoLaunchOption = ; } - let eventIndexingSettings = null; - let crawlerState; - - if (!this.state.eventIndexingEnabled) { - crawlerState =
{_t("Message downloader is stopped.")}
; - } else if (this.state.crawlingRooms === 0) { - crawlerState =
{_t("Message downloader is currently idle.")}
; - } else { - crawlerState = ( -
{_t( - "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", - { crawlingRooms: this.state.crawlingRooms, - totalRooms: this.state.totalCrawlingRooms, - })} -
- ); - } - - if (EventIndexPeg.get() !== null) { - eventIndexingSettings = ( -
- {_t("Encrypted search")} - { - _t( "To enable search in encrypted rooms, Riot needs to run " + - "a background process to download historical messages " + - "from those rooms to your computer.", - ) - } -
- {_t("Message disk usage:")} {formatBytes(this.state.eventIndexSize, 0)}
- {crawlerState}
-
- - - - -
- ); - } - return (
{_t("Preferences")}
- {eventIndexingSettings} +
{_t("Composer")} From 695b8aff5b4672d3af70db317c023f84c9968aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 17:14:55 +0100 Subject: [PATCH 029/282] EventIndexPanel: Reword the enable/disable setting. --- src/components/views/settings/EventIndexPanel.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 98ba83f62b..bade2d8735 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -114,7 +114,7 @@ export default class EventIndexPanel extends React.Component { + label={_t('Download and index encrypted messages')} /> Date: Mon, 20 Jan 2020 10:06:20 +0100 Subject: [PATCH 030/282] Lifecycle: Comment why we need to initialize the index before the client. --- src/Lifecycle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index aab7884b2e..b52e5e9da5 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,6 +588,9 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { + // The client might want to populate some views with events from the + // index (e.g. the FilePanel), therefore initialize the event index + // before the client. await EventIndexPeg.init(); await MatrixClientPeg.start(); } else { From 0c854fce9b19543a57e2494b24b494faefcca35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 10:09:58 +0100 Subject: [PATCH 031/282] FilePanel: Remove a stale comment. --- src/components/structures/FilePanel.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6faec27284..cb788cd6d2 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,6 @@ const FilePanel = createReactClass({ }, ); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - // TODO: Remove this stale comment? Which comment above? const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; From b4c8a686cec16290f56603d3fe925ddc1d975830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:41:55 +0100 Subject: [PATCH 032/282] EventIndex: Don't import the whole js-sdk. --- src/indexing/EventIndex.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0e48af749c..67a3d4cace 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -16,9 +16,7 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; - -import * as Matrix from 'matrix-js-sdk'; -import {EventTimeline} from 'matrix-js-sdk'; +import {EventTimeline, RoomMember} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -445,7 +443,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); + const member = new RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 0b4b9d8d5d166ed7454b7d729fc5a08478ac6fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:42:46 +0100 Subject: [PATCH 033/282] EventIndex: Simplify the json event getting logic. --- src/indexing/EventIndex.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 67a3d4cace..e361f66edc 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,10 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -311,10 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 4cf44cf5a561f801c107b121f109e18236db0e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:43:20 +0100 Subject: [PATCH 034/282] EventIndex/FilePanel: Allow longer lines. --- src/components/structures/FilePanel.js | 3 +-- src/indexing/EventIndex.js | 16 ++++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index cb788cd6d2..71e8143f0a 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,7 @@ const FilePanel = createReactClass({ }, ); - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, - filter); + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index e361f66edc..cb77d92c27 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -479,15 +479,13 @@ export default class EventIndex { // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS); + timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); } }); // Set the pagination token to the oldest event that we retrieved. if (matrixEvents.length > 0) { - timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), - EventTimeline.BACKWARDS); + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), EventTimeline.BACKWARDS); return true; } else { timeline.setPaginationToken("", EventTimeline.BACKWARDS); @@ -501,7 +499,7 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction === EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { @@ -509,7 +507,7 @@ export default class EventIndex { const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { - timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); } return true; } @@ -532,8 +530,7 @@ export default class EventIndex { const timelineSet = timelineWindow._timelineSet; const token = timeline.timeline.getPaginationToken(direction); - const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, - room, limit, token, direction); + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; @@ -541,8 +538,7 @@ export default class EventIndex { return ret; }; - const paginationPromise = paginationMethod(timelineWindow, tl, room, - direction, limit); + const paginationPromise = paginationMethod(timelineWindow, tl, room, direction, limit); tl.pendingPaginate = paginationPromise; return paginationPromise; From d2ef6ba3f52395b93f38833b87b2d50d1962c89f Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 14:27:19 +0000 Subject: [PATCH 035/282] js-sdk 4.0.0-rc.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d4e58c80e4..d24fcd1777 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "3.0.0", + "matrix-js-sdk": "4.0.0-rc.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", "prop-types": "^15.5.8", diff --git a/yarn.lock b/yarn.lock index 0177629cd8..808721a188 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5732,10 +5732,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw== -matrix-js-sdk@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-3.0.0.tgz#97908f9eda9eeb3ba0333b7e474c45f2b258e50c" - integrity sha512-lzUMwJAZHw7Dk0K+rubqe6kEpy4+pJ+qCp8n6lisfdKfMDJXdNCkjiiXRnakM1ZD4PFYK8ju89+NfxlyhAAd4A== +matrix-js-sdk@4.0.0-rc.1: + version "4.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-4.0.0-rc.1.tgz#160d445364aa8ff36ae4fffb0ebc944f680afa4e" + integrity sha512-1tP9fUCU5I4bXQQHuVlKHbpySh71aM6sugVC6L8ikG1ebZuKkAc2tXf1tiHizp3hZadHoTZ1PGH19ZFlKDZ8Qw== dependencies: another-json "^0.2.0" browser-request "^0.3.3" From 6c8f20df60f2cb1b9dfbe2de434d1c63f89ee9be Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 20 Jan 2020 14:34:07 +0000 Subject: [PATCH 036/282] Prepare changelog for v2.0.0-rc.1 --- CHANGELOG.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae2711e25..eda0e0e911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,159 @@ +Changes in [2.0.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0-rc.1) (2020-01-20) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.6...v2.0.0-rc.1) + +BREAKING CHANGES +================ + * The react-sdk node module now exports ES6 rather than ES5. If you + wish to supports target that aren't compatible with ES6, you + will need to transpile the react-sdk to a suitable dialect. + +All Changes +=========== + * Fix arrows keys moving through edit history + [\#3874](https://github.com/matrix-org/matrix-react-sdk/pull/3874) + * Fix error about MessagePanel not being available for read markers + [\#3867](https://github.com/matrix-org/matrix-react-sdk/pull/3867) + * Adjust secret storage to work before sync + [\#3864](https://github.com/matrix-org/matrix-react-sdk/pull/3864) + * Update from Weblate + [\#3872](https://github.com/matrix-org/matrix-react-sdk/pull/3872) + * Remove unused deps and dev-deps + [\#3870](https://github.com/matrix-org/matrix-react-sdk/pull/3870) + * Tidy Jest test stuff and dependencies + [\#3869](https://github.com/matrix-org/matrix-react-sdk/pull/3869) + * Move feature flag check for new session toast + [\#3865](https://github.com/matrix-org/matrix-react-sdk/pull/3865) + * Catch exception in checkTerms if no ID server + [\#3863](https://github.com/matrix-org/matrix-react-sdk/pull/3863) + * Catch exception if passphrase dialog cancelled + [\#3862](https://github.com/matrix-org/matrix-react-sdk/pull/3862) + * Disable key request dialogs with cross-signing + [\#3860](https://github.com/matrix-org/matrix-react-sdk/pull/3860) + * Toasts for new, unverified sessions + [\#3859](https://github.com/matrix-org/matrix-react-sdk/pull/3859) + * Check for a matrixclient before trying to use it + [\#3861](https://github.com/matrix-org/matrix-react-sdk/pull/3861) + * Room header & message box shields now reflect cross-signing state + [\#3850](https://github.com/matrix-org/matrix-react-sdk/pull/3850) + * Fix Array.concat undefined + [\#3857](https://github.com/matrix-org/matrix-react-sdk/pull/3857) + * Update chokidar to fix reskindex not working + [\#3856](https://github.com/matrix-org/matrix-react-sdk/pull/3856) + * Make the new DM invite dialog work for regular invites too + [\#3854](https://github.com/matrix-org/matrix-react-sdk/pull/3854) + * Fix event handler leak in MemberStatusMessageAvatar + [\#3855](https://github.com/matrix-org/matrix-react-sdk/pull/3855) + * Move DM creation logic into DMInviteDialog + [\#3843](https://github.com/matrix-org/matrix-react-sdk/pull/3843) + * Remove all text when cutting in the composer + [\#3848](https://github.com/matrix-org/matrix-react-sdk/pull/3848) + * Add a ToastStore + [\#3853](https://github.com/matrix-org/matrix-react-sdk/pull/3853) + * 'Members' button always toggle the right panel + [\#3804](https://github.com/matrix-org/matrix-react-sdk/pull/3804) + * Fix timing of when Composer considers itself to be modified + [\#3842](https://github.com/matrix-org/matrix-react-sdk/pull/3842) + * Compute download file icon immediately + [\#3851](https://github.com/matrix-org/matrix-react-sdk/pull/3851) + * Fix not being able to open profiles from the timeline + [\#3852](https://github.com/matrix-org/matrix-react-sdk/pull/3852) + * Add post-login complete security flow + [\#3847](https://github.com/matrix-org/matrix-react-sdk/pull/3847) + * Added cut/copy and pasting user pills from editor. + [\#3828](https://github.com/matrix-org/matrix-react-sdk/pull/3828) + * Fix imports for help & support tab + [\#3846](https://github.com/matrix-org/matrix-react-sdk/pull/3846) + * Humanize the recent DM rooms ourselves for translations + [\#3841](https://github.com/matrix-org/matrix-react-sdk/pull/3841) + * Improve the quality of invite suggestions by filtering out DMs + [\#3840](https://github.com/matrix-org/matrix-react-sdk/pull/3840) + * Fix linter and tests on develop + [\#3845](https://github.com/matrix-org/matrix-react-sdk/pull/3845) + * Fix sourcemaps by refactoring the build system + [\#3839](https://github.com/matrix-org/matrix-react-sdk/pull/3839) + * Don't error on unverified/unknown devices. + [\#3837](https://github.com/matrix-org/matrix-react-sdk/pull/3837) + * Padlock icons in room header + [\#3835](https://github.com/matrix-org/matrix-react-sdk/pull/3835) + * Don't allow upgrade from untrusted key backup. + [\#3822](https://github.com/matrix-org/matrix-react-sdk/pull/3822) + * Emoji verification: Change name of 🔒 to lock + [\#3825](https://github.com/matrix-org/matrix-react-sdk/pull/3825) + * Room padlock decorations only if cross-signing is enabled + [\#3838](https://github.com/matrix-org/matrix-react-sdk/pull/3838) + * Enable end-to-end tests for sourcemaps (+Windows instructions) + [\#3827](https://github.com/matrix-org/matrix-react-sdk/pull/3827) + * Repair community member info panel + [\#3832](https://github.com/matrix-org/matrix-react-sdk/pull/3832) + * Add feature flag around the presence indicator in room list + [\#3831](https://github.com/matrix-org/matrix-react-sdk/pull/3831) + * Display a padlock icon beside invite-only rooms in the room list + [\#3821](https://github.com/matrix-org/matrix-react-sdk/pull/3821) + * Update from Weblate + [\#3830](https://github.com/matrix-org/matrix-react-sdk/pull/3830) + * Fix listener leak on RoomView + [\#3826](https://github.com/matrix-org/matrix-react-sdk/pull/3826) + * Regenerate i18n for sourcemaps branch + [\#3824](https://github.com/matrix-org/matrix-react-sdk/pull/3824) + * Fix tests for sourcemaps branch + [\#3823](https://github.com/matrix-org/matrix-react-sdk/pull/3823) + * Jest + [\#3724](https://github.com/matrix-org/matrix-react-sdk/pull/3724) + * Sourcemaps: develop -> feature branch + [\#3817](https://github.com/matrix-org/matrix-react-sdk/pull/3817) + * Support pasting a bunch of identifiers into the invite dialog + [\#3820](https://github.com/matrix-org/matrix-react-sdk/pull/3820) + * Support 3PIDs (email addresses) in the invite dialog + [\#3819](https://github.com/matrix-org/matrix-react-sdk/pull/3819) + * Placeholder PR for cleaner diffs: ES6 + [\#3765](https://github.com/matrix-org/matrix-react-sdk/pull/3765) + * Misc fixes for ES6 imports/exports + [\#3766](https://github.com/matrix-org/matrix-react-sdk/pull/3766) + * Wire up the invite targets dialog to a real composer and show selections + [\#3815](https://github.com/matrix-org/matrix-react-sdk/pull/3815) + * Change ref handling in TextualBody to prevent it parsing generated nodes + [\#3711](https://github.com/matrix-org/matrix-react-sdk/pull/3711) + * Render encoded html entities in og:description + [\#3789](https://github.com/matrix-org/matrix-react-sdk/pull/3789) + * Update package.json for new build process + cosmetics + [\#3767](https://github.com/matrix-org/matrix-react-sdk/pull/3767) + * Convert CommonJS exports to ES6 exports + [\#3761](https://github.com/matrix-org/matrix-react-sdk/pull/3761) + * Round 2 of CommonJS to ES6 imports + [\#3764](https://github.com/matrix-org/matrix-react-sdk/pull/3764) + * Strip all variation selectors on emoji + [\#3814](https://github.com/matrix-org/matrix-react-sdk/pull/3814) + * Use the new js-sdk imports and import from src + [\#3763](https://github.com/matrix-org/matrix-react-sdk/pull/3763) + * Convert many imports to handle ES6 exports + [\#3762](https://github.com/matrix-org/matrix-react-sdk/pull/3762) + * Fix userinfo for users not in the room + [\#3812](https://github.com/matrix-org/matrix-react-sdk/pull/3812) + * Attempt to fix e2e tests + [\#3811](https://github.com/matrix-org/matrix-react-sdk/pull/3811) + * Add bunch of null-guards and similar to fix React Errors/complaints + [\#3752](https://github.com/matrix-org/matrix-react-sdk/pull/3752) + * Delegate all room alias validation to the RoomAliasField validator + [\#3807](https://github.com/matrix-org/matrix-react-sdk/pull/3807) + * Support filtering and searching for users to invite in DMs + [\#3802](https://github.com/matrix-org/matrix-react-sdk/pull/3802) + * Add suggestions for which users to invite to chat + [\#3801](https://github.com/matrix-org/matrix-react-sdk/pull/3801) + * Use `flex-start` instead of `start` for postcss + [\#3760](https://github.com/matrix-org/matrix-react-sdk/pull/3760) + * Define getLanguageFromBrowser() for LanguageDropdown + [\#3769](https://github.com/matrix-org/matrix-react-sdk/pull/3769) + * Introduce babel's export-default-from plugin to fix build errors + [\#3768](https://github.com/matrix-org/matrix-react-sdk/pull/3768) + * Add a bit of debugging to incorrect components in the Skinner + [\#3770](https://github.com/matrix-org/matrix-react-sdk/pull/3770) + * [BREAKING] Refactor the entire build process for babel@7 and TypeScript + (chunk 1 of many) + [\#3722](https://github.com/matrix-org/matrix-react-sdk/pull/3722) + * Implementation of new potential skinning mechanism + [\#3723](https://github.com/matrix-org/matrix-react-sdk/pull/3723) + Changes in [1.7.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.7.6) (2020-01-13) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.6-rc.2...v1.7.6) From 4ee2fce1f11946fa48f84eb300bdfa1b195d0009 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 20 Jan 2020 14:34:07 +0000 Subject: [PATCH 037/282] v2.0.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d24fcd1777..b607ed04ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.7.6", + "version": "2.0.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From f77eb078498e1bdd761e176519fb5a5dc884529c Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 15:16:41 +0000 Subject: [PATCH 038/282] Verify individual messages via cross-signing Fixes #11880 --- res/css/views/rooms/_EventTile.scss | 21 ++++++++-- res/themes/light/css/_light.scss | 1 + src/components/views/rooms/EventTile.js | 51 +++++++++++++++++++++---- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index fbac1e932a..81ba547ff0 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -367,6 +367,11 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { opacity: 1; } +.mx_EventTile_e2eIcon_userVerified { + background-image: url('$(res)/img/e2e/normal.svg'); + opacity: 0.5; +} + .mx_EventTile_e2eIcon_unencrypted { background-image: url('$(res)/img/e2e/warning.svg'); opacity: 1; @@ -415,7 +420,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { padding-left: 60px; } @@ -427,8 +433,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { border-left: $e2e-unverified-color 5px solid; } +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { + border-left: $e2e-userVerified-color 5px solid; +} + .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified.mx_EventTile_info .mx_EventTile_line { padding-left: 78px; } @@ -439,14 +450,16 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > a > .mx_MessageTimestamp { left: 3px; width: auto; } // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > .mx_EventTile_e2eIcon { display: block; left: 41px; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 288fb3cadc..17b9a344ef 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -224,6 +224,7 @@ $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-userVerified-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index dce4dc8a93..4aefe6929b 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -235,6 +235,7 @@ export default createReactClass({ this._suppressReadReceiptAnimation = false; const client = this.context; client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.on("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.on("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); @@ -260,6 +261,7 @@ export default createReactClass({ componentWillUnmount: function() { const client = this.context; client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); @@ -282,18 +284,42 @@ export default createReactClass({ } }, + onUserVerificationChanged: function(userId, _trustStatus) { + if (userId === this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); + } + }, + _verifyEvent: async function(mxEvent) { if (!mxEvent.isEncrypted()) { return; } + // If we directly trust the device, short-circuit here const verified = await this.context.isEventSenderVerified(mxEvent); + if (verified) { + this.setState({ + verified: "verified" + }, () => { + // Decryption may have caused a change in size + this.props.onHeightChanged(); + }); + return; + } + + const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); + if (!eventSenderTrust) { + // We cannot find the device. Instead, we have to verify the user. + const userTrust = await this.context.checkUserTrust(mxEvent.getSender()); + this.setState({ + verified: userTrust.isVerified() ? "user-verified": "warning", + }, this.props.onHeightChanged); // Decryption may have cause a change in size + return; + } + this.setState({ - verified: verified, - }, () => { - // Decryption may have caused a change in size - this.props.onHeightChanged(); - }); + verified: eventSenderTrust.isVerified() ? "verified" : "warning", + }, this.props.onHeightChanged); // Decryption may have caused a change in size }, _propsEqual: function(objA, objB) { @@ -473,8 +499,10 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified) { + if (this.state.verified === "verified") { return; // no icon for verified + } else if (this.state.verified === "user-verified") { + return (); } else { return (); } @@ -604,8 +632,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === true, - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === false, + mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", + mx_EventTile_userVerified: !isBubbleMessage && this.state.verified === "user-verified", mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, @@ -901,6 +930,12 @@ function E2ePadlockUnencrypted(props) { ); } +function E2ePadlockUserVerified(props) { + return ( + + ); +} + class E2ePadlock extends React.Component { static propTypes = { icon: PropTypes.string.isRequired, From 51fb3b494f8a87f28e2b20f9eafe0e96275de924 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 15:25:01 +0000 Subject: [PATCH 039/282] lint and i18n --- src/components/views/rooms/EventTile.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 4aefe6929b..f2a77935bc 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -299,7 +299,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: "verified" + verified: "verified", }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4af203177c..510c1be0c7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -906,6 +906,7 @@ "This message cannot be decrypted": "This message cannot be decrypted", "Encrypted by an unverified device": "Encrypted by an unverified device", "Unencrypted": "Unencrypted", + "Encrypted by a deleted device": "Encrypted by a deleted device", "Please select the destination room for this message": "Please select the destination room for this message", "Scroll to bottom of page": "Scroll to bottom of page", "Close preview": "Close preview", From 1b9b30d4ea735798f344d4fbc36655d4f1ff6950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 17:42:24 +0100 Subject: [PATCH 040/282] EventIndexPanel: Get more stats for our indexer, not just the size. --- .../views/settings/EventIndexPanel.js | 40 +++++++++++++------ src/i18n/strings/en_EN.json | 18 +++++---- src/indexing/BaseEventIndexManager.js | 13 +++++- src/indexing/EventIndex.js | 27 ++++++++++++- 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index bade2d8735..f8c61e092d 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -35,6 +35,9 @@ export default class EventIndexPanel extends React.Component { eventIndexSize: 0, crawlingRooms: 0, totalCrawlingRooms: 0, + eventCount: 0, + roomCount: 0, + currentRoom: null, eventIndexingEnabled: SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), crawlerSleepTime: @@ -44,22 +47,35 @@ export default class EventIndexPanel extends React.Component { async componentWillMount(): void { let eventIndexSize = 0; + let roomCount = 0; + let eventCount = 0; let crawlingRooms = 0; let totalCrawlingRooms = 0; + let currentRoom = null; const eventIndex = EventIndexPeg.get(); if (eventIndex !== null) { - eventIndexSize = await eventIndex.indexSize(); + const stats = await eventIndex.getStats(); + eventIndexSize = stats.size; + roomCount = stats.roomCount; + eventCount = stats.eventCount; + const crawledRooms = eventIndex.currentlyCrawledRooms(); crawlingRooms = crawledRooms.crawlingRooms.size; totalCrawlingRooms = crawledRooms.totalRooms.size; + + const room = eventIndex.currentRoom(); + if (room) currentRoom = room.name; } this.setState({ eventIndexSize, crawlingRooms, totalCrawlingRooms, + eventCount, + roomCount, + currentRoom, }); } @@ -82,16 +98,15 @@ export default class EventIndexPanel extends React.Component { let crawlerState; if (!this.state.eventIndexingEnabled) { - crawlerState =
{_t("Message downloader is stopped.")}
; - } else if (this.state.crawlingRooms === 0) { - crawlerState =
{_t("Message downloader is currently idle.")}
; + crawlerState =
{_t("Message search for encrypted rooms is disabled.")}
; + } else if (this.state.currentRoom === null) { + crawlerState =
{_t("Not currently downloading messages for any room.")}
; } else { crawlerState = (
{_t( - "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", - { crawlingRooms: this.state.crawlingRooms, - totalRooms: this.state.totalCrawlingRooms, - })} + "Downloading mesages for %(currentRoom)s.", + { currentRoom: this.state.currentRoom } + )}
); } @@ -101,13 +116,14 @@ export default class EventIndexPanel extends React.Component {
{_t("Encrypted search")} { - _t( "To enable search in encrypted rooms, Riot needs to run " + - "a background process to download historical messages " + - "from those rooms to your computer.", + _t( "Riot is securely caching encrypted messages locally for them" + + "to appear in search results:" ) }
- {_t("Message disk usage:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {_t("Indexed messages:")} {this.state.eventCount}
+ {_t("Number of rooms:")} {this.state.roomCount}
{crawlerState}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1d1636bfb2..a968c145d9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -554,6 +554,16 @@ "Failed to set display name": "Failed to set display name", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", + "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", + "Not currently downloading messages for any room.": "Not currently downloading messages for any room.", + "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", + "Encrypted search": "Encrypted search", + "Riot is securely caching encrypted messages locally for themto appear in search results:": "Riot is securely caching encrypted messages locally for themto appear in search results:", + "Space used:": "Space used:", + "Indexed messages:": "Indexed messages:", + "Number of rooms:": "Number of rooms:", + "Download and index encrypted messages": "Download and index encrypted messages", + "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", "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.", @@ -732,14 +742,6 @@ "Start automatically after system login": "Start automatically after system login", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", - "Message downloader is stopped.": "Message downloader is stopped.", - "Message downloader is currently idle.": "Message downloader is currently idle.", - "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.": "Currently downloading mesages in %(crawlingRooms)s of %(totalRooms)s rooms.", - "Encrypted search": "Encrypted search", - "To enable search in encrypted rooms, Riot needs to run a background process to download historical messages from those rooms to your computer.": "To enable search in encrypted rooms, Riot needs to run a background process to download historical messages from those rooms to your computer.", - "Message disk usage:": "Message disk usage:", - "Download and index encrypted messages": "Download and index encrypted messages", - "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", "Preferences": "Preferences", "Composer": "Composer", "Timeline": "Timeline", diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index 733dc05dd6..819b3e65a7 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -67,6 +67,12 @@ export interface HistoricEvent { profile: MatrixProfile; } +export interface IndexStats { + size: number; + event_count: number; + room_count: number; +} + /** * Base class for classes that provide platform-specific event indexing. * @@ -118,9 +124,12 @@ export default class BaseEventIndexManager { } /** - * Get the disk usage of the index + * Get statistical information of the index. + * + * @return {Promise} A promise that will resolve to the index + * statistics. */ - async indexSize(): Promise { + async getStats(): Promise { throw new Error("Unimplemented"); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0d7f43b839..d00a0530ba 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -425,9 +425,9 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } - async indexSize() { + async getStats() { const indexManager = PlatformPeg.get().getEventIndexingManager(); - return indexManager.indexSize(); + return indexManager.getStats(); } currentlyCrawledRooms() { @@ -456,4 +456,27 @@ export default class EventIndex { return {crawlingRooms, totalRooms}; } + + /** + * Get the room that we are currently crawling. + * + * @returns A MatrixRoom that is being currently crawled, null if no room is + * currently being crawled. + */ + currentRoom() { + if (this._currentCheckpoint === null && this.crawlerCheckpoints.length === 0) { + console.log("EventIndex: No current nor any checkpoint"); + return null; + } + + const client = MatrixClientPeg.get(); + + if (this._currentCheckpoint !== null) { + console.log("EventIndex: Current checkpoint available"); + return client.getRoom(this._currentCheckpoint.roomId); + } else { + console.log("EventIndex: No current but have checkpoint available"); + return client.getRoom(this.crawlerCheckpoints[0].roomId); + } + } } From 8de149704e52c4b1872b32efb899bdd5fd0b6707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 17:43:55 +0100 Subject: [PATCH 041/282] EventIndexPanel: Dynamically update the indexer stats. --- .../views/settings/EventIndexPanel.js | 25 +++++++++++++++++++ src/indexing/EventIndex.js | 23 ++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index f8c61e092d..b777957a3a 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -45,6 +45,29 @@ export default class EventIndexPanel extends React.Component { }; } + async updateCurrentRoom(room) { + const eventIndex = EventIndexPeg.get(); + const stats = await eventIndex.getStats(); + let currentRoom = null; + + if (room) currentRoom = room.name; + + this.setState({ + eventIndexSize: stats.size, + roomCount: stats.roomCount, + eventCount: stats.eventCount, + currentRoom: currentRoom, + }); + } + + componentWillUnmount(): void { + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom.bind(this)); + } + } + async componentWillMount(): void { let eventIndexSize = 0; let roomCount = 0; @@ -56,6 +79,8 @@ export default class EventIndexPanel extends React.Component { const eventIndex = EventIndexPeg.get(); if (eventIndex !== null) { + eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this)); + const stats = await eventIndex.getStats(); eventIndexSize = stats.size; roomCount = stats.roomCount; diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index d00a0530ba..5676636eed 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -19,6 +19,7 @@ import {MatrixClientPeg} from "../MatrixClientPeg"; import SettingsStore from '../settings/SettingsStore'; import {SettingLevel} from "../settings/SettingsStore"; import {sleep} from "../utils/promise"; +import {EventEmitter} from "events"; /* * Event indexing class that wraps the platform specific event indexing. @@ -35,6 +36,7 @@ export default class EventIndex { this._crawler = null; this._currentCheckpoint = null; this.liveEventsForIndex = new Set(); + this._eventEmitter = new EventEmitter(); } async init() { @@ -185,6 +187,10 @@ export default class EventIndex { indexManager.addEventToIndex(e, profile); } + emitNewCheckpoint() { + this._eventEmitter.emit("changedCheckpoint", this.currentRoom()); + } + async crawlerFunc() { let cancelled = false; @@ -214,7 +220,10 @@ export default class EventIndex { sleepTime = this._crawlerIdleTime; } - this._currentCheckpoint = null; + if (this._currentCheckpoint !== null) { + this._currentCheckpoint = null; + this.emitNewCheckpoint(); + } await sleep(sleepTime); @@ -234,6 +243,7 @@ export default class EventIndex { } this._currentCheckpoint = checkpoint; + this.emitNewCheckpoint(); idle = false; @@ -465,18 +475,23 @@ export default class EventIndex { */ currentRoom() { if (this._currentCheckpoint === null && this.crawlerCheckpoints.length === 0) { - console.log("EventIndex: No current nor any checkpoint"); return null; } const client = MatrixClientPeg.get(); if (this._currentCheckpoint !== null) { - console.log("EventIndex: Current checkpoint available"); return client.getRoom(this._currentCheckpoint.roomId); } else { - console.log("EventIndex: No current but have checkpoint available"); return client.getRoom(this.crawlerCheckpoints[0].roomId); } } + + on(eventName, callback) { + this._eventEmitter.on(eventName, callback); + } + + removeListener(eventName, callback) { + this._eventEmitter.removeListener(eventName, callback); + } } From 12c4e453870f4570aa2cb85631be8efdfc423e97 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 17:14:31 +0000 Subject: [PATCH 042/282] User verified but device deleted isn't a useful state --- res/css/views/rooms/_EventTile.scss | 14 +++++++------- res/themes/light/css/_light.scss | 2 +- src/components/views/rooms/EventTile.js | 14 ++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 81ba547ff0..e54255c4c4 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -367,7 +367,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { opacity: 1; } -.mx_EventTile_e2eIcon_userVerified { +.mx_EventTile_e2eIcon_unknown { background-image: url('$(res)/img/e2e/normal.svg'); opacity: 0.5; } @@ -421,7 +421,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { padding-left: 60px; } @@ -433,13 +433,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { border-left: $e2e-unverified-color 5px solid; } -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { - border-left: $e2e-userVerified-color 5px solid; +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { + border-left: $e2e-unknown-color 5px solid; } .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_userVerified.mx_EventTile_info .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { padding-left: 78px; } @@ -451,7 +451,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > a > .mx_MessageTimestamp { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp { left: 3px; width: auto; } @@ -459,7 +459,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon, -.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > .mx_EventTile_e2eIcon { +.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon { display: block; left: 41px; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 17b9a344ef..c868c81549 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -224,7 +224,7 @@ $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color -$e2e-userVerified-color: #e8bf37; +$e2e-unknown-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f2a77935bc..037b080aa3 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -309,10 +309,8 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { - // We cannot find the device. Instead, we have to verify the user. - const userTrust = await this.context.checkUserTrust(mxEvent.getSender()); this.setState({ - verified: userTrust.isVerified() ? "user-verified": "warning", + verified: "unknown", }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } @@ -501,8 +499,8 @@ export default createReactClass({ if (ev.isEncrypted()) { if (this.state.verified === "verified") { return; // no icon for verified - } else if (this.state.verified === "user-verified") { - return (); + } else if (this.state.verified === "unknown") { + return (); } else { return (); } @@ -634,7 +632,7 @@ export default createReactClass({ mx_EventTile_actionBarFocused: this.state.actionBarFocused, mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", - mx_EventTile_userVerified: !isBubbleMessage && this.state.verified === "user-verified", + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === "unknown", mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, @@ -930,9 +928,9 @@ function E2ePadlockUnencrypted(props) { ); } -function E2ePadlockUserVerified(props) { +function E2ePadlockUnknown(props) { return ( - + ); } From 8df07334c34ca39898bddf02deb9af96ac0859b1 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 20 Jan 2020 11:00:28 +0000 Subject: [PATCH 043/282] Translated using Weblate (Italian) Currently translated at 100.0% (2035 of 2035 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index bb35bd6d69..a519c138b1 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2055,5 +2055,24 @@ "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Il Backup Chiavi è attivo sul tuo account ma non è stato impostato da questa sessione. Per impostare un archivio segreto, ripristina il tuo backup chiavi.", "Restore": "Ripristina", "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup": "L'archivio segreto verrà impostato usando i dettagli esistenti del backup chiavi. La password dell'archivio segreto e la chiave di recupero saranno le stesse del backup chiavi", - "Restore your Key Backup": "Ripristina il tuo Backup Chiavi" + "Restore your Key Backup": "Ripristina il tuo Backup Chiavi", + "New Session": "Nuova sessione", + "New invite dialog": "Nuova finestra di invito", + "Other users may not trust it": "Altri utenti potrebbero non fidarsi", + "Later": "Più tardi", + "Failed to invite the following users to chat: %(csvUsers)s": "Impossibile invitare i seguenti utenti alla chat: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Impossibile creare il messaggio diretto. Ricontrolla gli utenti che vuoi invitare e riprova.", + "Something went wrong trying to invite the users.": "Qualcosa è andato storto provando ad invitare gli utenti.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "Impossibile invitare quegli utenti. Ricontrolla gli utenti che vuoi invitare e riprova.", + "Recently Direct Messaged": "Contattati direttamente di recente", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "Se non riesci a trovare qualcuno, chiedi il nome utente (es. @utente:server.it) o condividi questa stanza.", + "Complete security": "Sicurezza completa", + "Verify this session to grant it access to encrypted messages.": "Verifica questa sessione per concederle accesso ai messaggi cifrati.", + "Start": "Inizia", + "Session verified": "Sessione verificata", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "La tua sessione ora è verificata. Ha accesso ai tuoi messaggi cifrati e gli altri utenti la vedranno come fidata.", + "Done": "Fatto", + "Without completing security on this device, it won’t have access to encrypted messages.": "Se non completi la sicurezza su questo dispositivo, esso non avrà accesso ai messaggi cifrati.", + "Go Back": "Torna", + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "L'archivio segreto verrà impostato usando i dettagli del backup chiavi esistente. La password dell'archivio segreto e la chiave di ripristino saranno le stesse del backup chiavi." } From d3ae65698f21950f9c4208441d2f3eff963bae8e Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 15:26:34 +0000 Subject: [PATCH 044/282] Add prepublish script https://github.com/matrix-org/matrix-react-sdk/pull/3723 removed the prepare script which was how the SDK got built before being published. Add it back as a more modern prepublish script. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b607ed04ca..bc99e0eeda 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "typings": "./lib/index.d.ts", "matrix_src_main": "./src/index.js", "scripts": { + "prepublish": "yarn build", "i18n": "matrix-gen-i18n", "prunei18n": "matrix-prune-i18n", "diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && ./scripts/gen-i18n.js && node scripts/compare-file.js src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json", From befd4e1f5a0f893722858b70280d278c5aa05b6d Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 17:25:08 +0000 Subject: [PATCH 045/282] shout more for unknown devices, but keep the tooltip --- res/css/views/rooms/_EventTile.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index e54255c4c4..d292c729dd 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -368,8 +368,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile_e2eIcon_unknown { - background-image: url('$(res)/img/e2e/normal.svg'); - opacity: 0.5; + background-image: url('$(res)/img/e2e/warning.svg'); + opacity: 1; } .mx_EventTile_e2eIcon_unencrypted { From 71f96770c6d494b8fd8a08ab304d25dc17d7185c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 20 Jan 2020 17:27:00 +0000 Subject: [PATCH 046/282] Prepare changelog for v2.0.0-rc.2 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda0e0e911..7630231615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [2.0.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0-rc.2) (2020-01-20) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.0.0-rc.1...v2.0.0-rc.2) + + * Add prepublish script + [\#3877](https://github.com/matrix-org/matrix-react-sdk/pull/3877) + Changes in [2.0.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0-rc.1) (2020-01-20) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.6...v2.0.0-rc.1) From 4521ef1a7bed272288bf2e63fe673eeedeb0ea40 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 20 Jan 2020 17:27:00 +0000 Subject: [PATCH 047/282] v2.0.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc99e0eeda..9203b20bfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 0bcfe5819fd033040429aba0941d450b5477e0ee Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Jan 2020 20:31:36 +0000 Subject: [PATCH 048/282] Integrate handleHomeEnd --- src/accessibility/RovingTabIndex.js | 18 +++++++++++++++--- src/components/views/rooms/RoomList.js | 8 +++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.js index 85aa133aa4..2445a47e35 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.js @@ -23,6 +23,7 @@ import React, { useRef, useReducer, } from "react"; +import PropTypes from "prop-types"; import {Key} from "../Keyboard"; /** @@ -128,7 +129,7 @@ const reducer = (state, action) => { } }; -export const RovingTabIndexProvider = ({children}) => { +export const RovingTabIndexProvider = ({children, handleHomeEnd}) => { const [state, dispatch] = useReducer(reducer, { activeRef: null, refs: [], @@ -136,13 +137,24 @@ export const RovingTabIndexProvider = ({children}) => { const context = useMemo(() => ({state, dispatch}), [state]); + if (handleHomeEnd) { + return + + { children } + + + } + return - {children} + { children } ; }; +RovingTabIndexProvider.propTypes = { + handleHomeEnd: PropTypes.bool, +}; // Helper to handle Home/End to jump to first/last roving-tab-index for widgets such as treeview -export const RovingTabIndexHomeEndHelper = ({children}) => { +export const HomeEndHelper = ({children}) => { const context = useContext(RovingTabIndexContext); const onKeyDown = useCallback((ev) => { diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index a137a36c60..bd563b2f28 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -39,7 +39,7 @@ import * as sdk from "../../../index"; import * as Receipt from "../../../utils/Receipt"; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; -import {RovingTabIndexProvider, RovingTabIndexHomeEndHelper} from "../../../accessibility/RovingTabIndex"; +import {RovingTabIndexProvider} from "../../../accessibility/RovingTabIndex"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -788,10 +788,8 @@ export default createReactClass({ onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave} > - - - { subListComponents } - + + { subListComponents }
); From be6a3821215b699d683cd7a628e583bdaa68d792 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Jan 2020 20:46:12 +0000 Subject: [PATCH 049/282] delint --- src/accessibility/RovingTabIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.js index 2445a47e35..8924815f23 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.js @@ -142,7 +142,7 @@ export const RovingTabIndexProvider = ({children, handleHomeEnd}) => { { children } - + ; } return From 4e018905fc6ad87e6f75f4fd31fe30a8e96d1699 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 15:17:47 -0700 Subject: [PATCH 050/282] Remove Chrome stuff (not needed for riot-web tests anymore) --- .buildkite/pipeline.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index a8ce1273fb..85aff94069 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -83,12 +83,6 @@ steps: # webpack loves to gorge itself on resources. queue: "medium" command: - # Install chrome - - "echo '--- Installing Chrome'" - - "wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -" - - "sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'" - - "apt-get update" - - "apt-get install -y google-chrome-stable" # TODO: Remove hacky chmod for BuildKite - "chmod +x ./scripts/ci/*.sh" - "chmod +x ./scripts/*" @@ -98,8 +92,6 @@ steps: - "yarn build" - "echo '+++ Running Tests'" - "./scripts/ci/riot-unit-tests.sh" - env: - CHROME_BIN: "/usr/bin/google-chrome-stable" plugins: - docker#v3.0.1: image: "node:10" From 62b1dd77a68fdf5cee58fd0f9041a05e9155d0d7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 16:12:59 -0700 Subject: [PATCH 051/282] Fix layering of the riot-web tests pipeline --- .buildkite/pipeline.yaml | 5 +---- scripts/ci/layered-riot-web.sh | 33 +++++++++++++++++++++++++++++++++ scripts/ci/riot-unit-tests.sh | 7 ++----- scripts/fetchdep.sh | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 scripts/ci/layered-riot-web.sh diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 85aff94069..100d14e967 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -86,16 +86,13 @@ steps: # TODO: Remove hacky chmod for BuildKite - "chmod +x ./scripts/ci/*.sh" - "chmod +x ./scripts/*" - - "echo '--- Installing Dependencies'" - - "./scripts/ci/install-deps.sh" - - "echo '--- Running initial build steps'" - - "yarn build" - "echo '+++ Running Tests'" - "./scripts/ci/riot-unit-tests.sh" plugins: - docker#v3.0.1: image: "node:10" propagate-environment: true + workdir: "/workdir/matrix-react-sdk" - label: "🌐 i18n" command: diff --git a/scripts/ci/layered-riot-web.sh b/scripts/ci/layered-riot-web.sh new file mode 100644 index 0000000000..2b908be68f --- /dev/null +++ b/scripts/ci/layered-riot-web.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Creates an environment similar to one that riot-web would expect for +# development. This means going one directory up (and assuming we're in +# a directory like /workdir/matrix-react-sdk) and putting riot-web and +# the js-sdk there. + +cd ../ # Assume we're at something like /workdir/matrix-react-sdk + +# Set up the js-sdk first +matrix-react-sdk/scripts/fetchdep.sh matrix-org matrix-js-sdk +pushd matrix-js-sdk +yarn link +yarn install +#yarn build +popd + +# Now set up the react-sdk +pushd matrix-react-sdk +yarn link matrix-js-sdk +yarn link +yarn install +#yarn build +popd + +# Finally, set up riot-web +matrix-react-sdk/scripts/fetchdep.sh vector-im riot-web +pushd riot-web +yarn link matrix-js-sdk +yarn link matrix-react-sdk +yarn install +yarn build:res +popd diff --git a/scripts/ci/riot-unit-tests.sh b/scripts/ci/riot-unit-tests.sh index 215af13030..7a9ed77793 100755 --- a/scripts/ci/riot-unit-tests.sh +++ b/scripts/ci/riot-unit-tests.sh @@ -6,9 +6,6 @@ set -ev -RIOT_WEB_DIR=riot-web - -scripts/ci/build.sh -pushd "$RIOT_WEB_DIR" +scripts/ci/layered-riot-web.sh +cd ../riot-web yarn test -popd diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index f82752bfc5..f477fd08b8 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -17,7 +17,7 @@ clone() { if [ -n "$branch" ] then echo "Trying to use $org/$repo#$branch" - git clone git://github.com/$org/$repo.git $repo --branch "$branch" && exit 0 + git clone git://github.com/$org/$repo.git $repo --branch "$branch" --depth=1 && exit 0 fi } From 3eeeb9c6afbacf46da266675ff17772747a3a47f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 16:20:02 -0700 Subject: [PATCH 052/282] Remove irrelevant build steps --- scripts/ci/layered-riot-web.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/ci/layered-riot-web.sh b/scripts/ci/layered-riot-web.sh index 2b908be68f..f58794b451 100644 --- a/scripts/ci/layered-riot-web.sh +++ b/scripts/ci/layered-riot-web.sh @@ -12,7 +12,6 @@ matrix-react-sdk/scripts/fetchdep.sh matrix-org matrix-js-sdk pushd matrix-js-sdk yarn link yarn install -#yarn build popd # Now set up the react-sdk @@ -20,7 +19,6 @@ pushd matrix-react-sdk yarn link matrix-js-sdk yarn link yarn install -#yarn build popd # Finally, set up riot-web From 27412ba0b2f342a42652ae6da69d41cfc2a17b03 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 16:23:33 -0700 Subject: [PATCH 053/282] Fix end-to-end test layering too --- .buildkite/pipeline.yaml | 1 + scripts/ci/build.sh | 25 ------------------------- scripts/ci/end-to-end-tests.sh | 10 +++++----- 3 files changed, 6 insertions(+), 30 deletions(-) delete mode 100755 scripts/ci/build.sh diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 100d14e967..de61d4e5b9 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -76,6 +76,7 @@ steps: - docker#v3.0.1: image: "matrixdotorg/riotweb-ci-e2etests-env:latest" propagate-environment: true + workdir: "/workdir/matrix-react-sdk" - label: "🔧 Riot Tests" agents: diff --git a/scripts/ci/build.sh b/scripts/ci/build.sh deleted file mode 100755 index 0b1fa23093..0000000000 --- a/scripts/ci/build.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# script which is run by the CI build (after `yarn test`). -# -# clones riot-web develop and runs the tests against our version of react-sdk. - -set -ev - -RIOT_WEB_DIR=riot-web -REACT_SDK_DIR=`pwd` - -yarn link - -scripts/fetchdep.sh vector-im riot-web - -pushd "$RIOT_WEB_DIR" - -yarn link matrix-js-sdk -yarn link matrix-react-sdk - -yarn install - -yarn build - -popd diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index a592888292..9eb3c2bd87 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -21,15 +21,15 @@ handle_error() { trap 'handle_error' ERR -RIOT_WEB_DIR=riot-web -REACT_SDK_DIR=`pwd` - echo "--- Building Riot" -scripts/ci/build.sh +scripts/ci/layered-riot-web.sh +cd ../riot-web +yarn build +cd ../matrix-react-sdk # run end to end tests pushd test/end-to-end-tests -ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web +ln -s ../riot-web riot/riot-web # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh # CHROME_PATH=$(which google-chrome-stable) ./run.sh echo "--- Install synapse & other dependencies" From 19615d372175e6f7e8f7756e33f236ac41cb626e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 16:33:13 -0700 Subject: [PATCH 054/282] Disable minification of Riot in end-to-end tests --- scripts/ci/end-to-end-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index 9eb3c2bd87..cc1e548af9 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -25,7 +25,7 @@ trap 'handle_error' ERR echo "--- Building Riot" scripts/ci/layered-riot-web.sh cd ../riot-web -yarn build +CI_PACKAGE=true yarn build cd ../matrix-react-sdk # run end to end tests pushd test/end-to-end-tests From 776b3af6bb46c7a55a121abfffe32dc26efcf772 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 16:33:22 -0700 Subject: [PATCH 055/282] Fix relative pathing on riot-web link --- scripts/ci/end-to-end-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index cc1e548af9..bae268bbe3 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -29,7 +29,7 @@ CI_PACKAGE=true yarn build cd ../matrix-react-sdk # run end to end tests pushd test/end-to-end-tests -ln -s ../riot-web riot/riot-web +ln -s ../../../riot-web riot/riot-web # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh # CHROME_PATH=$(which google-chrome-stable) ./run.sh echo "--- Install synapse & other dependencies" From 3b2f96bc047bbe221efe216ced07dc927d016d09 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 18:02:54 -0700 Subject: [PATCH 056/282] Try explicitly mapping the directory --- scripts/ci/end-to-end-tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index bae268bbe3..9bdb512940 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -25,11 +25,12 @@ trap 'handle_error' ERR echo "--- Building Riot" scripts/ci/layered-riot-web.sh cd ../riot-web +riot_web_dir=`pwd` CI_PACKAGE=true yarn build cd ../matrix-react-sdk # run end to end tests pushd test/end-to-end-tests -ln -s ../../../riot-web riot/riot-web +ln -s $riot_web_dir riot/riot-web # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh # CHROME_PATH=$(which google-chrome-stable) ./run.sh echo "--- Install synapse & other dependencies" From 4913d579e37c5bcb0c39d6291fb1e43110c4ffc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 09:24:20 +0100 Subject: [PATCH 057/282] EventIndexPanel: Reword the crawler state if no room is being crawled. --- src/components/views/settings/EventIndexPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index b777957a3a..b3c75cd336 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -125,7 +125,7 @@ export default class EventIndexPanel extends React.Component { if (!this.state.eventIndexingEnabled) { crawlerState =
{_t("Message search for encrypted rooms is disabled.")}
; } else if (this.state.currentRoom === null) { - crawlerState =
{_t("Not currently downloading messages for any room.")}
; + crawlerState =
{_t("Not downloading messages for any room.")}
; } else { crawlerState = (
{_t( From 908a00a13dc936d9bab61c165d9fe941999f8924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 10:06:04 +0100 Subject: [PATCH 058/282] EventIndexPanel: Move the panel from the preferences to the security tab. --- src/components/views/settings/EventIndexPanel.js | 14 ++++++++++++-- .../tabs/user/PreferencesUserSettingsTab.js | 4 ---- .../settings/tabs/user/SecurityUserSettingsTab.js | 9 +++++++++ src/i18n/strings/en_EN.json | 5 +++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index b3c75cd336..5b8029a09a 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -138,8 +138,7 @@ export default class EventIndexPanel extends React.Component { if (EventIndexPeg.get() !== null) { eventIndexingSettings = ( -
- {_t("Encrypted search")} +
{ _t( "Riot is securely caching encrypted messages locally for them" + "to appear in search results:" @@ -165,6 +164,17 @@ export default class EventIndexPanel extends React.Component { onChange={this._onCrawlerSleepTimeChange} />
); + } else { + eventIndexingSettings = ( +
+ { + _t( "Riot can't securely cache encrypted messages locally" + + "while running in a web browser. Use Riot Desktop for" + + "encrypted messages to appear in search results." + ) + } +
+ ); } return eventIndexingSettings; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 5ecafcc5ae..8cbaba037d 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -144,8 +144,6 @@ export default class PreferencesUserSettingsTab extends React.Component { } render() { - const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel'); - let autoLaunchOption = null; if (this.state.autoLaunchSupported) { autoLaunchOption =
{_t("Preferences")}
- -
{_t("Composer")} {this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 5eadfc234a..8ef9983efd 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -242,6 +242,7 @@ export default class SecurityUserSettingsTab extends React.Component { render() { const DevicesPanel = sdk.getComponent('views.settings.DevicesPanel'); const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag'); + const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel'); const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel'); const keyBackup = ( @@ -253,6 +254,13 @@ export default class SecurityUserSettingsTab extends React.Component {
); + const eventIndex = ( +
+ {_t("Encrypted search")} + +
+ ); + // XXX: There's no such panel in the current cross-signing designs, but // it's useful to have for testing the feature. If there's no interest // in having advanced details here once all flows are implemented, we @@ -281,6 +289,7 @@ export default class SecurityUserSettingsTab extends React.Component {
{keyBackup} + {eventIndex} {crossSigning} {this._renderCurrentDeviceInfo()}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a968c145d9..f78e3594ee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -555,15 +555,15 @@ "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", - "Not currently downloading messages for any room.": "Not currently downloading messages for any room.", + "Not downloading messages for any room.": "Not downloading messages for any room.", "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", - "Encrypted search": "Encrypted search", "Riot is securely caching encrypted messages locally for themto appear in search results:": "Riot is securely caching encrypted messages locally for themto appear in search results:", "Space used:": "Space used:", "Indexed messages:": "Indexed messages:", "Number of rooms:": "Number of rooms:", "Download and index encrypted messages": "Download and index encrypted messages", "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", + "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.", "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.", @@ -759,6 +759,7 @@ "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Key backup": "Key backup", + "Encrypted search": "Encrypted search", "Cross-signing": "Cross-signing", "Security & Privacy": "Security & Privacy", "Devices": "Devices", From d34f1e52ad1e7d499018f96f6942501c7598b005 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 10:08:53 +0000 Subject: [PATCH 059/282] constants for e2estates --- src/components/views/rooms/EventTile.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 037b080aa3..9c73daaa50 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -66,6 +66,12 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; +const E2ESTATE = { + VERIFIED: "verified", + WARNING: "warning", + UNKNOWN: "unknown", +}; + // Add all the Mjolnir stuff to the renderer for (const evType of ALL_RULE_TYPES) { stateEventTileTypes[evType] = 'messages.TextualEvent'; @@ -299,7 +305,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: "verified", + verified: E2ESTATE.VERIFIED, }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); @@ -310,13 +316,13 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { this.setState({ - verified: "unknown", + verified: E2ESTATE.UNKNOWN, }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } this.setState({ - verified: eventSenderTrust.isVerified() ? "verified" : "warning", + verified: eventSenderTrust.isVerified() ? E2ESTATE.VERIFIED : E2ESTATE.WARNING, }, this.props.onHeightChanged); // Decryption may have caused a change in size }, @@ -497,9 +503,9 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified === "verified") { + if (this.state.verified === E2ESTATE.VERIFIED) { return; // no icon for verified - } else if (this.state.verified === "unknown") { + } else if (this.state.verified === E2ESTATE.UNKNOWN) { return (); } else { return (); @@ -630,9 +636,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", - mx_EventTile_unknown: !isBubbleMessage && this.state.verified === "unknown", + mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2ESTATE.VERIFIED, + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2ESTATE.WARNING, + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2ESTATE.UNKNOWN, mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, From 996d0f1d2074ef76e5c2d111569dd888cb01ab00 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 21 Jan 2020 08:05:43 +0000 Subject: [PATCH 060/282] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index dbd8319b8a..b459fb9306 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2075,5 +2075,9 @@ "Something went wrong trying to invite the users.": "在嘗試邀請使用者時發生錯誤。", "We couldn't invite those users. Please check the users you want to invite and try again.": "我們無法邀請那些使用者。請檢查您想要邀請的使用者並再試一次。", "Recently Direct Messaged": "最近傳送過直接訊息", - "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "如果您找不到某人,請詢問他們的使用者名稱(範例:@user:server.com)或分享此聊天室。" + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "如果您找不到某人,請詢問他們的使用者名稱(範例:@user:server.com)或分享此聊天室。", + "Verify User": "驗證使用者", + "For extra security, verify this user by checking a one-time code on both of your devices.": "為了提高安全性,請透過檢查您兩個裝置上的一次性代碼來驗證此使用者。", + "For maximum security, do this in person.": "為了取得最強的安全性,請親自進行。", + "Start Verification": "開始驗證" } From 961f52924e0c84020b55a3ca45e065541933ec0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Tue, 21 Jan 2020 10:26:12 +0000 Subject: [PATCH 061/282] Translated using Weblate (French) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index d1c4f7379c..abe05e9812 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2075,5 +2075,9 @@ "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Le coffre secret sera configuré en utilisant les détails existants de votre sauvegarde de clés. Votre phrase de passe et votre clé de récupération seront les mêmes que celles de votre sauvegarde de clés.", "New Session": "Nouvelle session", "Other users may not trust it": "D’autres utilisateurs pourraient ne pas lui faire confiance", - "Later": "Plus tard" + "Later": "Plus tard", + "Verify User": "Vérifier l’utilisateur", + "For extra security, verify this user by checking a one-time code on both of your devices.": "Pour une meilleure sécurité, vérifiez cet utilisateur en comparant un code à usage unique sur vos deux appareils.", + "For maximum security, do this in person.": "Pour une sécurité maximale, faites-le en personne.", + "Start Verification": "Commencer la vérification" } From 21dea189855ec027cd4bf8a3f1768aca62f83c52 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 20 Jan 2020 20:29:42 +0000 Subject: [PATCH 062/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index e5b9f637f1..c5334c1592 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -1156,7 +1156,7 @@ "Show avatars in user and room mentions": "Profilkép mutatása a felhasználó és szoba említéseknél", "Enable big emoji in chat": "Nagy Emojik engedélyezése a csevegésekben", "Send typing notifications": "Gépelés visszajelzés küldése", - "Enable Community Filter Panel": "Közösségi szűrő panel engedélyezése", + "Enable Community Filter Panel": "Közösségi szűrő panel bekapcsolása", "Messages containing my username": "Üzenetek amik a nevemet tartalmazzák", "The other party cancelled the verification.": "A másik fél törölte az ellenőrzést.", "Verified!": "Ellenőrizve!", @@ -2076,5 +2076,9 @@ "Something went wrong trying to invite the users.": "Valami nem sikerült a felhasználók meghívásával.", "We couldn't invite those users. Please check the users you want to invite and try again.": "Ezeket a felhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra.", "Recently Direct Messaged": "Nemrég küldött Közvetlen Üzenetek", - "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "Ha nem találsz valakit, akkor kérdezd meg a felhasználói nevét (pl.: @felhasználó:szerver.com) vagy oszd meg ezt a szobát." + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "Ha nem találsz valakit, akkor kérdezd meg a felhasználói nevét (pl.: @felhasználó:szerver.com) vagy oszd meg ezt a szobát.", + "Verify User": "Felhasználó ellenőrzése", + "For extra security, verify this user by checking a one-time code on both of your devices.": "A biztonság fokozásáért ellenőrizd ezt a felhasználót egy egyszeri kód egyeztetésével mindkettőtök készülékén.", + "For maximum security, do this in person.": "A legnagyobb biztonság érdekében ezt személyesen tedd meg.", + "Start Verification": "Ellenőrzés elindítása" } From 46f2c1f29b3a5ec9551b302a29e9aff317f120a5 Mon Sep 17 00:00:00 2001 From: Makoto Kato Date: Tue, 21 Jan 2020 06:51:42 +0000 Subject: [PATCH 063/282] Translated using Weblate (Japanese) Currently translated at 60.1% (1226 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ja/ --- src/i18n/strings/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 55cc8782ef..787dcbc4ca 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -601,7 +601,7 @@ "Copied!": "コピーされました!", "Failed to copy": "コピーに失敗しました", "Add an 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?": "サードパーティのサイトに移動して、%(integationsUrl)s で使用するためにアカウントを認証できるようになります。続行しますか?", + "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?": "サードパーティのサイトに移動して、%(integrationsUrl)s で使用するためにアカウントを認証できるようになります。続行しますか?", "Removed or unknown message type": "削除されたまたは未知のメッセージタイプ", "Message removed by %(userId)s": "%(userId)s によってメッセージが削除されました", "Message removed": "メッセージが削除された", @@ -630,7 +630,7 @@ "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "%(roomName)s を %(groupId)s から削除してもよろしいですか?", "Removing a room from the community will also remove it from the community page.": "コミュニティから部屋を削除すると、コミュニティページからもその部屋が削除されます。", "Failed to remove room from community": "コミュニティからの部屋の削除に失敗しました", - "Failed to remove '%(roomName)s' from %(groupId)s": "%(groupName)s から '%(roomName)s' を削除できませんでした", + "Failed to remove '%(roomName)s' from %(groupId)s": "%(groupId)s から '%(roomName)s' を削除できませんでした", "Something went wrong!": "何かが間違っていた!", "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "%(groupId)s の '%(roomName)s' の表示を更新できませんでした。", "Visibility in Room List": "ルームリストの可視性", @@ -668,7 +668,7 @@ "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)s が参加しました", "%(oneUser)sjoined %(count)s times|other": "%(oneUser)s が %(count)s 回参加しました", "%(oneUser)sjoined %(count)s times|one": "%(oneUser)s が参加しました", - "%(severalUsers)sleft %(count)s times|other": "%(severalUers)s は %(count)s 回退出しました", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s は %(count)s 回退出しました", "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)s は退出しました", "%(oneUser)sleft %(count)s times|other": "%(oneUser)s は %(count)s 回退出しました", "%(oneUser)sleft %(count)s times|one": "%(oneUser)s は退出しました", From b5d5f4e13a8291d27479eb01d39e12ab2948ab6f Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 20 Jan 2020 18:09:12 +0000 Subject: [PATCH 064/282] Translated using Weblate (Turkish) Currently translated at 70.0% (1428 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 81f0522922..1e92d28586 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1422,5 +1422,23 @@ "Enable Community Filter Panel": "Toluluk Filtre Panelini Aç", "Match system theme": "Sistem temasıyla eşle", "Allow Peer-to-Peer for 1:1 calls": "1:1 çağrılar için eşten-eşe izin ver", - "Missing media permissions, click the button below to request.": "Medya izinleri eksik, alttaki butona tıkayarak talep edin." + "Missing media permissions, click the button below to request.": "Medya izinleri eksik, alttaki butona tıkayarak talep edin.", + "Credits": "Katkıda Bulunanlar", + "Clear cache and reload": "Belleği temizle ve yeniden yükle", + "Customise your experience with experimental labs features. Learn more.": "Deneysel laboratuar özellikler ile deneyiminizi özelleştirebilirsiniz. Daha fazla.", + "Ignored/Blocked": "Yoksayılan/Bloklanan", + "Error adding ignored user/server": "Yoksayılan kullanıcı/sunucu eklenirken hata", + "Error subscribing to list": "Listeye abone olunurken hata", + "Error removing ignored user/server": "Yoksayılan kullanıcı/sunucu silinirken hata", + "Error unsubscribing from list": "Listeden abonelikten çıkılırken hata", + "You are not subscribed to any lists": "Herhangi bir listeye aboneliğiniz bulunmuyor", + "⚠ These settings are meant for advanced users.": "⚠ Bu ayarlar ileri düzey kullanıcılar içindir.", + "Unignore": "Yoksayma", + "This bridge was provisioned by ": "Bu körpü tarafından provize edildi", + "Connected to on ": " ağındaki kanala bağlandı", + "Below is a list of bridges connected to this room.": "Bu odaya bağlanmış köprülerin bir listesi alttadır.", + "Change room avatar": "Oda resmini değiştir", + "Members only (since the point in time of selecting this option)": "Sadece üyeler ( bu seçeneği seçtiğinizden itibaren)", + "Unable to revoke sharing for email address": "E-posta adresi paylaşımı kaldırılamadı", + "Unable to revoke sharing for phone number": "Telefon numarası paylaşımı kaldırılamıyor" } From 6b85284632fad2d83f38d9f71959e8afcac1a76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 13:20:30 +0100 Subject: [PATCH 065/282] EventIndexPanel: Move the bulk of the event index info into a modal. --- .../dialogs/eventindex/ManageEventIndex.js | 226 ++++++++++++++++++ .../views/settings/EventIndexPanel.js | 93 ++----- src/i18n/strings/en_EN.json | 25 +- 3 files changed, 259 insertions(+), 85 deletions(-) create mode 100644 src/async-components/views/dialogs/eventindex/ManageEventIndex.js diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js new file mode 100644 index 0000000000..23aa61c33a --- /dev/null +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -0,0 +1,226 @@ +/* +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 * as sdk from '../../../../index'; +import {MatrixClientPeg} from '../../../../MatrixClientPeg'; +import PropTypes from 'prop-types'; +import { _t } from '../../../../languageHandler'; + +import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../../components/views/elements/LabelledToggleSwitch"; +import Field from "../../../../components/views/elements/Field"; +import {formatBytes} from "../../../../utils/FormattingUtils"; +import EventIndexPeg from "../../../../indexing/EventIndexPeg"; +import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; + + +/* + * Walks the user through the process of creating an e2e key backup + * on the server. + */ +export default class ManageEventIndex extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + eventIndexSize: 0, + crawlingRooms: 0, + totalCrawlingRooms: 0, + eventCount: 0, + roomCount: 0, + currentRoom: null, + eventIndexingEnabled: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), + crawlerSleepTime: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), + }; + + } + + async updateCurrentRoom(room) { + const eventIndex = EventIndexPeg.get(); + const stats = await eventIndex.getStats(); + let currentRoom = null; + + if (room) currentRoom = room.name; + + this.setState({ + eventIndexSize: stats.size, + roomCount: stats.roomCount, + eventCount: stats.eventCount, + currentRoom: currentRoom, + }); + } + + componentWillUnmount(): void { + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom.bind(this)); + } + } + + async componentWillMount(): void { + let eventIndexSize = 0; + let roomCount = 0; + let eventCount = 0; + let crawlingRooms = 0; + let totalCrawlingRooms = 0; + let currentRoom = null; + + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this)); + + const stats = await eventIndex.getStats(); + eventIndexSize = stats.size; + roomCount = stats.roomCount; + eventCount = stats.eventCount; + + const crawledRooms = eventIndex.currentlyCrawledRooms(); + crawlingRooms = crawledRooms.crawlingRooms.size; + totalCrawlingRooms = crawledRooms.totalRooms.size; + + const room = eventIndex.currentRoom(); + if (room) currentRoom = room.name; + } + + this.setState({ + eventIndexSize, + crawlingRooms, + totalCrawlingRooms, + eventCount, + roomCount, + currentRoom, + }); + } + + _onEventIndexingEnabledChange = (checked) => { + SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); + + if (checked) EventIndexPeg.start(); + else EventIndexPeg.stop(); + + this.setState({eventIndexingEnabled: checked}); + } + + _onCrawlerSleepTimeChange = (e) => { + this.setState({crawlerSleepTime: e.target.value}); + SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); + } + + _onDisable = () => { + this.props.onFinished(false); + } + + _onDone = () => { + this.props.onFinished(true); + } + + render() { + let eventIndexingSettings = null; + let crawlerState; + + if (!this.state.eventIndexingEnabled) { + crawlerState =
{_t("Message search for encrypted rooms is disabled.")}
; + } else if (this.state.currentRoom === null) { + crawlerState =
{_t("Not downloading messages for any room.")}
; + } else { + crawlerState = ( +
{_t( + "Downloading mesages for %(currentRoom)s.", + { currentRoom: this.state.currentRoom } + )} +
+ ); + } + + if (EventIndexPeg.get() !== null) { + eventIndexingSettings = ( +
+ { + _t( "Riot is securely caching encrypted messages locally for them " + + "to appear in search results:" + ) + } +
+ {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {_t("Indexed messages:")} {this.state.eventCount}
+ {_t("Number of rooms:")} {this.state.roomCount}
+ {crawlerState}
+
+ + + + +
+ ); + } else { + eventIndexingSettings = ( +
+ { + _t( "Riot can't securely cache encrypted messages locally" + + "while running in a web browser. Use Riot Desktop for" + + "encrypted messages to appear in search results." + ) + } +
+ ); + } + + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + let buttons; + + buttons =
+
+ + {_t("Disable")} + + + {_t("Done")} + +
+
; + + return ( + {}} + title={_t("Message search")} + > +
+ {eventIndexingSettings} +
+
+ {buttons} +
+
+ ); + } +} diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 5b8029a09a..fd3facbc6b 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -21,6 +21,7 @@ import classNames from 'classnames'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; +import AccessibleButton from "../elements/AccessibleButton"; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import Field from "../elements/Field"; @@ -33,30 +34,17 @@ export default class EventIndexPanel extends React.Component { this.state = { eventIndexSize: 0, - crawlingRooms: 0, - totalCrawlingRooms: 0, - eventCount: 0, roomCount: 0, - currentRoom: null, - eventIndexingEnabled: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), - crawlerSleepTime: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), }; } async updateCurrentRoom(room) { const eventIndex = EventIndexPeg.get(); const stats = await eventIndex.getStats(); - let currentRoom = null; - - if (room) currentRoom = room.name; this.setState({ eventIndexSize: stats.size, roomCount: stats.roomCount, - eventCount: stats.eventCount, - currentRoom: currentRoom, }); } @@ -71,10 +59,6 @@ export default class EventIndexPanel extends React.Component { async componentWillMount(): void { let eventIndexSize = 0; let roomCount = 0; - let eventCount = 0; - let crawlingRooms = 0; - let totalCrawlingRooms = 0; - let currentRoom = null; const eventIndex = EventIndexPeg.get(); @@ -84,84 +68,41 @@ export default class EventIndexPanel extends React.Component { const stats = await eventIndex.getStats(); eventIndexSize = stats.size; roomCount = stats.roomCount; - eventCount = stats.eventCount; - - const crawledRooms = eventIndex.currentlyCrawledRooms(); - crawlingRooms = crawledRooms.crawlingRooms.size; - totalCrawlingRooms = crawledRooms.totalRooms.size; - - const room = eventIndex.currentRoom(); - if (room) currentRoom = room.name; } this.setState({ eventIndexSize, - crawlingRooms, - totalCrawlingRooms, - eventCount, roomCount, - currentRoom, }); } - _onEventIndexingEnabledChange = (checked) => { - SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); + _onManage = async () => { + Modal.createTrackedDialogAsync('Message search', 'Message search', + import('../../../async-components/views/dialogs/eventindex/ManageEventIndex'), + { + onFinished: () => {}, + }, null, /* priority = */ false, /* static = */ true, + ); - if (checked) EventIndexPeg.start(); - else EventIndexPeg.stop(); - - this.setState({eventIndexingEnabled: checked}); - } - - _onCrawlerSleepTimeChange = (e) => { - this.setState({crawlerSleepTime: e.target.value}); - SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); } render() { let eventIndexingSettings = null; - let crawlerState; - - if (!this.state.eventIndexingEnabled) { - crawlerState =
{_t("Message search for encrypted rooms is disabled.")}
; - } else if (this.state.currentRoom === null) { - crawlerState =
{_t("Not downloading messages for any room.")}
; - } else { - crawlerState = ( -
{_t( - "Downloading mesages for %(currentRoom)s.", - { currentRoom: this.state.currentRoom } - )} -
- ); - } if (EventIndexPeg.get() !== null) { eventIndexingSettings = (
- { - _t( "Riot is securely caching encrypted messages locally for them" + - "to appear in search results:" - ) - }
- {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
- {_t("Indexed messages:")} {this.state.eventCount}
- {_t("Number of rooms:")} {this.state.roomCount}
- {crawlerState}
+ {_t( "Securely cache encrypted messages locally for them " + + "to appear in search results, using ") + } {formatBytes(this.state.eventIndexSize, 0)} + {_t( " to store messages from ")} {this.state.roomCount} {_t("rooms.")} +
+
+ + {_t("Manage")} +
- - - -
); } else { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f78e3594ee..6b1eda0b0c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -554,15 +554,10 @@ "Failed to set display name": "Failed to set display name", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", - "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", - "Not downloading messages for any room.": "Not downloading messages for any room.", - "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", - "Riot is securely caching encrypted messages locally for themto appear in search results:": "Riot is securely caching encrypted messages locally for themto appear in search results:", - "Space used:": "Space used:", - "Indexed messages:": "Indexed messages:", - "Number of rooms:": "Number of rooms:", - "Download and index encrypted messages": "Download and index encrypted messages", - "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", + "Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ", + " to store messages from ": " to store messages from ", + "rooms.": "rooms.", + "Manage": "Manage", "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.", "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", @@ -2040,6 +2035,18 @@ "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", "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.": "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.", + "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", + "Not downloading messages for any room.": "Not downloading messages for any room.", + "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", + "Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot is securely caching encrypted messages locally for them to appear in search results:", + "Space used:": "Space used:", + "Indexed messages:": "Indexed messages:", + "Number of rooms:": "Number of rooms:", + "Download and index encrypted messages": "Download and index encrypted messages", + "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", + "Disable or enable": "Disable or enable", + "Disable": "Disable", + "Message search": "Message search", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" From 8e26268079ca83954a78336859b679bb0937b533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 13:38:20 +0100 Subject: [PATCH 066/282] SecurityUserSettingsTab: Rename encrypted search section. --- .../views/settings/tabs/user/SecurityUserSettingsTab.js | 2 +- src/i18n/strings/en_EN.json | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 8ef9983efd..7b22dd15e2 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -256,7 +256,7 @@ export default class SecurityUserSettingsTab extends React.Component { const eventIndex = (
- {_t("Encrypted search")} + {_t("Message search")}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6b1eda0b0c..b2ac55ba04 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -754,7 +754,7 @@ "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Key backup": "Key backup", - "Encrypted search": "Encrypted search", + "Message search": "Message search", "Cross-signing": "Cross-signing", "Security & Privacy": "Security & Privacy", "Devices": "Devices", @@ -2044,9 +2044,7 @@ "Number of rooms:": "Number of rooms:", "Download and index encrypted messages": "Download and index encrypted messages", "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", - "Disable or enable": "Disable or enable", "Disable": "Disable", - "Message search": "Message search", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" From c8a2f6a5a0133a55f324adfcb11fc25af53e6cab Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 13:33:16 +0000 Subject: [PATCH 067/282] Move room header shields over the avatar for the room Currently this is calibrated like the lil' DM icon is --- res/css/views/rooms/_RoomHeader.scss | 6 +++++- src/components/views/rooms/RoomHeader.js | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 45b9733faa..0d92247735 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -19,7 +19,10 @@ limitations under the License. border-bottom: 1px solid $primary-hairline-color; .mx_E2EIcon { - margin: 0 5px; + margin: 0; + position: absolute; + bottom: 0; + right: -5px; } } @@ -171,6 +174,7 @@ limitations under the License. width: 28px; height: 28px; margin: 0 7px; + position: relative; } .mx_RoomHeader_avatar .mx_BaseAvatar_image { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 09f3fd489f..15f0daa200 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -310,8 +310,7 @@ export default createReactClass({ return (
-
{ roomAvatar }
- { e2eIcon } +
{ roomAvatar }{ e2eIcon }
{ privateIcon } { name } { topicElement } From a0599dedf0b39e66c3ee49f4e772e3380667cd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 15:45:29 +0100 Subject: [PATCH 068/282] EventIndex: Use the newly exposed TimelineWindow methods. --- src/indexing/EventIndex.js | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb77d92c27..c081440233 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -494,35 +494,12 @@ export default class EventIndex { } paginateTimelineWindow(room, timelineWindow, direction, limit) { - let tl; - - // TODO this is from the js-sdk, this should probably be exposed to - // us through the js-sdk. - const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction === EventTimeline.BACKWARDS) ? - timeline.retreat(limit) : timeline.advance(limit); - - if (count) { - timelineWindow._eventCount += count; - const excess = timelineWindow._eventCount - timelineWindow._windowLimit; - - if (excess > 0) { - timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); - } - return true; - } - - return false; - }; - - // TODO these private fields should be somehow exposed in the js-sdk. - if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; - else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + const tl = timelineWindow.getTimelineIndex(direction); if (!tl) return Promise.resolve(false); if (tl.pendingPaginate) return tl.pendingPaginate; - if (moveWindowCap(timelineWindow, tl, direction, limit)) { + if (timelineWindow.extend(direction, limit)) { return Promise.resolve(true); } @@ -532,8 +509,8 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; + timelineWindow.extend(direction, limit); return ret; }; From 9c0cf326c1992d4d641c46509cd4534504e656d3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 21 Jan 2020 15:12:59 +0000 Subject: [PATCH 069/282] Only show devices and verify actions in E2EE rooms This changes logic to only show the devices list and verify button in E2EE rooms, matching the design. Fixes https://github.com/vector-im/riot-web/issues/11839 --- src/components/views/right_panel/UserInfo.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a0819be472..b08f07ace4 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1308,15 +1308,18 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { userTrust.isVerified(); const isMe = user.userId === cli.getUserId(); let verifyButton; - if (!userVerified && !isMe) { + if (isRoomEncrypted && !userVerified && !isMe) { verifyButton = verifyUser(user)}> {_t("Verify")} ; } - const devicesSection = ; + let devicesSection; + if (isRoomEncrypted) { + devicesSection = ; + } const securitySection = (
From 790d2c147203ca390804dbfa8ed14120416ab969 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 21 Jan 2020 15:30:01 +0000 Subject: [PATCH 070/282] Fix toast icon to prevent clipping This fixes the bottom and right edges of the toast icon, which were getting clipped away. Fixes https://github.com/vector-im/riot-web/issues/11915 --- res/css/structures/_ToastContainer.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 5634a97c53..5b5c49f357 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -51,8 +51,8 @@ limitations under the License. &.mx_Toast_hasIcon { &::after { content: ""; - width: 21px; - height: 20px; + width: 22px; + height: 22px; grid-column: 1; grid-row: 1; mask-size: 100%; From b2aba6db352027c6f7d5db0256e3483eb5a6aded Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 15:32:32 +0000 Subject: [PATCH 071/282] Initial attempt to make toggleInlineFormat paragraph-aware --- src/editor/deserialize.js | 2 +- src/editor/operations.js | 70 +++++++++++---- test/editor/mock.js | 10 +++ test/editor/operations-test.js | 154 +++++++++++++++++++++++++++++++++ test/editor/range-test.js | 12 +-- 5 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 test/editor/operations-test.js diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 1fdbf9490c..7ba4c3eda3 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -250,7 +250,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { } export function parsePlainTextMessage(body, partCreator, isQuotedMessage) { - const lines = body.split("\n"); + const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n const parts = lines.reduce((parts, line, i) => { if (isQuotedMessage) { parts.push(partCreator.plain(QUOTE_LINE_PREFIX)); diff --git a/src/editor/operations.js b/src/editor/operations.js index e2661faf59..6bae60e6b8 100644 --- a/src/editor/operations.js +++ b/src/editor/operations.js @@ -104,23 +104,63 @@ export function toggleInlineFormat(range, prefix, suffix = prefix) { const {model, parts} = range; const {partCreator} = model; - const isFormatted = parts.length && - parts[0].text.startsWith(prefix) && - parts[parts.length - 1].text.endsWith(suffix); + // compute paragraph [start, end] indexes + const paragraphIndexes = []; + let startIndex = 0; + // let seenNewlines = 0; + for (let i = 2; i < parts.length; i++) { + // paragraph breaks can be denoted in a multitude of ways, + // - 2 newline parts in sequence + // - newline part, plain(), newline part - if (isFormatted) { - // remove prefix and suffix - const partWithoutPrefix = parts[0].serialize(); - partWithoutPrefix.text = partWithoutPrefix.text.substr(prefix.length); - parts[0] = partCreator.deserializePart(partWithoutPrefix); + const isBlank = part => !part.text || !/\S/.test(part.text); + const isNL = part => part.type === "newline"; - const partWithoutSuffix = parts[parts.length - 1].serialize(); - const suffixPartText = partWithoutSuffix.text; - partWithoutSuffix.text = suffixPartText.substring(0, suffixPartText.length - suffix.length); - parts[parts.length - 1] = partCreator.deserializePart(partWithoutSuffix); - } else { - parts.unshift(partCreator.plain(prefix)); - parts.push(partCreator.plain(suffix)); + // bump startIndex onto the first non-blank after the paragraph ending + if (isBlank(parts[i - 2]) && isNL(parts[i - 1]) && !isNL(parts[i]) && !isBlank(parts[i])) { + startIndex = i; + } + + if (isNL(parts[i - 1]) && isNL(parts[i])) { + paragraphIndexes.push([startIndex, i - 1]); + startIndex = i + 1; + } else if (isNL(parts[i - 2]) && isBlank(parts[i - 1]) && isNL(parts[i])) { + paragraphIndexes.push([startIndex, i - 2]); + startIndex = i + 1; + } } + if (startIndex < parts.length) { + // TODO don't use parts.length here to clean up any trailing cruft + paragraphIndexes.push([startIndex, parts.length]); + } + + // keep track of how many things we have inserted as an offset:=0 + let offset = 0; + paragraphIndexes.forEach(([startIndex, endIndex]) => { + // for each paragraph apply the same rule + const base = startIndex + offset; + const index = endIndex + offset; + + const isFormatted = (index - base > 0) && + parts[base].text.startsWith(prefix) && + parts[index - 1].text.endsWith(suffix); + + if (isFormatted) { + // remove prefix and suffix + const partWithoutPrefix = parts[base].serialize(); + partWithoutPrefix.text = partWithoutPrefix.text.substr(prefix.length); + parts[base] = partCreator.deserializePart(partWithoutPrefix); + + const partWithoutSuffix = parts[index - 1].serialize(); + const suffixPartText = partWithoutSuffix.text; + partWithoutSuffix.text = suffixPartText.substring(0, suffixPartText.length - suffix.length); + parts[index - 1] = partCreator.deserializePart(partWithoutSuffix); + } else { + parts.splice(index, 0, partCreator.plain(suffix)); // splice in the later one first to not change offset + parts.splice(base, 0, partCreator.plain(prefix)); + offset += 2; // offset index to account for the two items we just spliced in + } + }); + replaceRangeAndExpandSelection(range, parts); } diff --git a/test/editor/mock.js b/test/editor/mock.js index bb1a51d14b..6de65cf23d 100644 --- a/test/editor/mock.js +++ b/test/editor/mock.js @@ -67,3 +67,13 @@ export function createPartCreator(completions = []) { }; return new PartCreator(new MockRoom(), new MockClient(), autoCompleteCreator); } + +export function createRenderer() { + const render = (c) => { + render.caret = c; + render.count += 1; + }; + render.count = 0; + render.caret = null; + return render; +} diff --git a/test/editor/operations-test.js b/test/editor/operations-test.js new file mode 100644 index 0000000000..591166759c --- /dev/null +++ b/test/editor/operations-test.js @@ -0,0 +1,154 @@ +/* +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 {getLineAndNodePosition} from "../../src/editor/caret"; +import EditorModel from "../../src/editor/model"; +import {createPartCreator, createRenderer} from "./mock"; +import {toggleInlineFormat} from "../../src/editor/operations"; + +describe('editor/operations: formatting operations', () => { + describe('toggleInlineFormat', () => { + it('works for words', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.plain("hello world!"), + ], pc, renderer); + + const range = model.startRange(model.positionForOffset(6, false), + model.positionForOffset(11, false)); // around "world" + + expect(range.parts[0].text).toBe("world"); + expect(model.serializeParts()).toEqual([{"text": "hello world!", "type": "plain"}]); + toggleInlineFormat(range, "_"); + expect(model.serializeParts()).toEqual([{"text": "hello _world_!", "type": "plain"}]); + }); + + it('works for parts of words', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.plain("hello world!"), + ], pc, renderer); + + const range = model.startRange(model.positionForOffset(7, false), + model.positionForOffset(10, false)); // around "orl" + + expect(range.parts[0].text).toBe("orl"); + expect(model.serializeParts()).toEqual([{"text": "hello world!", "type": "plain"}]); + toggleInlineFormat(range, "*"); + expect(model.serializeParts()).toEqual([{"text": "hello w*orl*d!", "type": "plain"}]); + }); + + it('works for around pills', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.plain("hello there "), + pc.atRoomPill("@room"), + pc.plain(", how are you doing?"), + ], pc, renderer); + + const range = model.startRange(model.positionForOffset(6, false), + model.positionForOffset(30, false)); // around "there @room, how are you" + + expect(range.parts.map(p => p.text).join("")).toBe("there @room, how are you"); + expect(model.serializeParts()).toEqual([ + {"text": "hello there ", "type": "plain"}, + {"text": "@room", "type": "at-room-pill"}, + {"text": ", how are you doing?", "type": "plain"}, + ]); + toggleInlineFormat(range, "_"); + expect(model.serializeParts()).toEqual([ + {"text": "hello _there ", "type": "plain"}, + {"text": "@room", "type": "at-room-pill"}, + {"text": ", how are you_ doing?", "type": "plain"}, + ]); + }); + + it('works for a paragraph', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.plain("hello world,"), + pc.newline(), + pc.plain("how are you doing?"), + ], pc, renderer); + + const range = model.startRange(model.positionForOffset(6, false), + model.positionForOffset(16, false)); // around "world,\nhow" + + expect(range.parts.map(p => p.text).join("")).toBe("world,\nhow"); + expect(model.serializeParts()).toEqual([ + {"text": "hello world,", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "how are you doing?", "type": "plain"}, + ]); + toggleInlineFormat(range, "**"); + expect(model.serializeParts()).toEqual([ + {"text": "hello **world,", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "how** are you doing?", "type": "plain"}, + ]); + }); + + it('works for multiple paragraph', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.plain("hello world,"), + pc.newline(), + pc.plain("how are you doing?"), + pc.newline(), + pc.newline(), + pc.plain("new paragraph"), + ], pc, renderer); + + let range = model.startRange(model.positionForOffset(0, true), + model.getPositionAtEnd()); // select-all + + expect(model.serializeParts()).toEqual([ + {"text": "hello world,", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "how are you doing?", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "\n", "type": "newline"}, + {"text": "new paragraph", "type": "plain"}, + ]); + toggleInlineFormat(range, "__"); + expect(model.serializeParts()).toEqual([ + {"text": "__hello world,", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "how are you doing?__", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "\n", "type": "newline"}, + {"text": "__new paragraph__", "type": "plain"}, + ]); + range = model.startRange(model.positionForOffset(0, true), + model.getPositionAtEnd()); // select-all + console.log("RANGE", range.parts); + toggleInlineFormat(range, "__"); + expect(model.serializeParts()).toEqual([ + {"text": "hello world,", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "how are you doing?", "type": "plain"}, + {"text": "\n", "type": "newline"}, + {"text": "\n", "type": "newline"}, + {"text": "new paragraph", "type": "plain"}, + ]); + }); + }); +}); diff --git a/test/editor/range-test.js b/test/editor/range-test.js index 53fb6cb765..b69ed9eb53 100644 --- a/test/editor/range-test.js +++ b/test/editor/range-test.js @@ -15,17 +15,7 @@ limitations under the License. */ import EditorModel from "../../src/editor/model"; -import {createPartCreator} from "./mock"; - -function createRenderer() { - const render = (c) => { - render.caret = c; - render.count += 1; - }; - render.count = 0; - render.caret = null; - return render; -} +import {createPartCreator, createRenderer} from "./mock"; const pillChannel = "#riot-dev:matrix.org"; From 9a530a72f6d27dd67d338533fe3823c880d7ff77 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 15:36:25 +0000 Subject: [PATCH 072/282] delint --- test/editor/operations-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/editor/operations-test.js b/test/editor/operations-test.js index 591166759c..872cc78bdb 100644 --- a/test/editor/operations-test.js +++ b/test/editor/operations-test.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {getLineAndNodePosition} from "../../src/editor/caret"; import EditorModel from "../../src/editor/model"; import {createPartCreator, createRenderer} from "./mock"; import {toggleInlineFormat} from "../../src/editor/operations"; From a2892f5b02a8998a7d19985b59f65d246d99c99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 16:40:32 +0100 Subject: [PATCH 073/282] EventIndex: Fix some lint issues. --- src/components/views/settings/EventIndexPanel.js | 9 +-------- .../settings/tabs/user/PreferencesUserSettingsTab.js | 1 - src/indexing/EventIndex.js | 4 ++-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index fd3facbc6b..8ed4b114e7 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -15,16 +15,10 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import AccessibleButton from "../elements/AccessibleButton"; -import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; -import Field from "../elements/Field"; import {formatBytes} from "../../../utils/FormattingUtils"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; @@ -83,7 +77,6 @@ export default class EventIndexPanel extends React.Component { onFinished: () => {}, }, null, /* priority = */ false, /* static = */ true, ); - } render() { @@ -111,7 +104,7 @@ export default class EventIndexPanel extends React.Component { { _t( "Riot can't securely cache encrypted messages locally" + "while running in a web browser. Use Riot Desktop for" + - "encrypted messages to appear in search results." + "encrypted messages to appear in search results.", ) }
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 8cbaba037d..bd1b7c2ca4 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,7 +23,6 @@ import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; -import {formatBytes} from "../../../../../utils/FormattingUtils"; export default class PreferencesUserSettingsTab extends React.Component { static COMPOSER_SETTINGS = [ diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 5676636eed..435f67447d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -470,8 +470,8 @@ export default class EventIndex { /** * Get the room that we are currently crawling. * - * @returns A MatrixRoom that is being currently crawled, null if no room is - * currently being crawled. + * @returns {Room} A MatrixRoom that is being currently crawled, null + * if no room is currently being crawled. */ currentRoom() { if (this._currentCheckpoint === null && this.crawlerCheckpoints.length === 0) { From b5e902e1f2f8f3a00fea93adeb7985db3ba13b23 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 15:55:21 +0000 Subject: [PATCH 074/282] Fix escaping commands using double-slash //, e.g //plain sends `/plain` --- src/components/views/rooms/SendMessageComposer.js | 9 +++++++-- src/editor/serialize.js | 12 ++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index c11d940331..c4ae2929af 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -24,6 +24,8 @@ import { containsEmote, stripEmoteCommand, unescapeMessage, + startsWith, + stripPrefix, } from '../../../editor/serialize'; import {CommandPartCreator} from '../../../editor/parts'; import BasicMessageComposer from "./BasicMessageComposer"; @@ -61,6 +63,9 @@ function createMessageContent(model, permalinkCreator) { if (isEmote) { model = stripEmoteCommand(model); } + if (startsWith(model, "//")) { + model = stripPrefix(model, "/"); + } model = unescapeMessage(model); const repliedToEvent = RoomViewStore.getQuotingEvent(); @@ -175,13 +180,13 @@ export default class SendMessageComposer extends React.Component { const parts = this.model.parts; const firstPart = parts[0]; if (firstPart) { - if (firstPart.type === "command") { + if (firstPart.type === "command" && !firstPart.text.startsWith("//")) { return true; } // be extra resilient when somehow the AutocompleteWrapperModel or // CommandPartCreator fails to insert a command part, so we don't send // a command as a message - if (firstPart.text.startsWith("/") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { + if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { return true; } } diff --git a/src/editor/serialize.js b/src/editor/serialize.js index a55eed97da..ba380f2809 100644 --- a/src/editor/serialize.js +++ b/src/editor/serialize.js @@ -61,18 +61,26 @@ export function textSerialize(model) { } export function containsEmote(model) { + return startsWith(model, "/me "); +} + +export function startsWith(model, prefix) { const firstPart = model.parts[0]; // part type will be "plain" while editing, // and "command" while composing a message. return firstPart && (firstPart.type === "plain" || firstPart.type === "command") && - firstPart.text.startsWith("/me "); + firstPart.text.startsWith(prefix); } export function stripEmoteCommand(model) { // trim "/me " + return stripPrefix(model, "/me "); +} + +export function stripPrefix(model, prefix) { model = model.clone(); - model.removeText({index: 0, offset: 0}, 4); + model.removeText({index: 0, offset: 0}, prefix.length); return model; } From 47ea453abfbd6ac5ce7f4e474d84eb7c8a3d4083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 16:58:41 +0100 Subject: [PATCH 075/282] ManageEventIndex: Fix a couple of lint issues. --- .../views/dialogs/eventindex/ManageEventIndex.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 23aa61c33a..8623856b2e 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import * as sdk from '../../../../index'; -import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import PropTypes from 'prop-types'; import { _t } from '../../../../languageHandler'; @@ -52,7 +51,6 @@ export default class ManageEventIndex extends React.Component { crawlerSleepTime: SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), }; - } async updateCurrentRoom(room) { @@ -146,10 +144,8 @@ export default class ManageEventIndex extends React.Component { crawlerState =
{_t("Not downloading messages for any room.")}
; } else { crawlerState = ( -
{_t( - "Downloading mesages for %(currentRoom)s.", - { currentRoom: this.state.currentRoom } - )} +
+ {_t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom })}
); } @@ -159,7 +155,7 @@ export default class ManageEventIndex extends React.Component {
{ _t( "Riot is securely caching encrypted messages locally for them " + - "to appear in search results:" + "to appear in search results:", ) }
@@ -188,7 +184,7 @@ export default class ManageEventIndex extends React.Component { { _t( "Riot can't securely cache encrypted messages locally" + "while running in a web browser. Use Riot Desktop for" + - "encrypted messages to appear in search results." + "encrypted messages to appear in search results.", ) }
@@ -196,9 +192,7 @@ export default class ManageEventIndex extends React.Component { } const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - let buttons; - - buttons =
+ const buttons =
{_t("Disable")} From 060938379a7183f7959aa4af436eef775ce6c9d3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 15:58:51 +0000 Subject: [PATCH 076/282] Fix changes after typing / at pos=0 allowing to cancel command --- src/components/views/rooms/SendMessageComposer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index c4ae2929af..8de105d84d 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -180,13 +180,14 @@ export default class SendMessageComposer extends React.Component { const parts = this.model.parts; const firstPart = parts[0]; if (firstPart) { - if (firstPart.type === "command" && !firstPart.text.startsWith("//")) { + if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) { return true; } // be extra resilient when somehow the AutocompleteWrapperModel or // CommandPartCreator fails to insert a command part, so we don't send // a command as a message - if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { + if (firstPart.text.startsWith("/") && firstPart.text.startsWith("//") && !firstPart.text.startsWith("//") + && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { return true; } } From b34fe45518fbfff23f92a860a7af653fc383180b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 16:50:04 +0000 Subject: [PATCH 077/282] First attempt. Has a lag issue due to the async-clear :( --- src/SlashCommands.js | 13 +++---- .../views/rooms/SendMessageComposer.js | 39 +++++++++++++++++-- src/i18n/strings/en_EN.json | 4 +- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 20b8ba76da..414dd60121 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -81,6 +81,8 @@ class Command { } run(roomId, args) { + // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` + if (!this.runFn) return; return this.runFn.bind(this)(roomId, args); } @@ -918,12 +920,12 @@ export function processCommandInput(roomId, input) { input = input.replace(/\s+$/, ''); if (input[0] !== '/') return null; // not a command - const bits = input.match(/^(\S+?)( +((.|\n)*))?$/); + const bits = input.match(/^(\S+?)(?: +((.|\n)*))?$/); let cmd; let args; if (bits) { cmd = bits[1].substring(1).toLowerCase(); - args = bits[3]; + args = bits[2]; } else { cmd = input; } @@ -932,11 +934,8 @@ export function processCommandInput(roomId, input) { cmd = aliases[cmd]; } if (CommandMap[cmd]) { - // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` - if (!CommandMap[cmd].runFn) return null; - return CommandMap[cmd].run(roomId, args); - } else { - return reject(_t('Unrecognised command:') + ' ' + input); } + return null; + // return reject(_t('Unrecognised command:') + ' ' + input); } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 8de105d84d..9f3a407402 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -43,6 +43,9 @@ import ContentMessages from '../../../ContentMessages'; import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +const SEND_ANYWAY = Symbol("send-anyway"); +const UNKNOWN_CMD = Symbol("unknown-cmd"); + function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); Object.assign(content, replyContent); @@ -194,7 +197,12 @@ export default class SendMessageComposer extends React.Component { return false; } - async _runSlashCommand() { + /** + * Parses and executes current input as a Slash Command + * @returns {Promise} UNKNOWN_CMD if the command is not known, + * SEND_ANYWAY if the input should be sent as message instead + */ + async _tryRunSlashCommand() { const commandText = this.model.parts.reduce((text, part) => { // use mxid to textify user pills in a command if (part.type === "user-pill") { @@ -236,16 +244,38 @@ export default class SendMessageComposer extends React.Component { } else { console.log("Command success."); } + } else { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + // unknown command, ask the user if they meant to send it as a message + const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, { + title: _t("Unknown Command"), + description: _t("Unrecognised command: ") + commandText, + button: _t('Send as message'), + danger: true, + }); + const [sendAnyway] = await finished; + return sendAnyway ? SEND_ANYWAY : UNKNOWN_CMD; } } - _sendMessage() { + async _sendMessage() { if (this.model.isEmpty) { return; } + + let shouldSend = true; + if (!containsEmote(this.model) && this._isSlashCommand()) { - this._runSlashCommand(); - } else { + const resp = await this._tryRunSlashCommand(); + if (resp === UNKNOWN_CMD) { + // unknown command, bail to let the user modify it + return; + } + + shouldSend = resp === SEND_ANYWAY; + } + + if (shouldSend) { const isReply = !!RoomViewStore.getQuotingEvent(); const {roomId} = this.props.room; const content = createMessageContent(this.model, this.props.permalinkCreator); @@ -259,6 +289,7 @@ export default class SendMessageComposer extends React.Component { }); } } + this.sendHistoryManager.save(this.model); // clear composer this.model.reset([]); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f0eab6b12d..314731a910 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -200,7 +200,6 @@ "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", - "Unrecognised command:": "Unrecognised command:", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", @@ -1077,6 +1076,9 @@ "Server error": "Server error", "Command error": "Command error", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", + "Unknown Command": "Unknown Command", + "Unrecognised command: ": "Unrecognised command: ", + "Send as message": "Send as message", "Failed to connect to integration manager": "Failed to connect to integration manager", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", "Add some now": "Add some now", From 9f7df33bc30acaceaa7d1f9d100fbf2de153d8d3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 16:57:07 +0000 Subject: [PATCH 078/282] re-arrange to split the async task into two and only wait on the user-blocking one --- src/SlashCommands.js | 10 +- .../views/rooms/SendMessageComposer.js | 100 +++++++++--------- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 414dd60121..2eb34576ac 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -907,14 +907,14 @@ const aliases = { /** - * Process the given text for /commands and perform them. + * Process the given text for /commands and return a bound method to perform them. * @param {string} roomId The room in which the command was performed. * @param {string} input The raw text input by the user. - * @return {Object|null} An object with the property 'error' if there was an error + * @return {null|function(): Object} Function returning an object with the property 'error' if there was an error * processing the command, or 'promise' if a request was sent out. * Returns null if the input didn't match a command. */ -export function processCommandInput(roomId, input) { +export function getCommand(roomId, input) { // trim any trailing whitespace, as it can confuse the parser for // IRC-style commands input = input.replace(/\s+$/, ''); @@ -934,8 +934,6 @@ export function processCommandInput(roomId, input) { cmd = aliases[cmd]; } if (CommandMap[cmd]) { - return CommandMap[cmd].run(roomId, args); + return () => CommandMap[cmd].run(roomId, args); } - return null; - // return reject(_t('Unrecognised command:') + ' ' + input); } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 9f3a407402..994c28f531 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -35,7 +35,7 @@ import ReplyThread from "../elements/ReplyThread"; import {parseEvent} from '../../../editor/deserialize'; import {findEditableEvent} from '../../../utils/EventUtils'; import SendHistoryManager from "../../../SendHistoryManager"; -import {processCommandInput} from '../../../SlashCommands'; +import {getCommand} from '../../../SlashCommands'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; import {_t, _td} from '../../../languageHandler'; @@ -197,12 +197,7 @@ export default class SendMessageComposer extends React.Component { return false; } - /** - * Parses and executes current input as a Slash Command - * @returns {Promise} UNKNOWN_CMD if the command is not known, - * SEND_ANYWAY if the input should be sent as message instead - */ - async _tryRunSlashCommand() { + _getSlashCommand() { const commandText = this.model.parts.reduce((text, part) => { // use mxid to textify user pills in a command if (part.type === "user-pill") { @@ -210,51 +205,41 @@ export default class SendMessageComposer extends React.Component { } return text + part.text; }, ""); - const cmd = processCommandInput(this.props.room.roomId, commandText); + return [getCommand(this.props.room.roomId, commandText), commandText]; + } - if (cmd) { - let error = cmd.error; - if (cmd.promise) { - try { - await cmd.promise; - } catch (err) { - error = err; - } + async _runSlashCommand(fn) { + const cmd = fn(); + let error = cmd.error; + if (cmd.promise) { + try { + await cmd.promise; + } catch (err) { + error = err; } - if (error) { - console.error("Command failure: %s", error); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - // assume the error is a server error when the command is async - const isServerError = !!cmd.promise; - const title = isServerError ? _td("Server error") : _td("Command error"); + } + if (error) { + console.error("Command failure: %s", error); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + // assume the error is a server error when the command is async + const isServerError = !!cmd.promise; + const title = isServerError ? _td("Server error") : _td("Command error"); - let errText; - if (typeof error === 'string') { - errText = error; - } else if (error.message) { - errText = error.message; - } else { - errText = _t("Server unavailable, overloaded, or something else went wrong."); - } - - Modal.createTrackedDialog(title, '', ErrorDialog, { - title: _t(title), - description: errText, - }); + let errText; + if (typeof error === 'string') { + errText = error; + } else if (error.message) { + errText = error.message; } else { - console.log("Command success."); + errText = _t("Server unavailable, overloaded, or something else went wrong."); } - } else { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - // unknown command, ask the user if they meant to send it as a message - const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, { - title: _t("Unknown Command"), - description: _t("Unrecognised command: ") + commandText, - button: _t('Send as message'), - danger: true, + + Modal.createTrackedDialog(title, '', ErrorDialog, { + title: _t(title), + description: errText, }); - const [sendAnyway] = await finished; - return sendAnyway ? SEND_ANYWAY : UNKNOWN_CMD; + } else { + console.log("Command success."); } } @@ -266,13 +251,24 @@ export default class SendMessageComposer extends React.Component { let shouldSend = true; if (!containsEmote(this.model) && this._isSlashCommand()) { - const resp = await this._tryRunSlashCommand(); - if (resp === UNKNOWN_CMD) { - // unknown command, bail to let the user modify it - return; + const [cmd, commandText] = this._getSlashCommand(); + if (cmd) { + shouldSend = false; + this._runSlashCommand(cmd); + } else { + // ask the user if their unknown command should be sent as a message instead + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + // unknown command, ask the user if they meant to send it as a message + const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, { + title: _t("Unknown Command"), + description: _t("Unrecognised command: ") + commandText, + button: _t('Send as message'), + danger: true, + }); + const [sendAnyway] = await finished; + // if !sendAnyway bail to let the user edit the composer and try again + if (!sendAnyway) return; } - - shouldSend = resp === SEND_ANYWAY; } if (shouldSend) { From 2480f709b31f9f05270430bf73235c904f029b2c Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 17:19:10 +0000 Subject: [PATCH 079/282] E2ESTATE -> E2E_STATE --- src/components/views/rooms/EventTile.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 9c73daaa50..bcd32d2c9c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -66,7 +66,7 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; -const E2ESTATE = { +const E2E_STATE = { VERIFIED: "verified", WARNING: "warning", UNKNOWN: "unknown", @@ -305,7 +305,7 @@ export default createReactClass({ const verified = await this.context.isEventSenderVerified(mxEvent); if (verified) { this.setState({ - verified: E2ESTATE.VERIFIED, + verified: E2E_STATE.VERIFIED, }, () => { // Decryption may have caused a change in size this.props.onHeightChanged(); @@ -316,13 +316,13 @@ export default createReactClass({ const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { this.setState({ - verified: E2ESTATE.UNKNOWN, + verified: E2E_STATE.UNKNOWN, }, this.props.onHeightChanged); // Decryption may have cause a change in size return; } this.setState({ - verified: eventSenderTrust.isVerified() ? E2ESTATE.VERIFIED : E2ESTATE.WARNING, + verified: eventSenderTrust.isVerified() ? E2E_STATE.VERIFIED : E2E_STATE.WARNING, }, this.props.onHeightChanged); // Decryption may have caused a change in size }, @@ -503,9 +503,9 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified === E2ESTATE.VERIFIED) { + if (this.state.verified === E2E_STATE.VERIFIED) { return; // no icon for verified - } else if (this.state.verified === E2ESTATE.UNKNOWN) { + } else if (this.state.verified === E2E_STATE.UNKNOWN) { return (); } else { return (); @@ -636,9 +636,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2ESTATE.VERIFIED, - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2ESTATE.WARNING, - mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2ESTATE.UNKNOWN, + mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2E_STATE.VERIFIED, + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2E_STATE.WARNING, + mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2E_STATE.UNKNOWN, mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, From 931c0885de5db562d24a5c3fc451a4296fbaf69f Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 21 Jan 2020 17:06:26 +0000 Subject: [PATCH 080/282] Translated using Weblate (Turkish) Currently translated at 70.7% (1440 of 2038 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 1e92d28586..5d83ff5b06 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1440,5 +1440,17 @@ "Change room avatar": "Oda resmini değiştir", "Members only (since the point in time of selecting this option)": "Sadece üyeler ( bu seçeneği seçtiğinizden itibaren)", "Unable to revoke sharing for email address": "E-posta adresi paylaşımı kaldırılamadı", - "Unable to revoke sharing for phone number": "Telefon numarası paylaşımı kaldırılamıyor" + "Unable to revoke sharing for phone number": "Telefon numarası paylaşımı kaldırılamıyor", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Bu sayfadaki oda, kullanıcı veya grup ID si gibi betimleyici bilgiler sunucuya gönderilmeden önce silindi.", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Çağrıların sağlıklı bir şekide yapılabilmesi için lütfen anasunucunuzun (%(homeserverDomain)s) yöneticisinden bir TURN sunucusu yapılandırmasını isteyin.", + "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)s kullanıcıları isimlerini %(count)s kez değiştirdiler", + "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)s ismini %(count)s kez değiştirdi", + "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)s değişiklik yapmadı", + "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)s %(count)s kez değişiklik yapmadı", + "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)s değişiklik yapmadı", + "Room alias": "Oda lakabı", + "Please provide a room alias": "Lütfen bir oda lakabı belirtin", + "This alias is available to use": "Bu lakap kullanmaya uygun", + "This alias is already in use": "Bu lakap zaten kullanımda", + "And %(count)s more...|other": "ve %(count)s kez daha..." } From 33220c2d7230c999b0f65158a66affdd87e756a1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 21 Jan 2020 10:53:17 -0700 Subject: [PATCH 081/282] Ensure generated files are present for riot-web tests --- scripts/ci/riot-unit-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ci/riot-unit-tests.sh b/scripts/ci/riot-unit-tests.sh index 7a9ed77793..337c0fe6c3 100755 --- a/scripts/ci/riot-unit-tests.sh +++ b/scripts/ci/riot-unit-tests.sh @@ -8,4 +8,5 @@ set -ev scripts/ci/layered-riot-web.sh cd ../riot-web +yarn build:genfiles # so the tests can run. Faster version of `build` yarn test From a8df058ea6f7e55e2b8f1bbddc171a46777febe8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 17:54:27 +0000 Subject: [PATCH 082/282] tidy up, improve wording on modal --- .../views/rooms/SendMessageComposer.js | 23 +++++++++++++------ src/i18n/strings/en_EN.json | 4 +++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 994c28f531..7870699fec 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -43,9 +43,6 @@ import ContentMessages from '../../../ContentMessages'; import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -const SEND_ANYWAY = Symbol("send-anyway"); -const UNKNOWN_CMD = Symbol("unknown-cmd"); - function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); Object.assign(content, replyContent); @@ -256,14 +253,26 @@ export default class SendMessageComposer extends React.Component { shouldSend = false; this._runSlashCommand(cmd); } else { - // ask the user if their unknown command should be sent as a message instead + // ask the user if their unknown command should be sent as a message const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - // unknown command, ask the user if they meant to send it as a message const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, { title: _t("Unknown Command"), - description: _t("Unrecognised command: ") + commandText, + description:
+

+ { _t("Unrecognised command: %(commandText)s", {commandText}) } +

+

+ { _t("You can use /help to list available commands. Did you mean to send this as a message?", {}, { + code: t => { t }, + }) } +

+

+ { _t("Protip: Begin your message with // to start it with a slash.", {}, { + code: t => { t }, + }) } +

+
, button: _t('Send as message'), - danger: true, }); const [sendAnyway] = await finished; // if !sendAnyway bail to let the user edit the composer and try again diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 314731a910..da4111aec8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1077,7 +1077,9 @@ "Command error": "Command error", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", "Unknown Command": "Unknown Command", - "Unrecognised command: ": "Unrecognised command: ", + "Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "You can use /help to list available commands. Did you mean to send this as a message?", + "Protip: Begin your message with // to start it with a slash.": "Protip: Begin your message with // to start it with a slash.", "Send as message": "Send as message", "Failed to connect to integration manager": "Failed to connect to integration manager", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", From e455aa474d652c1f66228ea4e2e2f8b69f2a796b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 17:58:53 +0000 Subject: [PATCH 083/282] improve copy further --- src/components/views/rooms/SendMessageComposer.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 7870699fec..4402a034f6 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -267,7 +267,7 @@ export default class SendMessageComposer extends React.Component { }) }

- { _t("Protip: Begin your message with // to start it with a slash.", {}, { + { _t("Hint: Begin your message with // to start it with a slash.", {}, { code: t => { t }, }) }

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index da4111aec8..a3c56e5973 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1079,7 +1079,7 @@ "Unknown Command": "Unknown Command", "Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s", "You can use /help to list available commands. Did you mean to send this as a message?": "You can use /help to list available commands. Did you mean to send this as a message?", - "Protip: Begin your message with // to start it with a slash.": "Protip: Begin your message with // to start it with a slash.", + "Hint: Begin your message with // to start it with a slash.": "Hint: Begin your message with // to start it with a slash.", "Send as message": "Send as message", "Failed to connect to integration manager": "Failed to connect to integration manager", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", From 708f62784fbf7c46e2105e255faa28f6d1fd5879 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 21 Jan 2020 10:59:33 -0700 Subject: [PATCH 084/282] Consistency Co-Authored-By: J. Ryan Stinnett --- scripts/fetchdep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index f477fd08b8..0142305797 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -17,7 +17,7 @@ clone() { if [ -n "$branch" ] then echo "Trying to use $org/$repo#$branch" - git clone git://github.com/$org/$repo.git $repo --branch "$branch" --depth=1 && exit 0 + git clone git://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0 fi } From 7b26067397e5bc1870c725968778bde83c6b0b34 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Jan 2020 18:03:01 +0000 Subject: [PATCH 085/282] delint --- src/components/views/rooms/SendMessageComposer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 4402a034f6..c4970c4570 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -262,7 +262,8 @@ export default class SendMessageComposer extends React.Component { { _t("Unrecognised command: %(commandText)s", {commandText}) }

- { _t("You can use /help to list available commands. Did you mean to send this as a message?", {}, { + { _t("You can use /help to list available commands. " + + "Did you mean to send this as a message?", {}, { code: t => { t }, }) }

From 2c6fe780123e2cdffdb65961f282c7afb8e06156 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 10:36:20 +0000 Subject: [PATCH 086/282] Fix roving room list for resizer and ff tabstop a11y --- src/accessibility/RovingTabIndex.js | 70 ++++++++----------- src/components/structures/RoomSubList.js | 4 -- .../views/groups/GroupInviteTile.js | 3 +- src/components/views/rooms/RoomList.js | 27 +++---- src/components/views/rooms/RoomTile.js | 3 +- 5 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.js index 8924815f23..38f2594baf 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.js @@ -129,7 +129,7 @@ const reducer = (state, action) => { } }; -export const RovingTabIndexProvider = ({children, handleHomeEnd}) => { +export const RovingTabIndexProvider = ({children, handleHomeEnd, onKeyDown}) => { const [state, dispatch] = useReducer(reducer, { activeRef: null, refs: [], @@ -137,53 +137,43 @@ export const RovingTabIndexProvider = ({children, handleHomeEnd}) => { const context = useMemo(() => ({state, dispatch}), [state]); - if (handleHomeEnd) { - return - - { children } - - ; - } - - return - { children } - ; -}; -RovingTabIndexProvider.propTypes = { - handleHomeEnd: PropTypes.bool, -}; - -// Helper to handle Home/End to jump to first/last roving-tab-index for widgets such as treeview -export const HomeEndHelper = ({children}) => { - const context = useContext(RovingTabIndexContext); - - const onKeyDown = useCallback((ev) => { - // check if we actually have any items - if (context.state.refs.length <= 0) return; - - let handled = true; - switch (ev.key) { - case Key.HOME: - // move focus to first item - context.state.refs[0].current.focus(); - break; - case Key.END: - // move focus to last item - context.state.refs[context.state.refs.length - 1].current.focus(); - break; - default: - handled = false; + const onKeyDownHandler = useCallback((ev) => { + let handled = false; + if (handleHomeEnd) { + // check if we actually have any items + switch (ev.key) { + case Key.HOME: + handled = true; + // move focus to first item + if (context.state.refs.length > 0) { + context.state.refs[0].current.focus(); + } + break; + case Key.END: + handled = true; + // move focus to last item + if (context.state.refs.length > 0) { + context.state.refs[context.state.refs.length - 1].current.focus(); + } + break; + } } if (handled) { ev.preventDefault(); ev.stopPropagation(); + } else if (onKeyDown) { + return onKeyDown(ev); } }, [context.state]); - return
- { children } -
; + return + { children({onKeyDownHandler}) } + ; +}; +RovingTabIndexProvider.propTypes = { + handleHomeEnd: PropTypes.bool, + onKeyDown: PropTypes.func, }; // Hook to register a roving tab index diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 2d41abf902..600b418fe0 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -142,10 +142,6 @@ export default class RoomSubList extends React.PureComponent { onHeaderKeyDown = (ev) => { switch (ev.key) { - case Key.TAB: - // Prevent LeftPanel handling Tab if focus is on the sublist header itself - ev.stopPropagation(); - break; case Key.ARROW_LEFT: // On ARROW_LEFT collapse the room sublist if (!this.state.hidden && !this.props.forceExpand) { diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index 3b15c6ff41..91c930525d 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -128,7 +128,8 @@ export default createReactClass({ 'mx_RoomTile_badgeShown': this.state.badgeHover || isMenuDisplayed, }); - const label =
+ // XXX: this is a workaround for Firefox giving this div a tabstop :( [tabIndex] + const label =
{ groupName }
; diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index bd563b2f28..ee3100b535 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -777,21 +777,22 @@ export default createReactClass({ const subListComponents = this._mapSubListProps(subLists); - const {resizeNotifier, collapsed, searchFilter, ConferenceHandler, ...props} = this.props; // eslint-disable-line + const {resizeNotifier, collapsed, searchFilter, ConferenceHandler, onKeyDown, ...props} = this.props; // eslint-disable-line return ( -
- + + {({onKeyDownHandler}) =>
{ subListComponents } - -
+
} + ); }, }); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 3b13001225..f4f5fa10fc 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -353,7 +353,8 @@ export default createReactClass({ }); subtextLabel = subtext ? { subtext } : null; - label =
{ name }
; + // XXX: this is a workaround for Firefox giving this div a tabstop :( [tabIndex] + label =
{ name }
; } else if (this.state.hover) { const Tooltip = sdk.getComponent("elements.Tooltip"); tooltip = ; From 37fb500e22aeb4762cef90ca8ca4f28eead57fa6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 10:41:10 +0000 Subject: [PATCH 087/282] fix useCallback dependencies, delint --- src/accessibility/RovingTabIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.js index 38f2594baf..b481f08fe2 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.js @@ -165,7 +165,7 @@ export const RovingTabIndexProvider = ({children, handleHomeEnd, onKeyDown}) => } else if (onKeyDown) { return onKeyDown(ev); } - }, [context.state]); + }, [context.state, onKeyDown, handleHomeEnd]); return { children({onKeyDownHandler}) } From d211372740588bdac1ad740111d28183180d5e8b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 10:44:02 +0000 Subject: [PATCH 088/282] UI to bootsrap SSSS from key backup --- .../CreateSecretStorageDialog.js | 130 ++++++++++++++---- .../views/elements/DialogButtons.js | 17 ++- src/i18n/strings/en_EN.json | 6 +- 3 files changed, 123 insertions(+), 30 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 01b9c9c7c8..7d1b82681b 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -1,6 +1,6 @@ /* Copyright 2018, 2019 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. @@ -70,9 +70,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent { setPassPhrase: false, backupInfo: null, backupSigStatus: null, + // does the server offer a UI auth flow with just m.login.password + // for /keys/device_signing/upload? + canUploadKeysWithPasswordOnly: null, + accountPassword: '', + accountPasswordCorrect: null, }; this._fetchBackupInfo(); + this._queryKeyUploadAuth(); } componentWillUnmount() { @@ -96,11 +102,32 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + 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.flows) { + console.log("uploadDeviceSigningKeys advertised no flows!"); + } + const canUploadKeysWithPasswordOnly = error.data.flows.some(f => { + return f.stages.length === 1 && f.stages[0] === 'm.login.password'; + }); + this.setState({ + canUploadKeysWithPasswordOnly, + }); + } + } + _collectRecoveryKeyNode = (n) => { this._recoveryKeyNode = n; } - _onMigrateNextClick = () => { + _onMigrateFormSubmit = (e) => { + e.preventDefault(); this._bootstrapSecretStorage(); } @@ -127,29 +154,46 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _doBootstrapUIAuth = async (makeRequest) => { + if (this.state.canUploadKeysWithPasswordOnly) { + await makeRequest({ + type: 'm.login.password', + identifier: { + type: 'm.id.user', + user: MatrixClientPeg.get().getUserId(), + }, + // https://github.com/matrix-org/synapse/issues/5665 + user: MatrixClientPeg.get().getUserId(), + password: this.state.accountPassword, + }); + } else { + const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); + const { finished } = Modal.createTrackedDialog( + 'Cross-signing keys dialog', '', InteractiveAuthDialog, + { + title: _t("Send cross-signing keys to homeserver"), + matrixClient: MatrixClientPeg.get(), + makeRequest, + }, + ); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + } + } + _bootstrapSecretStorage = async () => { this.setState({ phase: PHASE_STORING, error: null, }); + const cli = MatrixClientPeg.get(); + try { - const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); await cli.bootstrapSecretStorage({ - authUploadDeviceSigningKeys: async (makeRequest) => { - const { finished } = Modal.createTrackedDialog( - 'Cross-signing keys dialog', '', InteractiveAuthDialog, - { - title: _t("Send cross-signing keys to homeserver"), - matrixClient: MatrixClientPeg.get(), - makeRequest, - }, - ); - const [confirmed] = await finished; - if (!confirmed) { - throw new Error("Cross-signing key upload auth canceled"); - } - }, + authUploadDeviceSigningKeys: this._doBootstrapUIAuth, createSecretStorageKey: async () => this._keyInfo, keyBackupInfo: this.state.backupInfo, }); @@ -157,7 +201,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent { phase: PHASE_DONE, }); } catch (e) { - this.setState({ error: e }); + if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { + this.setState({ + accountPasswordCorrect: false, + phase: PHASE_MIGRATE, + }); + } else { + this.setState({ error: e }); + } console.error("Error bootstrapping secret storage", e); } } @@ -285,6 +336,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE; } + _onAccountPasswordChange = (e) => { + this.setState({ + accountPassword: e.target.value, + }); + } + _renderPhaseRestoreKeyBackup() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return
@@ -309,18 +366,41 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // it automatically. // https://github.com/vector-im/riot-web/issues/11696 const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return
+ const Field = sdk.getComponent('views.elements.Field'); + + let authPrompt; + if (this.state.canUploadKeysWithPasswordOnly) { + authPrompt =
+
{_t("Enter your account password to confirm the upgrade:")}
+
+
; + } else { + authPrompt =

+ {_t("You'll need to authenticate with the server to confirm the upgrade.")} +

; + } + + return

{_t( - "Secret Storage will be set up using your existing key backup details. " + - "Your secret storage passphrase and recovery key will be the same as " + - "they were for your key backup.", + "Upgrade this device to allow it to verify other devices, " + + "granting them access to encrypted messages and marking them " + + "as trusted for other users.", )}

+
{authPrompt}
-
; + ; } _renderPhasePassPhrase() { @@ -564,7 +644,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_RESTORE_KEY_BACKUP: return _t('Restore your Key Backup'); case PHASE_MIGRATE: - return _t('Migrate from Key Backup'); + return _t('Upgrade your encryption'); case PHASE_PASSPHRASE: return _t('Secure your encrypted messages with a passphrase'); case PHASE_PASSPHRASE_CONFIRM: diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js index 4e47e73052..7b37fdb4eb 100644 --- a/src/components/views/elements/DialogButtons.js +++ b/src/components/views/elements/DialogButtons.js @@ -34,8 +34,11 @@ export default createReactClass({ // A node to insert into the cancel button instead of default "Cancel" cancelButton: PropTypes.node, + // If true, make the primary button a form submit button (input type="submit") + primaryIsSubmit: PropTypes.bool, + // onClick handler for the primary button. - onPrimaryButtonClick: PropTypes.func.isRequired, + onPrimaryButtonClick: PropTypes.func, // should there be a cancel button? default: true hasCancel: PropTypes.bool, @@ -70,15 +73,23 @@ export default createReactClass({ } let cancelButton; if (this.props.cancelButton || this.props.hasCancel) { - cancelButton = ; } + return (
{ cancelButton } { this.props.children } - ; describe("RovingTabIndex", () => { it("RovingTabIndexProvider renders children as expected", () => { const wrapper = mount( -
Test
+ {() =>
Test
}
); expect(wrapper.text()).toBe("Test"); expect(wrapper.html()).toBe('
Test
'); @@ -55,9 +55,11 @@ describe("RovingTabIndex", () => { it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => { const wrapper = mount( - { button1 } - { button2 } - { button3 } + {() => + { button1 } + { button2 } + { button3 } + } ); // should begin with 0th being active @@ -95,13 +97,15 @@ describe("RovingTabIndex", () => { it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => { const wrapper = mount( - { button1 } - { button2 } - - {({onFocus, isActive, ref}) => - - } - + {() => + { button1 } + { button2 } + + {({onFocus, isActive, ref}) => + + } + + } ); // should begin with 0th being active From 644d68d836b085cccac8d06cea1ee416894970cc Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 10:59:50 +0000 Subject: [PATCH 090/282] Update copy in bootstrap success dialog phase --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 10 +++++++--- src/i18n/strings/en_EN.json | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 7d1b82681b..d75126687c 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -613,7 +613,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Your access to encrypted messages is now protected.", + "This device can now verify other devices, granting them access " + + "to encrypted messages and marking them as trusted for other users.", + )}

+

{_t( + "Verify other users in their profile.", )}

{content} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3aad50f45e..29ce36a814 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1998,7 +1998,8 @@ "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 access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.", + "Verify other users in their profile.": "Verify other users in their profile.", "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.", "Set up secret storage": "Set up secret storage", "Restore your Key Backup": "Restore your Key Backup", @@ -2008,7 +2009,7 @@ "Recovery key": "Recovery key", "Keep it safe": "Keep it safe", "Storing secrets...": "Storing secrets...", - "Success!": "Success!", + "Encryption upgraded": "Encryption upgraded", "Unable to set up secret storage": "Unable to set up secret storage", "Retry": "Retry", "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", @@ -2020,6 +2021,7 @@ "Set up Secure Message Recovery": "Set up Secure Message Recovery", "Secure your backup with a passphrase": "Secure your backup with a passphrase", "Starting backup...": "Starting backup...", + "Success!": "Success!", "Create Key Backup": "Create Key Backup", "Unable to create key backup": "Unable to create key backup", "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.", From fc724cfe709ec046db13283e2fd8bf91646bb017 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 11:05:25 +0000 Subject: [PATCH 091/282] fix tests some moar --- test/accessibility/RovingTabIndex-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/accessibility/RovingTabIndex-test.js b/test/accessibility/RovingTabIndex-test.js index e7d7a0977d..8be4a2976c 100644 --- a/test/accessibility/RovingTabIndex-test.js +++ b/test/accessibility/RovingTabIndex-test.js @@ -47,7 +47,7 @@ const button4 = ; describe("RovingTabIndex", () => { it("RovingTabIndexProvider renders children as expected", () => { const wrapper = mount( - {() =>
Test
} + {() =>
Test
}
); expect(wrapper.text()).toBe("Test"); expect(wrapper.html()).toBe('
Test
'); @@ -82,14 +82,14 @@ describe("RovingTabIndex", () => { // update the children, it should remain on the same button wrapper.setProps({ - children: [button1, button4, button2, button3], + children: () => [button1, button4, button2, button3], }); wrapper.update(); checkTabIndexes(wrapper.find("button"), [-1, -1, 0, -1]); // update the children, remove the active button, it should move to the next one wrapper.setProps({ - children: [button1, button4, button3], + children: () => [button1, button4, button3], }); wrapper.update(); checkTabIndexes(wrapper.find("button"), [-1, -1, 0]); From 85ee6bd51f06be795c1e596f353de422a9f21083 Mon Sep 17 00:00:00 2001 From: Zoe Date: Wed, 22 Jan 2020 11:17:54 +0000 Subject: [PATCH 092/282] Don't warn on unverified users; ensured behavior stays the same with flags off --- src/components/views/rooms/EventTile.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index bcd32d2c9c..634b77c9e1 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -70,6 +70,7 @@ const E2E_STATE = { VERIFIED: "verified", WARNING: "warning", UNKNOWN: "unknown", + NORMAL: "normal", }; // Add all the Mjolnir stuff to the renderer @@ -313,6 +314,22 @@ export default createReactClass({ return; } + // If cross-signing is off, the old behaviour is to scream at the user + // as if they've done something wrong, which they haven't + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + this.setState({ + verified: E2E_STATE.WARNING, + }, this.props.onHeightChanged); + return; + } + + if (!this.context.checkUserTrust(mxEvent.getSender()).isCrossSigningVerified()) { + this.setState({ + verified: E2E_STATE.NORMAL, + }, this.props.onHeightChanged); + return; + } + const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); if (!eventSenderTrust) { this.setState({ @@ -503,7 +520,9 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified === E2E_STATE.VERIFIED) { + if (this.state.verified === E2E_STATE.NORMAL) { + return; // no icon if we've not even cross-signed the user + } else if (this.state.verified === E2E_STATE.VERIFIED) { return; // no icon for verified } else if (this.state.verified === E2E_STATE.UNKNOWN) { return (); From 78f7622fc59e3a393f9485e82fd5c8da179065db Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 22 Jan 2020 01:55:12 +0000 Subject: [PATCH 093/282] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index b459fb9306..694d5e3d84 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2079,5 +2079,6 @@ "Verify User": "驗證使用者", "For extra security, verify this user by checking a one-time code on both of your devices.": "為了提高安全性,請透過檢查您兩個裝置上的一次性代碼來驗證此使用者。", "For maximum security, do this in person.": "為了取得最強的安全性,請親自進行。", - "Start Verification": "開始驗證" + "Start Verification": "開始驗證", + "Encrypted by a deleted device": "被已刪除的裝置加密" } From d92ddebe5a1a8f5b24c40800e1fdaae5fbec7910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 22 Jan 2020 07:34:43 +0000 Subject: [PATCH 094/282] Translated using Weblate (French) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index abe05e9812..8fda9e458e 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2079,5 +2079,6 @@ "Verify User": "Vérifier l’utilisateur", "For extra security, verify this user by checking a one-time code on both of your devices.": "Pour une meilleure sécurité, vérifiez cet utilisateur en comparant un code à usage unique sur vos deux appareils.", "For maximum security, do this in person.": "Pour une sécurité maximale, faites-le en personne.", - "Start Verification": "Commencer la vérification" + "Start Verification": "Commencer la vérification", + "Encrypted by a deleted device": "Chiffré par un appareil supprimé" } From e923fdb1498e5cf3effcdbf2da3101510042dde1 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 21 Jan 2020 20:54:31 +0000 Subject: [PATCH 095/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index c5334c1592..f6c85e1674 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2080,5 +2080,6 @@ "Verify User": "Felhasználó ellenőrzése", "For extra security, verify this user by checking a one-time code on both of your devices.": "A biztonság fokozásáért ellenőrizd ezt a felhasználót egy egyszeri kód egyeztetésével mindkettőtök készülékén.", "For maximum security, do this in person.": "A legnagyobb biztonság érdekében ezt személyesen tedd meg.", - "Start Verification": "Ellenőrzés elindítása" + "Start Verification": "Ellenőrzés elindítása", + "Encrypted by a deleted device": "Egy már törölt eszköz titkosította" } From 5571164fe0c842fce6b0701b8f54622a0e09e52e Mon Sep 17 00:00:00 2001 From: random Date: Wed, 22 Jan 2020 11:29:25 +0000 Subject: [PATCH 096/282] Translated using Weblate (Italian) Currently translated at 100.0% (2039 of 2039 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index a519c138b1..d6ebc985fb 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2074,5 +2074,10 @@ "Done": "Fatto", "Without completing security on this device, it won’t have access to encrypted messages.": "Se non completi la sicurezza su questo dispositivo, esso non avrà accesso ai messaggi cifrati.", "Go Back": "Torna", - "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "L'archivio segreto verrà impostato usando i dettagli del backup chiavi esistente. La password dell'archivio segreto e la chiave di ripristino saranno le stesse del backup chiavi." + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "L'archivio segreto verrà impostato usando i dettagli del backup chiavi esistente. La password dell'archivio segreto e la chiave di ripristino saranno le stesse del backup chiavi.", + "Encrypted by a deleted device": "Cifrato da un dispositivo eliminato", + "Verify User": "Verifica utente", + "For extra security, verify this user by checking a one-time code on both of your devices.": "Per maggiore sicurezza, verifica questo utente controllando un codice univoco sui vostri dispositivi.", + "For maximum security, do this in person.": "Per massima sicurezza, fatelo di persona.", + "Start Verification": "Inizia la verifica" } From e98269822cb5c48bfc692e1845734a340f5094db Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 11:44:47 +0000 Subject: [PATCH 097/282] Add icon to bootstrap dialogs --- res/css/_common.scss | 8 ++++++++ .../secretstorage/CreateSecretStorageDialog.js | 10 ++++++++-- src/components/views/dialogs/BaseDialog.js | 11 +++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 51d985efb7..abc57a95ed 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -338,6 +338,14 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { margin-bottom: 10px; } +.mx_Dialog_titleImage { + vertical-align: middle; + width: 25px; + height: 25px; + margin-left: -2px; + margin-right: 4px; +} + .mx_Dialog_title { font-size: 22px; line-height: 36px; diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index d75126687c..a56a1ee905 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -155,7 +155,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _doBootstrapUIAuth = async (makeRequest) => { - if (this.state.canUploadKeysWithPasswordOnly) { + if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) { await makeRequest({ type: 'm.login.password', identifier: { @@ -664,7 +664,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_DONE: return _t('Encryption upgraded'); default: - return null; + return ''; } } @@ -719,10 +719,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } + let headerImage; + if (this._titleForPhase(this.state.phase)) { + headerImage = require("../../../../../res/img/e2e/normal.svg"); + } + return (
diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 19f22a15ad..9238024b60 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -65,6 +65,9 @@ export default createReactClass({ // Title for the dialog. title: PropTypes.node.isRequired, + // Path to an icon to put in the header + headerImage: PropTypes.string, + // children should be the content of the dialog children: PropTypes.node, @@ -110,6 +113,13 @@ export default createReactClass({ ); } + let headerImage; + if (this.props.headerImage) { + headerImage = ; + } + return (
+ {headerImage} { this.props.title }
{ this.props.headerButton } From e1e53f567f93ab044f24bc195d40fa4046acd2fb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 11:56:27 +0000 Subject: [PATCH 098/282] add more tests --- .../views/rooms/SendMessageComposer.js | 3 +- .../views/rooms/SendMessageComposer-test.js | 83 +++++++++++++++++++ test/editor/mock.js | 10 +++ test/editor/model-test.js | 12 +-- 4 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 test/components/views/rooms/SendMessageComposer-test.js diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index c4970c4570..a857e40f55 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -58,7 +58,8 @@ function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { } } -function createMessageContent(model, permalinkCreator) { +// exported for tests +export function createMessageContent(model, permalinkCreator) { const isEmote = containsEmote(model); if (isEmote) { model = stripEmoteCommand(model); diff --git a/test/components/views/rooms/SendMessageComposer-test.js b/test/components/views/rooms/SendMessageComposer-test.js new file mode 100644 index 0000000000..d5a143a1fb --- /dev/null +++ b/test/components/views/rooms/SendMessageComposer-test.js @@ -0,0 +1,83 @@ +/* +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 RoomViewStore from "../../../../src/stores/RoomViewStore"; +import {createMessageContent} from "../../../../src/components/views/rooms/SendMessageComposer"; +import EditorModel from "../../../../src/editor/model"; +import {createPartCreator, createRenderer} from "../../../editor/mock"; + +jest.mock("../../../../src/stores/RoomViewStore"); + +describe('', () => { + describe("createMessageContent", () => { + RoomViewStore.getQuotingEvent.mockReturnValue(false); + const permalinkCreator = jest.fn(); + + it("sends plaintext messages correctly", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("hello world", "insertText", {offset: 11, atNodeEnd: true}); + + const content = createMessageContent(model, permalinkCreator); + + expect(content).toEqual({ + body: "hello world", + msgtype: "m.text", + }); + }); + + it("sends markdown messages correctly", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("hello *world*", "insertText", {offset: 13, atNodeEnd: true}); + + const content = createMessageContent(model, permalinkCreator); + + expect(content).toEqual({ + body: "hello *world*", + msgtype: "m.text", + format: "org.matrix.custom.html", + formatted_body: "hello world", + }); + }); + + it("strips /me from messages and marks them as m.emote accordingly", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("/me blinks __quickly__", "insertText", {offset: 22, atNodeEnd: true}); + + const content = createMessageContent(model, permalinkCreator); + + expect(content).toEqual({ + body: "blinks __quickly__", + msgtype: "m.emote", + format: "org.matrix.custom.html", + formatted_body: "blinks quickly", + }); + }); + + it("allows sending double-slash escaped slash commands correctly", () => { + const model = new EditorModel([], createPartCreator(), createRenderer()); + model.update("//dev/null is my favourite place", "insertText", {offset: 32, atNodeEnd: true}); + + const content = createMessageContent(model, permalinkCreator); + + expect(content).toEqual({ + body: "/dev/null is my favourite place", + msgtype: "m.text", + }); + }); + }); +}); + + diff --git a/test/editor/mock.js b/test/editor/mock.js index bb1a51d14b..6de65cf23d 100644 --- a/test/editor/mock.js +++ b/test/editor/mock.js @@ -67,3 +67,13 @@ export function createPartCreator(completions = []) { }; return new PartCreator(new MockRoom(), new MockClient(), autoCompleteCreator); } + +export function createRenderer() { + const render = (c) => { + render.caret = c; + render.count += 1; + }; + render.count = 0; + render.caret = null; + return render; +} diff --git a/test/editor/model-test.js b/test/editor/model-test.js index 826dde3d68..2a3584d508 100644 --- a/test/editor/model-test.js +++ b/test/editor/model-test.js @@ -15,17 +15,7 @@ limitations under the License. */ import EditorModel from "../../src/editor/model"; -import {createPartCreator} from "./mock"; - -function createRenderer() { - const render = (c) => { - render.caret = c; - render.count += 1; - }; - render.count = 0; - render.caret = null; - return render; -} +import {createPartCreator, createRenderer} from "./mock"; describe('editor/model', function() { describe('plain text manipulation', function() { From 832da062cccc814fe5748e60a9c71f03290a6eff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 13:37:27 +0000 Subject: [PATCH 099/282] Improve trailing spurious breaks + tests --- src/editor/deserialize.js | 2 +- src/editor/operations.js | 16 +++++--- test/editor/operations-test.js | 67 ++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 7ba4c3eda3..190963f357 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -250,7 +250,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { } export function parsePlainTextMessage(body, partCreator, isQuotedMessage) { - const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n + const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n, collapses \r\n const parts = lines.reduce((parts, line, i) => { if (isQuotedMessage) { parts.push(partCreator.plain(QUOTE_LINE_PREFIX)); diff --git a/src/editor/operations.js b/src/editor/operations.js index 6bae60e6b8..d0115d9ca7 100644 --- a/src/editor/operations.js +++ b/src/editor/operations.js @@ -100,6 +100,10 @@ export function formatRangeAsCode(range) { replaceRangeAndExpandSelection(range, parts); } +// parts helper methods +const isBlank = part => !part.text || !/\S/.test(part.text); +const isNL = part => part.type === "newline"; + export function toggleInlineFormat(range, prefix, suffix = prefix) { const {model, parts} = range; const {partCreator} = model; @@ -113,14 +117,12 @@ export function toggleInlineFormat(range, prefix, suffix = prefix) { // - 2 newline parts in sequence // - newline part, plain(), newline part - const isBlank = part => !part.text || !/\S/.test(part.text); - const isNL = part => part.type === "newline"; - // bump startIndex onto the first non-blank after the paragraph ending if (isBlank(parts[i - 2]) && isNL(parts[i - 1]) && !isNL(parts[i]) && !isBlank(parts[i])) { startIndex = i; } + // if at a paragraph break, store the indexes of the paragraph if (isNL(parts[i - 1]) && isNL(parts[i])) { paragraphIndexes.push([startIndex, i - 1]); startIndex = i + 1; @@ -129,9 +131,11 @@ export function toggleInlineFormat(range, prefix, suffix = prefix) { startIndex = i + 1; } } - if (startIndex < parts.length) { - // TODO don't use parts.length here to clean up any trailing cruft - paragraphIndexes.push([startIndex, parts.length]); + + const lastNonEmptyPart = parts.map(isBlank).lastIndexOf(false); + // If we have not yet included the final paragraph then add it now + if (startIndex <= lastNonEmptyPart) { + paragraphIndexes.push([startIndex, lastNonEmptyPart + 1]); } // keep track of how many things we have inserted as an offset:=0 diff --git a/test/editor/operations-test.js b/test/editor/operations-test.js index 872cc78bdb..90a9812306 100644 --- a/test/editor/operations-test.js +++ b/test/editor/operations-test.js @@ -18,6 +18,8 @@ import EditorModel from "../../src/editor/model"; import {createPartCreator, createRenderer} from "./mock"; import {toggleInlineFormat} from "../../src/editor/operations"; +const SERIALIZED_NEWLINE = {"text": "\n", "type": "newline"}; + describe('editor/operations: formatting operations', () => { describe('toggleInlineFormat', () => { it('works for words', () => { @@ -93,17 +95,54 @@ describe('editor/operations: formatting operations', () => { expect(range.parts.map(p => p.text).join("")).toBe("world,\nhow"); expect(model.serializeParts()).toEqual([ {"text": "hello world,", "type": "plain"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, {"text": "how are you doing?", "type": "plain"}, ]); toggleInlineFormat(range, "**"); expect(model.serializeParts()).toEqual([ {"text": "hello **world,", "type": "plain"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, {"text": "how** are you doing?", "type": "plain"}, ]); }); + it('works for a paragraph with spurious breaks around it in selected range', () => { + const renderer = createRenderer(); + const pc = createPartCreator(); + const model = new EditorModel([ + pc.newline(), + pc.newline(), + pc.plain("hello world,"), + pc.newline(), + pc.plain("how are you doing?"), + pc.newline(), + pc.newline(), + ], pc, renderer); + + const range = model.startRange(model.positionForOffset(0, false), model.getPositionAtEnd()); // select-all + + expect(range.parts.map(p => p.text).join("")).toBe("\n\nhello world,\nhow are you doing?\n\n"); + expect(model.serializeParts()).toEqual([ + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, + {"text": "hello world,", "type": "plain"}, + SERIALIZED_NEWLINE, + {"text": "how are you doing?", "type": "plain"}, + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, + ]); + toggleInlineFormat(range, "**"); + expect(model.serializeParts()).toEqual([ + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, + {"text": "**hello world,", "type": "plain"}, + SERIALIZED_NEWLINE, + {"text": "how are you doing?**", "type": "plain"}, + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, + ]); + }); + it('works for multiple paragraph', () => { const renderer = createRenderer(); const pc = createPartCreator(); @@ -116,36 +155,34 @@ describe('editor/operations: formatting operations', () => { pc.plain("new paragraph"), ], pc, renderer); - let range = model.startRange(model.positionForOffset(0, true), - model.getPositionAtEnd()); // select-all + let range = model.startRange(model.positionForOffset(0, true), model.getPositionAtEnd()); // select-all expect(model.serializeParts()).toEqual([ {"text": "hello world,", "type": "plain"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, {"text": "how are you doing?", "type": "plain"}, - {"text": "\n", "type": "newline"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, {"text": "new paragraph", "type": "plain"}, ]); toggleInlineFormat(range, "__"); expect(model.serializeParts()).toEqual([ {"text": "__hello world,", "type": "plain"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, {"text": "how are you doing?__", "type": "plain"}, - {"text": "\n", "type": "newline"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, {"text": "__new paragraph__", "type": "plain"}, ]); - range = model.startRange(model.positionForOffset(0, true), - model.getPositionAtEnd()); // select-all + range = model.startRange(model.positionForOffset(0, true), model.getPositionAtEnd()); // select-all console.log("RANGE", range.parts); toggleInlineFormat(range, "__"); expect(model.serializeParts()).toEqual([ {"text": "hello world,", "type": "plain"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, {"text": "how are you doing?", "type": "plain"}, - {"text": "\n", "type": "newline"}, - {"text": "\n", "type": "newline"}, + SERIALIZED_NEWLINE, + SERIALIZED_NEWLINE, {"text": "new paragraph", "type": "plain"}, ]); }); From fbb65f068a6bb8d153ef65310ec52b886cfdc5ee Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 22 Jan 2020 14:07:16 +0000 Subject: [PATCH 100/282] Support admin configurable message when reporting content This adds support for an admin-configured message in config.json to be shown in the report content dialog to allow linking to community rules, etc. Fixes https://github.com/vector-im/riot-web/issues/11992 --- src/components/views/dialogs/ReportEventDialog.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ReportEventDialog.js b/src/components/views/dialogs/ReportEventDialog.js index e77bb0693b..2442bc2f95 100644 --- a/src/components/views/dialogs/ReportEventDialog.js +++ b/src/components/views/dialogs/ReportEventDialog.js @@ -20,6 +20,8 @@ import { _t } from '../../../languageHandler'; import PropTypes from "prop-types"; import {MatrixEvent} from "matrix-js-sdk"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; +import SdkConfig from '../../../SdkConfig'; +import Markdown from '../../../Markdown'; /* * A dialog for reporting an event. @@ -95,6 +97,15 @@ export default class ReportEventDialog extends PureComponent { ); } + const adminMessageMD = + SdkConfig.get().reportEvent && + SdkConfig.get().reportEvent.adminMessageMD; + let adminMessage; + if (adminMessageMD) { + const html = new Markdown(adminMessageMD).toHTML(); + adminMessage =

; + } + return ( - + {adminMessage} Date: Wed, 22 Jan 2020 14:15:17 +0000 Subject: [PATCH 101/282] Change prepublish script to prepare prepublish is deprecated (prepare also runs for git checkouts, and lib will need to be built in this case). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa2cf8bf8b..8f49eed4b5 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "typings": "./lib/index.d.ts", "matrix_src_main": "./src/index.js", "scripts": { - "prepublish": "yarn build", + "prepare": "yarn build", "i18n": "matrix-gen-i18n", "prunei18n": "matrix-prune-i18n", "diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && ./scripts/gen-i18n.js && node scripts/compare-file.js src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json", From 516dd25797f4ad5c8fb53a6471ef65cf212879a7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 14:24:10 +0000 Subject: [PATCH 102/282] fix typo in fallback codepath --- src/components/views/rooms/SendMessageComposer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index a857e40f55..6a60037036 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -187,7 +187,7 @@ export default class SendMessageComposer extends React.Component { // be extra resilient when somehow the AutocompleteWrapperModel or // CommandPartCreator fails to insert a command part, so we don't send // a command as a message - if (firstPart.text.startsWith("/") && firstPart.text.startsWith("//") && !firstPart.text.startsWith("//") + if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { return true; } From 088b1ea6285b544108cb4e122aced3449ab14082 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 15:03:48 +0000 Subject: [PATCH 103/282] Retry end-to-end tests automatically once if they fail, flakey flake --- .buildkite/pipeline.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index de61d4e5b9..747625ae6e 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -77,6 +77,10 @@ steps: image: "matrixdotorg/riotweb-ci-e2etests-env:latest" propagate-environment: true workdir: "/workdir/matrix-react-sdk" + retry: + automatic: + - exit_status: 1 # retry end-to-end tests once as Puppeteer sometimes fails + - limit: 1 - label: "🔧 Riot Tests" agents: From e3a28e3e449a451a85710de272d32d26b47fab56 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 15:05:25 +0000 Subject: [PATCH 104/282] Remove the react-sdk version I'm not sure if there was ever a point where this did work and we had 'dist' and 'gitHead' properties in our package.json but I can't find any trace of them now and I'm sick of this just being there syaing '' all the time. --- .../views/settings/tabs/user/HelpUserSettingsTab.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index ab71de86b9..99b94d47f2 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -26,10 +26,6 @@ import Modal from "../../../../../Modal"; import * as sdk from "../../../../../"; import PlatformPeg from "../../../../../PlatformPeg"; -// if this looks like a release, use the 'version' from package.json; else use -// the git sha. Prepend version with v, to look like riot-web version -const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || ''; - // Simple method to help prettify GH Release Tags and Commit Hashes. const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i; const ghVersionLabel = function(repo, token='') { @@ -188,9 +184,6 @@ export default class HelpUserSettingsTab extends React.Component { ); } - const reactSdkVersion = REACT_SDK_VERSION !== '' - ? ghVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION) - : REACT_SDK_VERSION; const vectorVersion = this.state.vectorVersion ? ghVersionLabel('vector-im/riot-web', this.state.vectorVersion) : 'unknown'; @@ -243,7 +236,6 @@ export default class HelpUserSettingsTab extends React.Component {

{_t("Versions")}
- {_t("matrix-react-sdk version:")} {reactSdkVersion}
{_t("riot-web version:")} {vectorVersion}
{_t("olm version:")} {olmVersion}
{updateButton} From d04ba40efe49ef31f757e823a99f44fc9650e9ff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 15:05:40 +0000 Subject: [PATCH 105/282] fix syntax --- .buildkite/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 747625ae6e..f5f63b647a 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -80,7 +80,7 @@ steps: retry: automatic: - exit_status: 1 # retry end-to-end tests once as Puppeteer sometimes fails - - limit: 1 + limit: 1 - label: "🔧 Riot Tests" agents: From 735ba4fd33d7fcdfb5e6e36141176f20f99395dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:11:54 +0100 Subject: [PATCH 106/282] EventIndex: Correctly populate events on initial fill requests. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 71e8143f0a..c96f8770ad 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -98,7 +98,7 @@ const FilePanel = createReactClass({ if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); - await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); } this.setState({ timelineSet: timelineSet }); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c081440233..93c640cf8e 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -476,6 +476,16 @@ export default class EventIndex { fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // If this is a normal fill request, not a pagination request, we need + // to get our events in the BACKWARDS direction but populate them in the + // forwards direction. + // This needs to happen because a fill request might come with an + // exisitng timeline e.g. if you close and re-open the FilePanel. + if (fromEvent === null) { + matrixEvents.reverse(); + direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; + } + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { From c04872dd9b6974e334dca1286a69c422bc8ccaf9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 15:12:38 +0000 Subject: [PATCH 107/282] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4daf7cd29e..d19cbb9bfd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -687,7 +687,6 @@ "Clear cache and reload": "Clear cache and reload", "FAQ": "FAQ", "Versions": "Versions", - "matrix-react-sdk version:": "matrix-react-sdk version:", "riot-web version:": "riot-web version:", "olm version:": "olm version:", "Homeserver is": "Homeserver is", From 7e52eb9f65374ffcb0bc9cba99ea8c3d16ecd51b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 15:16:41 +0000 Subject: [PATCH 108/282] Unused import --- src/components/views/settings/tabs/user/HelpUserSettingsTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 99b94d47f2..a245c7c7b9 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -21,7 +21,6 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import AccessibleButton from "../../../elements/AccessibleButton"; import SdkConfig from "../../../../../SdkConfig"; import createRoom from "../../../../../createRoom"; -import packageJson from "../../../../../../package.json"; import Modal from "../../../../../Modal"; import * as sdk from "../../../../../"; import PlatformPeg from "../../../../../PlatformPeg"; From f917c2faea6fabd118bbf08251b9e341c793c740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:21:11 +0100 Subject: [PATCH 109/282] FilePanel: Listen for live events and add them to an open FilePanel. --- src/components/structures/FilePanel.js | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index c96f8770ad..a25f8babd1 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,7 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + decryptingEvents: new Set(), propTypes: { roomId: PropTypes.string.isRequired, @@ -41,8 +42,64 @@ const FilePanel = createReactClass({ }; }, + onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + if (room.roomId !== this.props.roomId) return; + if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; + + if (ev.isBeingDecrypted()) { + this.decryptingEvents.add(ev.getId()); + } else { + this.addEncryptedLiveEvent(ev); + } + }, + + onEventDecrypted (ev, err) { + if (ev.getRoomId() !== this.props.roomId) return; + const eventId = ev.getId(); + + if (!this.decryptingEvents.delete(eventId)) return; + if (err) return; + + this.addEncryptedLiveEvent(ev); + }, + + addEncryptedLiveEvent(ev, toStartOfTimeline) { + if (!this.state.timelineSet) return; + + const timeline = this.state.timelineSet.getLiveTimeline(); + if (ev.getType() !== "m.room.message") return; + if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) { + return; + } + + if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { + this.state.timelineSet.addEventToTimeline(ev, timeline, false); + } + }, + async componentDidMount() { + const client = MatrixClientPeg.get(); + await this.updateTimelineSet(this.props.roomId); + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.on('Room.timeline', this.onRoomTimeline.bind(this)); + client.on('Event.decrypted', this.onEventDecrypted.bind(this)); + } + }, + + componentWillUnmount() { + const client = MatrixClientPeg.get(); + if (client === null) return; + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.removeListener('Room.timeline', this.onRoomTimeline.bind(this)); + client.removeListener('Event.decrypted', this.onEventDecrypted.bind(this)); + } }, async fetchFileEventsServer(room) { From c5e8753b0535d898961e4e8a0d3d469a86d39fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:26:40 +0100 Subject: [PATCH 110/282] FilePanel: Don't import the whole of the js-sdk. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index a25f8babd1..7ee86436a5 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -19,7 +19,7 @@ import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import Matrix from 'matrix-js-sdk'; +import {Filter} from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; @@ -105,7 +105,7 @@ const FilePanel = createReactClass({ async fetchFileEventsServer(room) { const client = MatrixClientPeg.get(); - const filter = new Matrix.Filter(client.credentials.userId); + const filter = new Filter(client.credentials.userId); filter.setDefinition( { "room": { From c3418df9197ba46b9275f22e600997ae089f26f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:31:49 +0100 Subject: [PATCH 111/282] FilePanel: Remove whitespace before two function definitions. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 7ee86436a5..e03c587e61 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -42,7 +42,7 @@ const FilePanel = createReactClass({ }; }, - onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline(ev, room, toStartOfTimeline, removed, data) { if (room.roomId !== this.props.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; @@ -53,7 +53,7 @@ const FilePanel = createReactClass({ } }, - onEventDecrypted (ev, err) { + onEventDecrypted(ev, err) { if (ev.getRoomId() !== this.props.roomId) return; const eventId = ev.getId(); From 33b5d42c0687185ca02c1661e22e4d49dfcdb867 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 15:34:17 +0000 Subject: [PATCH 112/282] Be consistent about our settings svg, free the other one --- res/css/structures/_GroupView.scss | 2 +- res/img/icons-settings-room.svg | 6 ------ src/components/views/context_menus/RoomTileContextMenu.js | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 res/img/icons-settings-room.svg diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 4ec53a3c9a..517b8b1922 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -63,7 +63,7 @@ limitations under the License. } .mx_GroupHeader_editButton::before { - mask-image: url('$(res)/img/icons-settings-room.svg'); + mask-image: url('$(res)/img/feather-customised/settings.svg'); } .mx_GroupHeader_shareButton::before { diff --git a/res/img/icons-settings-room.svg b/res/img/icons-settings-room.svg deleted file mode 100644 index 421eefdefa..0000000000 --- a/res/img/icons-settings-room.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index 6e2bd8ebf5..2d8dec29c7 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -306,7 +306,7 @@ export default createReactClass({ return (
- + { _t('Settings') }
From 9706114bb571a903fd607838229717fe767fe465 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 16:54:31 +0000 Subject: [PATCH 113/282] move E2E_STATE to E2EIcon to simplify imports --- src/components/views/rooms/E2EIcon.js | 18 +++++++++++++----- src/components/views/rooms/EventTile.js | 8 +------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 545d1fd7ed..6ee20023ff 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -14,22 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import SettingsStore from '../../../settings/SettingsStore'; +export const E2E_STATE = { + VERIFIED: "verified", + WARNING: "warning", + UNKNOWN: "unknown", + NORMAL: "normal", +}; + export default function(props) { - const { isUser } = props; - const isNormal = props.status === "normal"; - const isWarning = props.status === "warning"; - const isVerified = props.status === "verified"; + const { isUser, status, className } = props; + const isNormal = status === E2E_STATE.NORMAL; + const isWarning = status === E2E_STATE.WARNING; + const isVerified = status === E2E_STATE.VERIFIED; const e2eIconClasses = classNames({ mx_E2EIcon: true, mx_E2EIcon_warning: isWarning, mx_E2EIcon_normal: isNormal, mx_E2EIcon_verified: isVerified, - }, props.className); + }, className); let e2eTitle; const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 634b77c9e1..940515f02e 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -33,6 +33,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {ALL_RULE_TYPES} from "../../../mjolnir/BanList"; import * as ObjectUtils from "../../../ObjectUtils"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {E2E_STATE} from "./E2EIcon"; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -66,13 +67,6 @@ const stateEventTileTypes = { 'm.room.related_groups': 'messages.TextualEvent', }; -const E2E_STATE = { - VERIFIED: "verified", - WARNING: "warning", - UNKNOWN: "unknown", - NORMAL: "normal", -}; - // Add all the Mjolnir stuff to the renderer for (const evType of ALL_RULE_TYPES) { stateEventTileTypes[evType] = 'messages.TextualEvent'; From b7d1c17ad1453c237e99e3bd87d1fdb722bb01d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 16:56:27 +0000 Subject: [PATCH 114/282] simple optimization to bail out of check on first failure --- src/components/structures/RoomView.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b02f6d503..23d3002faa 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -812,10 +812,10 @@ export default createReactClass({ /* Check all verified user devices. */ for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); - const allDevicesVerified = devices.every(({deviceId}) => { - return cli.checkDeviceTrust(userId, deviceId).isVerified(); + const anyDeviceNotVerified = devices.some(({deviceId}) => { + return !cli.checkDeviceTrust(userId, deviceId).isVerified(); }); - if (!allDevicesVerified) { + if (anyDeviceNotVerified) { this.setState({ e2eStatus: "warning", }); From 78654cc69308641142b355ad601a7fd071ed8b8f Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 18:08:39 +0000 Subject: [PATCH 115/282] Updated visuals for cross-signing bootstrap * Use Fields rather than plain inputs * Update padding & alignment to match designs * Add Skip buttons * Update copy as per designs Part of https://github.com/vector-im/riot-web/issues/11902 Based on https://github.com/matrix-org/matrix-react-sdk/pull/3897 --- .../_CreateSecretStorageDialog.scss | 20 ++--- .../CreateSecretStorageDialog.js | 88 ++++++++++--------- src/i18n/strings/en_EN.json | 18 ++-- 3 files changed, 65 insertions(+), 61 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 5899abdf73..ed5aaa05a3 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.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. @@ -22,7 +22,7 @@ limitations under the License. .mx_CreateSecretStorageDialog_primaryContainer { /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ - padding: 20px; + padding-top: 20px; } .mx_CreateSecretStorageDialog_primaryContainer::after { @@ -36,9 +36,13 @@ limitations under the License. align-items: flex-start; } +.mx_Field.mx_CreateSecretStorageDialog_passPhraseField { + margin-top: 0px; +} + .mx_CreateSecretStorageDialog_passPhraseHelp { flex: 1; - height: 85px; + height: 64px; margin-left: 20px; font-size: 80%; } @@ -47,16 +51,8 @@ limitations under the License. width: 100%; } -.mx_CreateSecretStorageDialog_passPhraseInput { - flex: none; - width: 250px; - border: 1px solid $accent-color; - border-radius: 5px; - padding: 10px; - margin-bottom: 1em; -} - .mx_CreateSecretStorageDialog_passPhraseMatch { + width: 200px; margin-left: 20px; } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index a56a1ee905..e3516d1245 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -405,6 +405,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _renderPhasePassPhrase() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + const Field = sdk.getComponent('views.elements.Field'); let strengthMeter; let helpText; @@ -430,39 +431,39 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_t( - "Warning: You should only set up secret storage from a trusted computer.", {}, - { b: sub => {sub} }, + "Set up encryption on this device to allow it to verify other devices, " + + "granting them access to encrypted messages and marking them as trusted for other users.", )}

{_t( - "We'll use secret storage to optionally store an encrypted copy of " + - "your cross-signing identity for verifying other devices and message " + - "keys on our server. Protect your access to encrypted messages with a " + - "passphrase to keep it secure.", + "Secure your encryption keys with a passphrase. For maximum security " + + "this should be different to your account password:" )}

-

{_t("For maximum security, this should be different from your account password.")}

-
-
- -
- {strengthMeter} - {helpText} -
+
+ +
+ {strengthMeter} + {helpText}
- + > + +
{_t("Advanced")} @@ -475,6 +476,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _renderPhasePassPhraseConfirm() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const Field = sdk.getComponent('views.elements.Field'); let matchText; if (this.state.passPhraseConfirm === this.state.passPhrase) { @@ -492,7 +494,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let passPhraseMatch = null; if (matchText) { - passPhraseMatch =
+ passPhraseMatch =
{matchText}
@@ -504,28 +506,32 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

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

-
-
-
- -
+
+ +
{passPhraseMatch}
- + > + +
; } @@ -650,9 +656,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_MIGRATE: return _t('Upgrade your encryption'); case PHASE_PASSPHRASE: - return _t('Secure your encrypted messages with a passphrase'); + return _t('Set up encryption'); case PHASE_PASSPHRASE_CONFIRM: - return _t('Confirm your passphrase'); + return _t('Confirm passphrase'); case PHASE_OPTOUT_CONFIRM: return _t('Warning!'); case PHASE_SHOWKEY: diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 29ce36a814..a49a8269df 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1976,16 +1976,15 @@ "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 device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.", "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", - "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", - "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", - "Enter a passphrase...": "Enter a passphrase...", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:", + "Enter a passphrase": "Enter a passphrase", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "That doesn't match.": "That doesn't match.", "Go back to set it again.": "Go back to set it again.", - "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", - "Repeat your passphrase...": "Repeat your passphrase...", + "Enter your passphrase a second time to confirm it.": "Enter your passphrase a second time to confirm it.", + "Confirm your passphrase": "Confirm your passphrase", "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.", "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.", "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", @@ -2004,8 +2003,7 @@ "Set up secret storage": "Set up secret storage", "Restore your Key Backup": "Restore your Key Backup", "Upgrade your encryption": "Upgrade your encryption", - "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase", - "Confirm your passphrase": "Confirm your passphrase", + "Set up encryption": "Set up encryption", "Recovery key": "Recovery key", "Keep it safe": "Keep it safe", "Storing secrets...": "Storing secrets...", @@ -2013,7 +2011,11 @@ "Unable to set up secret storage": "Unable to set up secret storage", "Retry": "Retry", "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Enter a passphrase...": "Enter a passphrase...", "Set up with a Recovery Key": "Set up with a Recovery Key", + "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", + "Repeat your passphrase...": "Repeat your passphrase...", "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.", "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.", "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).", From 30b0663eb0aa96974e84c89365e4dd8340d66e28 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 22 Jan 2020 18:15:35 +0000 Subject: [PATCH 116/282] lint --- .../views/dialogs/secretstorage/CreateSecretStorageDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index e3516d1245..932f6e30e4 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -436,7 +436,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { )}

{_t( "Secure your encryption keys with a passphrase. For maximum security " + - "this should be different to your account password:" + "this should be different to your account password:", )}

From cf5673be17de09afecc33450fb5b769ed967ab0d Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 22 Jan 2020 16:04:17 +0000 Subject: [PATCH 117/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (2042 of 2042 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index f6c85e1674..fa7528a397 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2081,5 +2081,10 @@ "For extra security, verify this user by checking a one-time code on both of your devices.": "A biztonság fokozásáért ellenőrizd ezt a felhasználót egy egyszeri kód egyeztetésével mindkettőtök készülékén.", "For maximum security, do this in person.": "A legnagyobb biztonság érdekében ezt személyesen tedd meg.", "Start Verification": "Ellenőrzés elindítása", - "Encrypted by a deleted device": "Egy már törölt eszköz titkosította" + "Encrypted by a deleted device": "Egy már törölt eszköz titkosította", + "Unknown Command": "Ismeretlen Parancs", + "Unrecognised command: %(commandText)s": "Ismeretlen parancs: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Használhatod a /help-et az elérhető parancsok kilistázásához. Ezt üzenetként akartad küldeni?", + "Hint: Begin your message with // to start it with a slash.": "Tipp: Ez üzenetedet kezd ezzel: //, ha perjellel szeretnéd kezdeni.", + "Send as message": "Üzenet küldése" } From 78ed8019d49426ee0c80406cc281f256a7b93cf1 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 22 Jan 2020 18:29:10 +0000 Subject: [PATCH 118/282] Translated using Weblate (Turkish) Currently translated at 72.6% (1482 of 2042 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 46 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 5d83ff5b06..21ccf03a46 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1452,5 +1452,49 @@ "Please provide a room alias": "Lütfen bir oda lakabı belirtin", "This alias is available to use": "Bu lakap kullanmaya uygun", "This alias is already in use": "Bu lakap zaten kullanımda", - "And %(count)s more...|other": "ve %(count)s kez daha..." + "And %(count)s more...|other": "ve %(count)s kez daha...", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatif olarak,turn.matrix.org adresindeki herkese açık sunucuyu kullanmayı deneyebilirsiniz. Fakat bu güvenilir olmayabilir. IP adresiniz bu sunucu ile paylaşılacaktır. Ayarlardan yönetebilirsiniz.", + "An error ocurred whilst trying to remove the widget from the room": "Görsel bileşen odadan silinmeye çalışılırken bir hata oluştu", + "Minimize apps": "Uygulamaları küçült", + "Maximize apps": "Uygulamaları büyült", + "Popout widget": "Görsel bileşeni göster", + "Please create a new issue on GitHub so that we can investigate this bug.": "Lütfen GitHub’da Yeni bir talep oluşturun ki bu hatayı inceleyebilelim.", + "Rotate counter-clockwise": "Saat yönünün tersine döndür", + "Language Dropdown": "Dil Listesi", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s, %(count)s kez ayrıldı", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)s %(count)s kez ayrıldı", + "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)s %(count)s kez katılıp ve ayrıldı", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s %(count)s kez katıldı ve ayrıldı", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)s ayrıldı ve yeniden katıldı", + "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)s %(count)s kez davetlerini reddetti", + "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)s davetlerini reddetti", + "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)s davetlerini reddetti", + "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)s davetlerini geri çekti", + "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)s davetini %(count)s kez geri çekti", + "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)s davetini geri çekti", + "were banned %(count)s times|other": "%(count)s kez yasaklandı", + "were banned %(count)s times|one": "yasaklandı", + "was banned %(count)s times|other": "%(count)s kez yasaklandı", + "was banned %(count)s times|one": "yasaklandı", + "were unbanned %(count)s times|other": "%(count)s kez yasak kaldırıldı", + "were unbanned %(count)s times|one": "yasak kaldırıldı", + "was unbanned %(count)s times|other": "%(count)s kez yasak kaldırıldı", + "was unbanned %(count)s times|one": "yasak kaldırıldı", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)s resimlerini değiştirdiler", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s %(count)s kez resmini değiştirdi", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s resmini değiştirdi", + "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)s %(count)s kez hiç bir değişiklik yapmadı", + "Try using one of the following valid address types: %(validTypesList)s.": "Takip eden geçerli adres tiplerinden birini kullanmayı deneyin: %(validTypesList)s.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "E-posta ile davet etmek için bir kimlik sunucusu kullan. Varsayılanı kullan (%(defaultIdentityServerName)s ya da Ayarlar kullanarak yönetin.", + "Use an identity server to invite by email. Manage in Settings.": "E-posta ile davet için bir kimlik sunucu kullan. Ayarlar dan yönet.", + "The following users may not exist": "Belirtilen kullanıcılar mevcut olmayabilir", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Altta belirtilen Matrix ID li profiller bulunamıyor - Onları yinede davet etmek ister misiniz?", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Lütfen neyin yanlış gittiğini bize bildirin ya da en güzeli problemi tanımlayan bir GitHub talebi oluşturun.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Logları göndermeden önce, probleminizi betimleyen bir GitHub talebi oluşturun.", + "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Topluluk ID leri sadece a-z, 0-9 ya da '=_-./' karakterlerini içerebilir", + "Set a room alias to easily share your room with other people.": "Odanızı diğer kişilerle kolayca paylaşabilmek için bir oda lakabı ayarların.", + "Create a public room": "Halka açık bir oda oluşturun", + "Make this room public": "Bu odayı halka açık yap", + "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 Riot to do this": "Sohbet tarihçesini kaybetmemek için, çıkmadan önce odanızın anahtarlarını dışarıya aktarın. Bunu yapabilmek için Riotun daha yeni sürümü gerekli. Ulaşmak için geri gitmeye ihtiyacınız var", + "Continue With Encryption Disabled": "Şifreleme Kapalı Şekilde Devam Et" } From a504faa2f6f80fa879d46e10009c45e4474aa257 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 22 Jan 2020 22:08:34 +0000 Subject: [PATCH 119/282] Treat links as external in report content admin message This marks all the links in the report content admin message (in Markdown format) as external so they open in a new tab. --- src/Markdown.js | 20 ++++++++++++++++++- .../views/dialogs/ReportEventDialog.js | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index acfea52100..437ceec88b 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -91,7 +91,7 @@ export default class Markdown { return true; } - toHTML() { + toHTML({ externalLinks = false } = {}) { const renderer = new commonmark.HtmlRenderer({ safe: false, @@ -125,6 +125,24 @@ export default class Markdown { } }; + renderer.link = function(node, entering) { + const attrs = this.attrs(node); + if (entering) { + attrs.push(['href', this.esc(node.destination)]); + if (node.title) { + attrs.push(['title', this.esc(node.title)]); + } + // Modified link behaviour to treat them all as external and + // thus opening in a new tab. + if (externalLinks) { + attrs.push(['target', '_blank']); + attrs.push(['rel', 'noopener']); + } + this.tag('a', attrs); + } else { + this.tag('/a'); + } + }; renderer.html_inline = html_if_tag_allowed; diff --git a/src/components/views/dialogs/ReportEventDialog.js b/src/components/views/dialogs/ReportEventDialog.js index 2442bc2f95..99853582dd 100644 --- a/src/components/views/dialogs/ReportEventDialog.js +++ b/src/components/views/dialogs/ReportEventDialog.js @@ -102,7 +102,7 @@ export default class ReportEventDialog extends PureComponent { SdkConfig.get().reportEvent.adminMessageMD; let adminMessage; if (adminMessageMD) { - const html = new Markdown(adminMessageMD).toHTML(); + const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true }); adminMessage =

; } From 054ebb9458ec5fe79d0ec958d8daef2ac919c26d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 21:07:29 -0700 Subject: [PATCH 120/282] Fix scrollable area and padding in user lists dialog --- res/css/views/dialogs/_InviteDialog.scss | 15 +++++++++++++++ src/components/views/dialogs/InviteDialog.js | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index 221ad7d48c..71fab50339 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -210,4 +210,19 @@ limitations under the License. .mx_InviteDialog { // Prevent the dialog from jumping around randomly when elements change. height: 590px; + padding-left: 20px; // the design wants some padding on the left +} + +.mx_InviteDialog_userSections { + margin-top: 10px; + overflow-y: auto; + padding-right: 45px; + height: 455px; // mx_InviteDialog's height minus some for the upper elements +} + +// Right margin for the design. We could apply this to the whole dialog, but then the scrollbar +// for the user section gets weird. +.mx_InviteDialog_helpText, +.mx_InviteDialog_addressBar { + margin-right: 45px; } diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 703b0b5121..cb4f123cea 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -970,7 +970,7 @@ export default class InviteDialog extends React.PureComponent { title={title} >

-

{helpText}

+

{helpText}

{this._renderEditor()}
@@ -987,8 +987,10 @@ export default class InviteDialog extends React.PureComponent {
{this._renderIdentityServerWarning()}
{this.state.errorText}
- {this._renderSection('recents')} - {this._renderSection('suggestions')} +
+ {this._renderSection('recents')} + {this._renderSection('suggestions')} +
); From 139b5663fe4015934d3833bdfe0cdf9b4e756ada Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 21:14:53 -0700 Subject: [PATCH 121/282] Remove user lists feature flag, making it the default Fixes https://github.com/vector-im/riot-web/issues/11201 --- src/RoomInvite.js | 156 +++------------------------------------ src/settings/Settings.js | 6 -- 2 files changed, 12 insertions(+), 150 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 2eccf69b0f..839d677069 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -20,13 +20,8 @@ import React from 'react'; import {MatrixClientPeg} from './MatrixClientPeg'; import MultiInviter from './utils/MultiInviter'; import Modal from './Modal'; -import { getAddressType } from './UserAddress'; -import createRoom from './createRoom'; import * as sdk from './'; -import dis from './dispatcher'; -import DMRoomMap from './utils/DMRoomMap'; import { _t } from './languageHandler'; -import SettingsStore from "./settings/SettingsStore"; import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog"; /** @@ -44,64 +39,21 @@ export function inviteMultipleToRoom(roomId, addrs) { } export function showStartChatInviteDialog() { - if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) { - // This new dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); - Modal.createTrackedDialog( - 'Start DM', '', InviteDialog, {kind: KIND_DM}, - /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, - ); - return; - } - - const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); - - Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, { - title: _t('Start a chat'), - description: _t("Who would you like to communicate with?"), - placeholder: (validAddressTypes) => { - // The set of valid address type can be mutated inside the dialog - // when you first have no IS but agree to use one in the dialog. - if (validAddressTypes.includes('email')) { - return _t("Email, name or Matrix ID"); - } - return _t("Name or Matrix ID"); - }, - validAddressTypes: ['mx-user-id', 'email'], - button: _t("Start Chat"), - onFinished: _onStartDmFinished, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); + // This dialog handles the room creation internally - we don't need to worry about it. + const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); + Modal.createTrackedDialog( + 'Start DM', '', InviteDialog, {kind: KIND_DM}, + /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, + ); } export function showRoomInviteDialog(roomId) { - if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) { - // This new dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); - Modal.createTrackedDialog( - 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, - /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, - ); - return; - } - - const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); - - Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { - title: _t('Invite new room members'), - button: _t('Send Invites'), - placeholder: (validAddressTypes) => { - // The set of valid address type can be mutated inside the dialog - // when you first have no IS but agree to use one in the dialog. - if (validAddressTypes.includes('email')) { - return _t("Email, name or Matrix ID"); - } - return _t("Name or Matrix ID"); - }, - validAddressTypes: ['mx-user-id', 'email'], - onFinished: (shouldInvite, addrs) => { - _onRoomInviteFinished(roomId, shouldInvite, addrs); - }, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); + // This dialog handles the room creation internally - we don't need to worry about it. + const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); + Modal.createTrackedDialog( + 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, + /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, + ); } /** @@ -122,60 +74,6 @@ export function isValid3pidInvite(event) { return true; } -// TODO: Canonical DMs replaces this -function _onStartDmFinished(shouldInvite, addrs) { - if (!shouldInvite) return; - - const addrTexts = addrs.map((addr) => addr.address); - - if (_isDmChat(addrTexts)) { - const rooms = _getDirectMessageRooms(addrTexts[0]); - if (rooms.length > 0) { - // A Direct Message room already exists for this user, so reuse it - dis.dispatch({ - action: 'view_room', - room_id: rooms[0], - should_peek: false, - joining: false, - }); - } else { - // Start a new DM chat - createRoom({dmUserId: addrTexts[0]}).catch((err) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to start chat', '', ErrorDialog, { - title: _t("Failed to start chat"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } - } else if (addrTexts.length === 1) { - // Start a new DM chat - createRoom({dmUserId: addrTexts[0]}).catch((err) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to start chat', '', ErrorDialog, { - title: _t("Failed to start chat"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } else { - // Start multi user chat - let room; - createRoom().then((roomId) => { - room = MatrixClientPeg.get().getRoom(roomId); - return inviteMultipleToRoom(roomId, addrTexts); - }).then((result) => { - return _showAnyInviteErrors(result.states, room, result.inviter); - }).catch((err) => { - console.error(err.stack); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t("Failed to invite"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - }); - } -} - export function inviteUsersToRoom(roomId, userIds) { return inviteMultipleToRoom(roomId, userIds).then((result) => { const room = MatrixClientPeg.get().getRoom(roomId); @@ -190,24 +88,6 @@ export function inviteUsersToRoom(roomId, userIds) { }); } -function _onRoomInviteFinished(roomId, shouldInvite, addrs) { - if (!shouldInvite) return; - - const addrTexts = addrs.map((addr) => addr.address); - - // Invite new users to a room - inviteUsersToRoom(roomId, addrTexts); -} - -// TODO: Immutable DMs replaces this -function _isDmChat(addrTexts) { - if (addrTexts.length === 1 && getAddressType(addrTexts[0]) === 'mx-user-id') { - return true; - } else { - return false; - } -} - function _showAnyInviteErrors(addrs, room, inviter) { // Show user any errors const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error'); @@ -243,15 +123,3 @@ function _showAnyInviteErrors(addrs, room, inviter) { return addrs; } - -function _getDirectMessageRooms(addr) { - const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); - const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); - const rooms = dmRooms.filter((dmRoom) => { - const room = MatrixClientPeg.get().getRoom(dmRoom); - if (room) { - return room.getMyMembership() === 'join'; - } - }); - return rooms; -} diff --git a/src/settings/Settings.js b/src/settings/Settings.js index eacf63e55d..e908861821 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -128,12 +128,6 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_ftue_dms": { - isFeature: true, - displayName: _td("New invite dialog"), - supportedLevels: LEVELS_FEATURE, - default: false, - }, "feature_presence_in_room_list": { isFeature: true, displayName: _td("Show a presence dot next to DMs in the room list"), From a196ecc8911fc951ac18ab6ed28c2a7426f7bc99 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 21:17:40 -0700 Subject: [PATCH 122/282] Fix i18n --- src/i18n/strings/en_EN.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d19cbb9bfd..6a68c70d56 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -121,15 +121,8 @@ "Moderator": "Moderator", "Admin": "Admin", "Custom (%(level)s)": "Custom (%(level)s)", - "Start a chat": "Start a chat", - "Who would you like to communicate with?": "Who would you like to communicate with?", - "Email, name or Matrix ID": "Email, name or Matrix ID", - "Start Chat": "Start Chat", - "Invite new room members": "Invite new room members", - "Send Invites": "Send Invites", - "Failed to start chat": "Failed to start chat", - "Operation failed": "Operation failed", "Failed to invite": "Failed to invite", + "Operation failed": "Operation failed", "Failed to invite users to the room:": "Failed to invite users to the room:", "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", "You need to be logged in.": "You need to be logged in.", @@ -372,7 +365,6 @@ "Render simple counters in room header": "Render simple counters in room header", "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "New invite dialog": "New invite dialog", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", @@ -943,6 +935,7 @@ "Invite": "Invite", "Share Link to User": "Share Link to User", "User Options": "User Options", + "Start a chat": "Start a chat", "Direct chats": "Direct chats", "Remove recent messages": "Remove recent messages", "Unmute": "Unmute", From e41f94bdb79273fda94fafe8fdd7c6556e488707 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 22:33:50 -0700 Subject: [PATCH 123/282] Try to populate user IDs even when the server's directory fails us Fixes https://github.com/vector-im/riot-web/issues/11943 --- src/components/views/dialogs/InviteDialog.js | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 703b0b5121..effb887fc9 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -586,13 +586,36 @@ export default class InviteDialog extends React.PureComponent { clearTimeout(this._debounceTimer); } this._debounceTimer = setTimeout(async () => { - MatrixClientPeg.get().searchUserDirectory({term}).then(r => { + MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { if (term !== this.state.filterText) { // Discard the results - we were probably too slow on the server-side to make // these results useful. This is a race we want to avoid because we could overwrite // more accurate results. return; } + + if (!r.results) r.results = []; + + // While we're here, try and autocomplete a search result for the mxid itself + // if there's no matches (and the input looks like a mxid). + if (term[0] === '@' && term.indexOf(':') > 1 && r.results.length === 0) { + try { + const profile = await MatrixClientPeg.get().getProfileInfo(term); + if (profile) { + // If we have a profile, we have enough information to assume that + // the mxid can be invited - add it to the list + r.results.push({ + user_id: term, + display_name: profile['displayname'], + avatar_url: profile['avatar_url'], + }); + } + } catch (e) { + console.warn("Non-fatal error trying to make an invite for a user ID"); + console.warn(e); + } + } + this.setState({ serverResultsMixin: r.results.map(u => ({ userId: u.user_id, From 65450e893c9862ef2ef7cdaeb22267bd19f3d731 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 22:40:02 -0700 Subject: [PATCH 124/282] Try and fix e2e tests for new invite dialog --- test/end-to-end-tests/src/usecases/invite.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/invite.js b/test/end-to-end-tests/src/usecases/invite.js index 6bee5dfd6f..75ebc61a88 100644 --- a/test/end-to-end-tests/src/usecases/invite.js +++ b/test/end-to-end-tests/src/usecases/invite.js @@ -31,10 +31,11 @@ module.exports = async function invite(session, userId) { } const inviteButton = await session.query(".mx_MemberList_invite"); await inviteButton.click(); - const inviteTextArea = await session.query(".mx_AddressPickerDialog textarea"); + const inviteTextArea = await session.query(".mx_InviteDialog_editor textarea"); await inviteTextArea.type(userId); - await inviteTextArea.press("Enter"); - const confirmButton = await session.query(".mx_Dialog_primary"); + const selectUserItem = await session.query(".mx_InviteDialog_roomTile"); + await selectUserItem.click(); + const confirmButton = await session.query(".mx_InviteDialog_goButton"); await confirmButton.click(); session.log.done(); }; From 4627e3b2825c5b547d1ccfe249ac74b75d5497d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 11:02:44 +0100 Subject: [PATCH 125/282] EventIndex: Refactor out the addInitialCheckpoints method. --- src/indexing/EventIndex.js | 94 +++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 435f67447d..09abb5d209 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -70,62 +70,62 @@ export default class EventIndex { client.removeListener('Room.timelineReset', this.onTimelineReset); } + /** Get crawler checkpoints for the encrypted rooms and store them in the index. + */ + async addInitialCheckpoints() { + const indexManager = PlatformPeg.get().getEventIndexingManager(); + const client = MatrixClientPeg.get(); + const rooms = client.getRooms(); + + const isRoomEncrypted = (room) => { + return client.isRoomEncrypted(room.roomId); + }; + + // We only care to crawl the encrypted rooms, non-encrypted. + // rooms can use the search provided by the homeserver. + const encryptedRooms = rooms.filter(isRoomEncrypted); + + console.log("EventIndex: Adding initial crawler checkpoints"); + + // Gather the prev_batch tokens and create checkpoints for + // our message crawler. + await Promise.all(encryptedRooms.map(async (room) => { + const timeline = room.getLiveTimeline(); + const token = timeline.getPaginationToken("b"); + + console.log("EventIndex: Got token for indexer", + room.roomId, token); + + const backCheckpoint = { + roomId: room.roomId, + token: token, + direction: "b", + }; + + const forwardCheckpoint = { + roomId: room.roomId, + token: token, + direction: "f", + }; + + await indexManager.addCrawlerCheckpoint(backCheckpoint); + await indexManager.addCrawlerCheckpoint(forwardCheckpoint); + this.crawlerCheckpoints.push(backCheckpoint); + this.crawlerCheckpoints.push(forwardCheckpoint); + })); + } + onSync = async (state, prevState, data) => { const indexManager = PlatformPeg.get().getEventIndexingManager(); if (prevState === "PREPARED" && state === "SYNCING") { - const addInitialCheckpoints = async () => { - const client = MatrixClientPeg.get(); - const rooms = client.getRooms(); - - const isRoomEncrypted = (room) => { - return client.isRoomEncrypted(room.roomId); - }; - - // We only care to crawl the encrypted rooms, non-encrypted. - // rooms can use the search provided by the homeserver. - const encryptedRooms = rooms.filter(isRoomEncrypted); - - console.log("EventIndex: Adding initial crawler checkpoints"); - - // Gather the prev_batch tokens and create checkpoints for - // our message crawler. - await Promise.all(encryptedRooms.map(async (room) => { - const timeline = room.getLiveTimeline(); - const token = timeline.getPaginationToken("b"); - - console.log("EventIndex: Got token for indexer", - room.roomId, token); - - const backCheckpoint = { - roomId: room.roomId, - token: token, - direction: "b", - }; - - const forwardCheckpoint = { - roomId: room.roomId, - token: token, - direction: "f", - }; - - await indexManager.addCrawlerCheckpoint(backCheckpoint); - await indexManager.addCrawlerCheckpoint(forwardCheckpoint); - this.crawlerCheckpoints.push(backCheckpoint); - this.crawlerCheckpoints.push(forwardCheckpoint); - })); - }; - // If our indexer is empty we're most likely running Riot the // first time with indexing support or running it with an // initial sync. Add checkpoints to crawl our encrypted rooms. const eventIndexWasEmpty = await indexManager.isEventIndexEmpty(); - if (eventIndexWasEmpty) await addInitialCheckpoints(); + if (eventIndexWasEmpty) await this.addInitialCheckpoints(); - // Start our crawler. - if (SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling')) { - this.startCrawler(); - } + this.startCrawler(); return; } From c0b713240427315475c000bc8c769f608d252043 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jan 2020 10:18:08 +0000 Subject: [PATCH 126/282] Different copy on done screen if we setup from scratch --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 7 ++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 932f6e30e4..8fd881fc32 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -75,6 +75,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { canUploadKeysWithPasswordOnly: null, accountPassword: '', accountPasswordCorrect: null, + // set if we are 'upgrading' encryption (making an SSSS store from + // an existing key backup secret). + doingUpgrade: null, }; this._fetchBackupInfo(); @@ -99,6 +102,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { phase, backupInfo, backupSigStatus, + // remember this after this phase so we can use appropriate copy + doingUpgrade: phase === PHASE_MIGRATE, }); } @@ -668,7 +673,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_STORING: return _t('Storing secrets...'); case PHASE_DONE: - return _t('Encryption upgraded'); + return this.state.doingUpgrade ? _t('Encryption upgraded') : _t('Encryption setup complete'); default: return ''; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a49a8269df..22c09b5ae6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2008,6 +2008,7 @@ "Keep it safe": "Keep it safe", "Storing secrets...": "Storing secrets...", "Encryption upgraded": "Encryption upgraded", + "Encryption setup complete": "Encryption setup complete", "Unable to set up secret storage": "Unable to set up secret storage", "Retry": "Retry", "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", From 5fd121d2afdf7b18ebff311b2221539a492fd20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 11:44:56 +0100 Subject: [PATCH 127/282] ManageEventIndex: Remove the unused stats. --- .../dialogs/eventindex/ManageEventIndex.js | 10 ------- src/indexing/EventIndex.js | 27 ------------------- 2 files changed, 37 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 8623856b2e..d9a0cdcb5d 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -41,8 +41,6 @@ export default class ManageEventIndex extends React.Component { this.state = { eventIndexSize: 0, - crawlingRooms: 0, - totalCrawlingRooms: 0, eventCount: 0, roomCount: 0, currentRoom: null, @@ -80,8 +78,6 @@ export default class ManageEventIndex extends React.Component { let eventIndexSize = 0; let roomCount = 0; let eventCount = 0; - let crawlingRooms = 0; - let totalCrawlingRooms = 0; let currentRoom = null; const eventIndex = EventIndexPeg.get(); @@ -94,18 +90,12 @@ export default class ManageEventIndex extends React.Component { roomCount = stats.roomCount; eventCount = stats.eventCount; - const crawledRooms = eventIndex.currentlyCrawledRooms(); - crawlingRooms = crawledRooms.crawlingRooms.size; - totalCrawlingRooms = crawledRooms.totalRooms.size; - const room = eventIndex.currentRoom(); if (room) currentRoom = room.name; } this.setState({ eventIndexSize, - crawlingRooms, - totalCrawlingRooms, eventCount, roomCount, currentRoom, diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 09abb5d209..a10f4aff71 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -440,33 +440,6 @@ export default class EventIndex { return indexManager.getStats(); } - currentlyCrawledRooms() { - const crawlingRooms = new Set(); - const totalRooms = new Set(); - - this.crawlerCheckpoints.forEach((checkpoint, index) => { - crawlingRooms.add(checkpoint.roomId); - }); - - if (this._currentCheckpoint !== null) { - crawlingRooms.add(this._currentCheckpoint.roomId); - } - - const client = MatrixClientPeg.get(); - const rooms = client.getRooms(); - - const isRoomEncrypted = (room) => { - return client.isRoomEncrypted(room.roomId); - }; - - const encryptedRooms = rooms.filter(isRoomEncrypted); - encryptedRooms.forEach((room, index) => { - totalRooms.add(room.roomId); - }); - - return {crawlingRooms, totalRooms}; - } - /** * Get the room that we are currently crawling. * From d68db74efe9bfb6952fe268ed53f97d9f305293c Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 17:17:40 +0000 Subject: [PATCH 128/282] Room list reflects encryption state --- res/css/views/rooms/_RoomHeader.scss | 4 +- res/css/views/rooms/_RoomTile.scss | 13 ++++ src/components/structures/RoomView.js | 1 + src/components/views/rooms/RoomTile.js | 101 +++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 0d92247735..a235a47fdd 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -22,7 +22,9 @@ limitations under the License. margin: 0; position: absolute; bottom: 0; - right: -5px; + right: -1px; + height: 10px; + width: 10px } } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index db2c09f6f1..a36d781669 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -98,6 +98,19 @@ limitations under the License. z-index: 2; } +// Note we match .mx_E2EIcon to make sure this matches more tightly than just +// .mx_E2EIcon on its own +.mx_RoomTile_e2eIcon.mx_E2EIcon { + height: 10px; + width: 10px; + display: block; + position: absolute; + bottom: -0px; + right: -1px; + z-index: 1; + margin: 0; +} + .mx_RoomTile_name { font-size: 14px; padding: 0 6px; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b02f6d503..f10c98dd98 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -796,6 +796,7 @@ export default createReactClass({ return; } + // Duplication betwen here and _updateE2eStatus in RoomTile /* At this point, the user has encryption on and cross-signing on */ const e2eMembers = await room.getEncryptionTargetMembers(); const verified = []; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index f4f5fa10fc..c277433a5c 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -33,6 +33,8 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; +import E2EIcon from './E2EIcon'; +import rate_limited_func from '../../../ratelimitedfunc'; export default createReactClass({ displayName: 'RoomTile', @@ -70,6 +72,7 @@ export default createReactClass({ notificationCount: this.props.room.getUnreadNotificationCount(), selected: this.props.room.roomId === RoomViewStore.getRoomId(), statusMessage: this._getStatusMessage(), + e2eStatus: null, }); }, @@ -102,6 +105,85 @@ export default createReactClass({ return statusUser._unstable_statusMessage; }, + onRoomStateMember: function(ev, state, member) { + // we only care about leaving users + // because trust state will change if someone joins a megolm session anyway + if (member.membership !== "leave") { + return; + } + // ignore members in other rooms + if (member.roomId !== this.props.room.roomId) { + return; + } + + this._updateE2eStatus(); + }, + + onUserVerificationChanged: function(userId, _trustStatus) { + if (!this.props.room.getMember(userId)) { + // Not in this room + return; + } + this._updateE2eStatus(); + }, + + onRoomTimeline: function(ev, room) { + if (!room) return; + if (room.roomId != this.props.room.roomId) return; + console.warn("e2e onRoomTimeline"); + if (ev.getType() !== "m.room.encryption") return; + console.warn("e2e onRoomTimeline ENCRYPTION"); + MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); + this.onFindingRoomToBeEncrypted(); + }, + + onFindingRoomToBeEncrypted: function () { + const cli = MatrixClientPeg.get(); + cli.on("RoomState.members", this.onRoomStateMember); + cli.on("userTrustStatusChanged", this.onUserVerificationChanged); + + this._updateE2eStatus(); + }, + + _updateE2eStatus: async function() { + const cli = MatrixClientPeg.get(); + if (!cli.isRoomEncrypted(this.props.room.roomId)) { + return; + } + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + return; + } + + // Duplication between here and _updateE2eStatus in RoomView + const e2eMembers = await this.props.room.getEncryptionTargetMembers(); + const verified = []; + const unverified = []; + e2eMembers.map(({userId}) => userId) + .filter((userId) => userId !== cli.getUserId()) + .forEach((userId) => { + (cli.checkUserTrust(userId).isCrossSigningVerified() ? + verified : unverified).push(userId) + }); + + /* Check all verified user devices. */ + for (const userId of verified) { + const devices = await cli.getStoredDevicesForUser(userId); + const allDevicesVerified = devices.every(({deviceId}) => { + return cli.checkDeviceTrust(userId, deviceId).isVerified(); + }); + if (!allDevicesVerified) { + this.setState({ + e2eStatus: "warning", + }); + return; + } + } + + this.setState({ + e2eStatus: unverified.length === 0 ? "verified" : "normal", + }); + }, + onRoomName: function(room) { if (room !== this.props.room) return; this.setState({ @@ -151,10 +233,19 @@ export default createReactClass({ }, componentDidMount: function() { + /* We bind here rather than in the definition because otherwise we wind up with the + method only being callable once every 500ms across all instances, which would be wrong */ + this._updateE2eStatus = rate_limited_func(this._updateE2eStatus, 500); + const cli = MatrixClientPeg.get(); cli.on("accountData", this.onAccountData); cli.on("Room.name", this.onRoomName); cli.on("RoomState.events", this.onJoinRule); + if (cli.isRoomEncrypted(this.props.room.roomId)) { + this.onFindingRoomToBeEncrypted(); + } else { + cli.on("Room.timeline", this.onRoomTimeline); + } ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); this.dispatcherRef = dis.register(this.onAction); @@ -172,6 +263,9 @@ export default createReactClass({ MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); cli.removeListener("RoomState.events", this.onJoinRule); + cli.removeListener("RoomState.members", this.onRoomStateMember); + cli.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); + cli.removeListener("Room.timeline", this.onRoomTimeline); } ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); dis.unregister(this.dispatcherRef); @@ -433,6 +527,12 @@ export default createReactClass({ privateIcon =
; } + let e2eIcon = null; + // For now, skip the icon for DMs. Possibly we want to move the DM icon elsewhere? + if (!dmUserId && this.state.e2eStatus) { + e2eIcon = + } + return {({onFocus, isActive, ref}) => @@ -453,6 +553,7 @@ export default createReactClass({
{ dmIndicator } + { e2eIcon }
{ privateIcon } From 8218962c37308c472989372e5343fb1f63e113f2 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 23 Jan 2020 03:45:50 +0000 Subject: [PATCH 129/282] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2042 of 2042 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 694d5e3d84..03fbe6f61b 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2080,5 +2080,10 @@ "For extra security, verify this user by checking a one-time code on both of your devices.": "為了提高安全性,請透過檢查您兩個裝置上的一次性代碼來驗證此使用者。", "For maximum security, do this in person.": "為了取得最強的安全性,請親自進行。", "Start Verification": "開始驗證", - "Encrypted by a deleted device": "被已刪除的裝置加密" + "Encrypted by a deleted device": "被已刪除的裝置加密", + "Unknown Command": "未知的指令", + "Unrecognised command: %(commandText)s": "無法識別的指令:%(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "您可以使用 /help 來列出可用的指令。您是要傳送此訊息嗎?", + "Hint: Begin your message with // to start it with a slash.": "提示:以 // 開頭讓您的訊息傳送時可以用斜線開頭。", + "Send as message": "以訊息傳送" } From f7ca8a1b845a1ea133a5b715b598f8c3c8323fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 23 Jan 2020 08:11:37 +0000 Subject: [PATCH 130/282] Translated using Weblate (French) Currently translated at 100.0% (2042 of 2042 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 8fda9e458e..472cd02e6f 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2080,5 +2080,10 @@ "For extra security, verify this user by checking a one-time code on both of your devices.": "Pour une meilleure sécurité, vérifiez cet utilisateur en comparant un code à usage unique sur vos deux appareils.", "For maximum security, do this in person.": "Pour une sécurité maximale, faites-le en personne.", "Start Verification": "Commencer la vérification", - "Encrypted by a deleted device": "Chiffré par un appareil supprimé" + "Encrypted by a deleted device": "Chiffré par un appareil supprimé", + "Unknown Command": "Commande inconnue", + "Unrecognised command: %(commandText)s": "Commande non reconnue : %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Vous pouvez utiliser /help pour obtenir la liste des commandes disponibles. Vouliez-vous envoyer un message ?", + "Hint: Begin your message with // to start it with a slash.": "Astuce : Votre message doit démarrer par // pour commencer par une barre oblique.", + "Send as message": "Envoyer comme message" } From 5d8249507cd685402ce2612d5b94565548022dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Thu, 23 Jan 2020 10:11:27 +0000 Subject: [PATCH 131/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (2042 of 2042 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index fa7528a397..da2c015335 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -138,7 +138,7 @@ "Failed to toggle moderator status": "Moderátor státuszt nem sikerült átállítani", "Failed to unban": "Kizárás visszavonása sikertelen", "Failed to upload profile picture!": "Profil kép feltöltése sikertelen!", - "Failed to verify email address: make sure you clicked the link in the email": "E-mail cím ellenőrzése sikertelen: ellenőrizd, hogy az e-mailnél lévő linkre rákattintottál", + "Failed to verify email address: make sure you clicked the link in the email": "E-mail cím ellenőrzése sikertelen: ellenőrizd, hogy az e-mailben lévő hivatkozásra kattintottál", "Failure to create room": "Szoba létrehozása sikertelen", "Favourites": "Kedvencek", "Fill screen": "Képernyő kitöltése", @@ -355,7 +355,7 @@ "You need to be able to invite users to do that.": "Hogy ezt csinálhasd meg kell tudnod hívni felhasználókat.", "You need to be logged in.": "Be kell jelentkezz.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Ez az e-mail cím, úgy néz ki, nincs összekötve a Matrix azonosítóval ezen a Matrix szerveren.", - "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "A jelszavadat sikeresen megváltoztattuk. Nem kapsz \"push\" értesítéseket amíg a többi eszközön vissza nem jelentkezel", + "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "A jelszavadat sikeresen megváltoztattuk. Addig nem fogsz leküldéses értesítéseket kapni, amíg a többi eszközön vissza nem jelentkezel", "You seem to be in a call, are you sure you want to quit?": "Úgy tűnik hívásban vagy, biztosan kilépsz?", "You seem to be uploading files, are you sure you want to quit?": "Úgy tűnik fájlokat töltesz fel, biztosan kilépsz?", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Nem leszel képes visszavonni ezt a változtatást mivel a felhasználót ugyanarra a szintre emeled amin te vagy.", @@ -451,7 +451,7 @@ "Username available": "Szabad felhasználói név", "Username not available": "A felhasználói név foglalt", "Something went wrong!": "Valami tönkrement!", - "If you already have a Matrix account you can log in instead.": "Ha már van Matrix fiókod akkor beléphetsz helyette.", + "If you already have a Matrix account you can log in instead.": "Ha már van Matrix fiókod, akkor beléphetsz helyette.", "Your browser does not support the required cryptography extensions": "A böngésződ nem támogatja a szükséges titkosítási kiterjesztést", "Not a valid Riot keyfile": "Nem érvényes Riot kulcsfájl", "Authentication check failed: incorrect password?": "Azonosítás sikertelen: hibás jelszó?", @@ -929,7 +929,7 @@ "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "A %(homeserverDomain)s szerver használatának folytatásához el kell olvasnod és el kell fogadnod az általános szerződési feltételeket.", "Review terms and conditions": "Általános Szerződési Feltételek elolvasása", "To continue, please enter your password:": "Folytatáshoz add meg a jelszavad:", - "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Ez végleg használhatatlanná teszi a fiókodat. Ezután nem fogsz tudni bejelentkezni, és más sem tud majd ezzel az azonosítóval fiókot létrehozni. Minden szobából amibe beléptél ki fogsz lépni, és törölni fogja minden fiók adatod az \"identity\" szerverről. Ez a művelet visszafordíthatatlan.", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Ez végleg használhatatlanná teszi a fiókodat. Ezután nem fogsz tudni bejelentkezni, és más sem tud majd ezzel az azonosítóval fiókot létrehozni. Minden szobából amibe beléptél ki fogsz lépni, és törölni fogja minden fiók adatod az személyazonosságod biztosító szerverről. Ez a művelet visszafordíthatatlan.", "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.": "A fiókod felfüggesztése nem jelenti alapértelmezetten azt, hogy az általad küldött üzenetek elfelejtődnek. Ha törölni szeretnéd az általad küldött üzeneteket, pipáld be a jelölőnégyzetet alul.", "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.": "Az üzenetek láthatósága a Matrix-ban hasonlít az emailhez. Az általad küldött üzenet törlése azt jelenti, hogy nem osztjuk meg új-, vagy vendég felhasználóval de a már regisztrált felhasználók akik már hozzáfértek az üzenethez továbbra is elérik a saját másolatukat.", "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)": "Kérlek töröld az összes általam küldött üzenetet amikor a fiókomat felfüggesztem (Figyelem: ez azt eredményezheti, hogy a jövőbeni felhasználók csak részleges beszélgetést látnak majd)", @@ -1053,7 +1053,7 @@ "Print it and store it somewhere safe": "Nyomtad ki és tárold biztonságos helyen", "Save it on a USB key or backup drive": "Mentsd el egy Pendrive-ra vagy a biztonsági mentésekhez", "Copy it to your personal cloud storage": "Másold fel a személyes felhődbe", - "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "A Biztonságos Üzenet Visszaállítás beállítása nélkül ha kijelentkezel vagy másik eszközt használsz, akkor nem tudod visszaállítani a régi titkosított üzeneteidet.", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "A Biztonságos üzenet-visszaállítás beállítása nélkül ha kijelentkezel vagy másik eszközt használsz, akkor nem tudod visszaállítani a régi titkosított üzeneteidet.", "Set up Secure Message Recovery": "Biztonságos Üzenet Visszaállítás beállítása", "Keep it safe": "Tartsd biztonságban", "Create Key Backup": "Kulcs mentés készítése", From ee33c7cd627bdc378d1760f288d0b027f94bf6f1 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 11:09:52 +0000 Subject: [PATCH 132/282] lint --- src/components/views/rooms/RoomTile.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index c277433a5c..6ad7bc3f2d 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -34,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import E2EIcon from './E2EIcon'; +// eslint-disable-next-line camelcase import rate_limited_func from '../../../ratelimitedfunc'; export default createReactClass({ @@ -137,7 +138,7 @@ export default createReactClass({ this.onFindingRoomToBeEncrypted(); }, - onFindingRoomToBeEncrypted: function () { + onFindingRoomToBeEncrypted: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.members", this.onRoomStateMember); cli.on("userTrustStatusChanged", this.onUserVerificationChanged); @@ -162,7 +163,7 @@ export default createReactClass({ .filter((userId) => userId !== cli.getUserId()) .forEach((userId) => { (cli.checkUserTrust(userId).isCrossSigningVerified() ? - verified : unverified).push(userId) + verified : unverified).push(userId); }); /* Check all verified user devices. */ @@ -530,7 +531,7 @@ export default createReactClass({ let e2eIcon = null; // For now, skip the icon for DMs. Possibly we want to move the DM icon elsewhere? if (!dmUserId && this.state.e2eStatus) { - e2eIcon = + e2eIcon = ; } return From a409b9b96fe706e02d88c8956886be0d103da794 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 11:14:01 +0000 Subject: [PATCH 133/282] whoops, left some printfs --- src/components/views/rooms/RoomTile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 6ad7bc3f2d..0b50d85ff6 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -131,9 +131,7 @@ export default createReactClass({ onRoomTimeline: function(ev, room) { if (!room) return; if (room.roomId != this.props.room.roomId) return; - console.warn("e2e onRoomTimeline"); if (ev.getType() !== "m.room.encryption") return; - console.warn("e2e onRoomTimeline ENCRYPTION"); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); this.onFindingRoomToBeEncrypted(); }, From 13bb719a890818e15ea14959fb5fdcab55cf54bc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 11:22:22 +0000 Subject: [PATCH 134/282] Add comment for operations loop --- src/editor/operations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/operations.js b/src/editor/operations.js index d0115d9ca7..d677d7016c 100644 --- a/src/editor/operations.js +++ b/src/editor/operations.js @@ -111,7 +111,7 @@ export function toggleInlineFormat(range, prefix, suffix = prefix) { // compute paragraph [start, end] indexes const paragraphIndexes = []; let startIndex = 0; - // let seenNewlines = 0; + // start at i=2 because we look at i and up to two parts behind to detect paragraph breaks at their end for (let i = 2; i < parts.length; i++) { // paragraph breaks can be denoted in a multitude of ways, // - 2 newline parts in sequence From 0d545ed3359a665860354e750c114bffbf36b425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 11:47:49 +0100 Subject: [PATCH 135/282] EventIndexPeg: Small refactor and change the init logic. This changes the way the event index is initialized, if it's disabled in the settings it will not be initialized at all, before only the crawler loop was not being started. --- src/indexing/EventIndexPeg.js | 57 +++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index a63756ab4e..c8c8cbefe9 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -21,17 +21,19 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import EventIndex from "../indexing/EventIndex"; -import SettingsStore from '../settings/SettingsStore'; +import SettingsStore, {SettingLevel} from '../settings/SettingsStore'; class EventIndexPeg { constructor() { this.index = null; + this._supportIsInstalled = false; } /** - * Create a new EventIndex and initialize it if the platform supports it. + * Initialize the EventIndexPeg and if event indexing is enabled initialize + * the event index. * - * @return {Promise} A promise that will resolve to true if an + * @return {Promise} A promise that will resolve to true if an * EventIndex was successfully initialized, false otherwise. */ async init() { @@ -40,12 +42,32 @@ class EventIndexPeg { } const indexManager = PlatformPeg.get().getEventIndexingManager(); - if (!indexManager || await indexManager.supportsEventIndexing() !== true) { - console.log("EventIndex: Platform doesn't support event indexing,", - "not initializing."); + if (!indexManager) { + console.log("EventIndex: Platform doesn't support event indexing, not initializing."); return false; } + this._supportIsInstalled = await indexManager.supportsEventIndexing(); + + if (!this.supportIsInstalled()) { + console.log("EventIndex: Event indexing isn't installed for the platform, not initializing."); + return false; + } + + if (!SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing')) { + console.log("EventIndex: Event indexing is disabled, not initializing"); + return false; + } + + return this.initEventIndex(); + } + + /* Initialize the event index. + * + * @returns {boolean} True if the event index was succesfully initialized, + * false otherwise. + */ + async initEventIndex() { const index = new EventIndex(); try { @@ -60,6 +82,29 @@ class EventIndexPeg { return true; } + /** + * Check if the current platform has support for event indexing. + * + * @return {boolean} True if it has support, false otherwise. Note that this + * does not mean that support is installed. + */ + platformHasSupport(): boolean { + return PlatformPeg.get().getEventIndexingManager() !== null; + } + + /** + * Check if event indexing support is installed for the platfrom. + * + * Event indexing might require additional optional modules to be installed, + * this tells us if those are installed. Note that this should only be + * called after the init() method was called. + * + * @return {boolean} True if support is installed, false otherwise. + */ + supportIsInstalled(): boolean { + return this._supportIsInstalled; + } + /** * Get the current event index. * From 9bee024da71b7faa06b3c1cec379b0c692d9b895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 12:24:06 +0100 Subject: [PATCH 136/282] ManageEventIndex: Remove some useless divs and add the enable case. --- .../dialogs/eventindex/ManageEventIndex.js | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index d9a0cdcb5d..754d1b8516 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -116,7 +116,11 @@ export default class ManageEventIndex extends React.Component { SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); } - _onDisable = () => { + _onDisable = async () => { + this.props.onFinished(false); + } + + _onEnable = async () => { this.props.onFinished(false); } @@ -126,17 +130,16 @@ export default class ManageEventIndex extends React.Component { render() { let eventIndexingSettings = null; + let buttons; let crawlerState; if (!this.state.eventIndexingEnabled) { - crawlerState =
{_t("Message search for encrypted rooms is disabled.")}
; + crawlerState = _t("Message search for encrypted rooms is disabled."); } else if (this.state.currentRoom === null) { - crawlerState =
{_t("Not downloading messages for any room.")}
; + crawlerState = _t("Not downloading messages for any room."); } else { crawlerState = ( -
- {_t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom })} -
+ _t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom }) ); } @@ -154,18 +157,30 @@ export default class ManageEventIndex extends React.Component { {_t("Number of rooms:")} {this.state.roomCount}
{crawlerState}
+
+ ); - - - + buttons = ( +
+ + {_t("Disable")} + + + {_t("Done")} + +
+ ); + } else if (!this.state.eventIndexingEnabled && this.state.eventIndexingInstalled) { + eventIndexingSettings = ( +
+ {_t( "Securely cache encrypted messages locally for them to appear in search results.")} +
+ ); + buttons = ( +
+ + {_t("Enable")} +
); } else { @@ -182,28 +197,14 @@ export default class ManageEventIndex extends React.Component { } const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const buttons =
-
- - {_t("Disable")} - - - {_t("Done")} - -
-
; return ( {}} + onFinished={this.props.onFinished} title={_t("Message search")} > -
- {eventIndexingSettings} -
-
- {buttons} -
+ {eventIndexingSettings} + {buttons}
); } From 4953f32cbae89967bfbea894a8d25740a3b7124a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 12:24:54 +0100 Subject: [PATCH 137/282] ManageEventIndex: Rename the enable crawler setting. --- .../views/dialogs/eventindex/ManageEventIndex.js | 2 +- src/settings/Settings.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 754d1b8516..76e41fe4b8 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -45,7 +45,7 @@ export default class ManageEventIndex extends React.Component { roomCount: 0, currentRoom: null, eventIndexingEnabled: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling'), + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), crawlerSleepTime: SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), }; diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 817adcfc4d..68b26dbae1 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -491,9 +491,9 @@ export const SETTINGS = { displayName: _td("How long should the crawler wait between requests"), default: 3000, }, - "enableCrawling": { + "enableEventIndexing": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - displayName: _td("How long should the crawler wait between requests"), + displayName: _td("Enable message search in encrypted rooms"), default: true, }, }; From 6c3ac2d0c4585798ba82b913b6ac42b87129c42c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 11:50:59 +0000 Subject: [PATCH 138/282] Add Reject & Ignore user button to invites view --- src/components/structures/RoomView.js | 39 +++++++++++++++++++- src/components/views/rooms/RoomPreviewBar.js | 14 ++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9b02f6d503..b3dafc4acd 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1367,6 +1367,41 @@ export default createReactClass({ }); }, + onRejectAndIgnoreClick: async function() { + this.setState({ + rejecting: true, + }); + + const cli = MatrixClientPeg.get(); + try { + const myMember = this.state.room.getMember(cli.getUserId()); + const inviteEvent = myMember.events.member; + const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers(); + ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk + cli.setIgnoredUsers(ignoredUsers); + + cli.leave(this.state.roomId); + dis.dispatch({ action: 'view_next_room' }); + this.setState({ + rejecting: false, + }); + } catch (error) { + console.error("Failed to reject invite: %s", error); + + const msg = error.message ? error.message : JSON.stringify(error); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { + title: _t("Failed to reject invite"), + description: msg, + }); + + self.setState({ + rejecting: false, + rejectError: error, + }); + } + }, + onRejectThreepidInviteButtonClicked: function(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 @@ -1671,9 +1706,11 @@ export default createReactClass({ return (
- + { _t("Reject & Ignore user") } + + ); + } break; } case MessageCase.ViewingRoom: { @@ -505,8 +516,6 @@ export default createReactClass({ } } - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - let subTitleElements; if (subTitle) { if (!Array.isArray(subTitle)) { @@ -554,6 +563,7 @@ export default createReactClass({
{ secondaryButton } + { extraComponents } { primaryButton }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2e92cbd282..9acbfbd1f3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1046,6 +1046,7 @@ "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", " invited you": " invited you", "Reject": "Reject", + "Reject & Ignore user": "Reject & Ignore user", "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", "%(roomName)s does not exist.": "%(roomName)s does not exist.", From 873952a83e9ac886606a733415c30f1f1d8c9cdd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 11:51:31 +0000 Subject: [PATCH 139/282] delint --- src/components/views/rooms/RoomPreviewBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index e9d752a80f..9af06190f7 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -477,7 +477,7 @@ export default createReactClass({ extraComponents.push( { _t("Reject & Ignore user") } - + , ); } break; From 1747a621189d909c643247206d88399f76f0fa1b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 11:55:08 +0000 Subject: [PATCH 140/282] add missing awaits --- src/components/structures/RoomView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b3dafc4acd..3f438ea909 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1378,9 +1378,9 @@ export default createReactClass({ const inviteEvent = myMember.events.member; const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers(); ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk - cli.setIgnoredUsers(ignoredUsers); + await cli.setIgnoredUsers(ignoredUsers); - cli.leave(this.state.roomId); + await cli.leave(this.state.roomId); dis.dispatch({ action: 'view_next_room' }); this.setState({ rejecting: false, From 30bfd4046d9eb135217688ddab9a8db962b6d55e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 12:27:25 +0000 Subject: [PATCH 141/282] improve spacing of buttons on RoomPreviewBar --- res/css/views/rooms/_RoomPreviewBar.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomPreviewBar.scss b/res/css/views/rooms/_RoomPreviewBar.scss index c7d03e3523..85b6916226 100644 --- a/res/css/views/rooms/_RoomPreviewBar.scss +++ b/res/css/views/rooms/_RoomPreviewBar.scss @@ -117,12 +117,17 @@ limitations under the License. .mx_RoomPreviewBar_actions { flex-direction: column-reverse; .mx_AccessibleButton { - padding: 7px 50px;//extra wide + padding: 7px 50px; //extra wide } & > * { margin-top: 12px; } + .mx_AccessibleButton.mx_AccessibleButton_kind_primary { + // to account for the padding of the primary button which causes inconsistent look between + // subsequent secondary (text) buttons + margin-bottom: 7px; + } } } From 947ea9823d6bb22ea57ed854e4e5ace81673bb0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 13:32:27 +0100 Subject: [PATCH 142/282] Settings: Remove the crawler sleep time setting. --- .../views/dialogs/eventindex/ManageEventIndex.js | 16 ---------------- src/indexing/EventIndex.js | 3 ++- src/settings/Settings.js | 5 ----- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 76e41fe4b8..be7df25381 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -46,8 +46,6 @@ export default class ManageEventIndex extends React.Component { currentRoom: null, eventIndexingEnabled: SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), - crawlerSleepTime: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'), }; } @@ -102,20 +100,6 @@ export default class ManageEventIndex extends React.Component { }); } - _onEventIndexingEnabledChange = (checked) => { - SettingsStore.setValue("enableCrawling", null, SettingLevel.DEVICE, checked); - - if (checked) EventIndexPeg.start(); - else EventIndexPeg.stop(); - - this.setState({eventIndexingEnabled: checked}); - } - - _onCrawlerSleepTimeChange = (e) => { - this.setState({crawlerSleepTime: e.target.value}); - SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); - } - _onDisable = async () => { this.props.onFinished(false); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index a10f4aff71..6d38f1683f 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -30,6 +30,7 @@ export default class EventIndex { // The time in ms that the crawler will wait loop iterations if there // have not been any checkpoints to consume in the last iteration. this._crawlerIdleTime = 5000; + this._crawlerSleepTime = 3000; // The maximum number of events our crawler should fetch in a single // crawl. this._eventsPerCrawl = 100; @@ -211,7 +212,7 @@ export default class EventIndex { // This is a low priority task and we don't want to spam our // homeserver with /messages requests so we set a hefty timeout // here. - let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime'); + let sleepTime = this._crawlerSleepTime; // Don't let the user configure a lower sleep time than 100 ms. sleepTime = Math.max(sleepTime, 100); diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 68b26dbae1..44f38f232f 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -486,11 +486,6 @@ export const SETTINGS = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: RIGHT_PANEL_PHASES.GroupMemberList, }, - "crawlerSleepTime": { - supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - displayName: _td("How long should the crawler wait between requests"), - default: 3000, - }, "enableEventIndexing": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("Enable message search in encrypted rooms"), From 4aa0658ac8e36f671fc6d715091cfc392e311ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 13:33:09 +0100 Subject: [PATCH 143/282] SecurityUserSettingsTab: Put the event index settings behind the feature flag. --- .../settings/tabs/user/SecurityUserSettingsTab.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 7b22dd15e2..eb5f346714 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -254,12 +254,15 @@ export default class SecurityUserSettingsTab extends React.Component {
); - const eventIndex = ( -
- {_t("Message search")} - -
- ); + let eventIndex; + if (SettingsStore.isFeatureEnabled("feature_event_indexing")) { + eventIndex = ( +
+ {_t("Message search")} + +
+ ); + } // XXX: There's no such panel in the current cross-signing designs, but // it's useful to have for testing the feature. If there's no interest From 64c4ad2eb9474dbacd681954682f677fb97d71cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 13:33:55 +0100 Subject: [PATCH 144/282] ManageEventIndex: Hook up the disable event index button. --- .../dialogs/eventindex/DisableEventIndex.js | 74 +++++++++++++++++++ .../dialogs/eventindex/ManageEventIndex.js | 6 +- src/i18n/strings/en_EN.json | 8 +- 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/async-components/views/dialogs/eventindex/DisableEventIndex.js diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js new file mode 100644 index 0000000000..159f8b3d95 --- /dev/null +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -0,0 +1,74 @@ +/* +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 * as sdk from '../../../../index'; +import PropTypes from 'prop-types'; +import { _t } from '../../../../languageHandler'; + +import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../../components/views/elements/LabelledToggleSwitch"; +import Field from "../../../../components/views/elements/Field"; +import {formatBytes} from "../../../../utils/FormattingUtils"; +import EventIndexPeg from "../../../../indexing/EventIndexPeg"; +import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; + + +/* + * Walks the user through the process of creating an e2e key backup + * on the server. + */ +export default class ManageEventIndex extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + eventIndexingEnabled: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), + }; + } + + _onDisable = async () => { + const eventIndex = EventIndexPeg.get(); + await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false); + await EventIndexPeg.deleteEventIndex(); + this.props.onFinished(true); + } + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); + + return ( + + {_t("If disabled, messages form encrypted rooms won't appear in search results")} + + + ); + } +} diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index be7df25381..cf0e2bccc6 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -19,6 +19,7 @@ import * as sdk from '../../../../index'; import PropTypes from 'prop-types'; import { _t } from '../../../../languageHandler'; +import Modal from '../../../../Modal'; import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; import LabelledToggleSwitch from "../../../../components/views/elements/LabelledToggleSwitch"; import Field from "../../../../components/views/elements/Field"; @@ -101,7 +102,10 @@ export default class ManageEventIndex extends React.Component { } _onDisable = async () => { - this.props.onFinished(false); + Modal.createTrackedDialogAsync("Disable message search", "Disable message search", + import("./DisableEventIndex"), + null, null, /* priority = */ false, /* static = */ true, + ); } _onEnable = async () => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2ac55ba04..31ba5581f3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -416,6 +416,7 @@ "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", "Show previews/thumbnails for images": "Show previews/thumbnails for images", "How long should the crawler wait between requests": "How long should the crawler wait between requests", + "Enable message search in encrypted rooms": "Enable message search in encrypted rooms", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", @@ -2035,6 +2036,8 @@ "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", "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.": "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.", + "If disabled, messages form encrypted rooms won't appear in search results": "If disabled, messages form encrypted rooms won't appear in search results", + "Disable": "Disable", "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", "Not downloading messages for any room.": "Not downloading messages for any room.", "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", @@ -2042,9 +2045,8 @@ "Space used:": "Space used:", "Indexed messages:": "Indexed messages:", "Number of rooms:": "Number of rooms:", - "Download and index encrypted messages": "Download and index encrypted messages", - "Message downloading sleep time(ms)": "Message downloading sleep time(ms)", - "Disable": "Disable", + "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", + "Enable": "Enable", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" From 78e1d1674f15d99a182158fcb73974284512e8c3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 13:00:17 +0000 Subject: [PATCH 145/282] reactor E2EIcon for reusability --- src/components/views/rooms/E2EIcon.js | 100 +++++++++++++------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 6ee20023ff..36f230f472 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -15,8 +15,10 @@ limitations under the License. */ import React from "react"; +import PropTypes from "prop-types"; import classNames from 'classnames'; -import { _t } from '../../../languageHandler'; + +import {_t, _td} from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import SettingsStore from '../../../settings/SettingsStore'; @@ -27,71 +29,65 @@ export const E2E_STATE = { NORMAL: "normal", }; -export default function(props) { - const { isUser, status, className } = props; - const isNormal = status === E2E_STATE.NORMAL; - const isWarning = status === E2E_STATE.WARNING; - const isVerified = status === E2E_STATE.VERIFIED; +const crossSigningUserTitles = { + [E2E_STATE.WARNING]: _td("This user has not verified all of their devices."), + [E2E_STATE.NORMAL]: _td("You have not verified this user. This user has verified all of their devices."), + [E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."), +}; +const crossSigningRoomTitles = { + [E2E_STATE.WARNING]: _td("Some users in this encrypted room are not verified by you or they have not verified " + + "their own devices."), + [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " + + "own devices."), +}; +const legacyUserTitles = { + [E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"), + [E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"), +}; +const legacyRoomTitles = { + [E2E_STATE.WARNING]: _td("Some devices in this encrypted room are not trusted"), + [E2E_STATE.VERIFIED]: _td("All devices in this encrypted room are trusted"), +}; + +const E2EIcon = ({isUser, status, className, size, onClick}) => { const e2eIconClasses = classNames({ mx_E2EIcon: true, - mx_E2EIcon_warning: isWarning, - mx_E2EIcon_normal: isNormal, - mx_E2EIcon_verified: isVerified, + mx_E2EIcon_warning: status === E2E_STATE.WARNING, + mx_E2EIcon_normal: status === E2E_STATE.NORMAL, + mx_E2EIcon_verified: status === E2E_STATE.VERIFIED, }, className); - let e2eTitle; + let e2eTitle; const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); if (crossSigning && isUser) { - if (isWarning) { - e2eTitle = _t( - "This user has not verified all of their devices.", - ); - } else if (isNormal) { - e2eTitle = _t( - "You have not verified this user. " + - "This user has verified all of their devices.", - ); - } else if (isVerified) { - e2eTitle = _t( - "You have verified this user. " + - "This user has verified all of their devices.", - ); - } + e2eTitle = crossSigningUserTitles[status]; } else if (crossSigning && !isUser) { - if (isWarning) { - e2eTitle = _t( - "Some users in this encrypted room are not verified by you or " + - "they have not verified their own devices.", - ); - } else if (isVerified) { - e2eTitle = _t( - "All users in this encrypted room are verified by you and " + - "they have verified their own devices.", - ); - } + e2eTitle = crossSigningRoomTitles[status]; } else if (!crossSigning && isUser) { - if (isWarning) { - e2eTitle = _t("Some devices for this user are not trusted"); - } else if (isVerified) { - e2eTitle = _t("All devices for this user are trusted"); - } + e2eTitle = legacyUserTitles[status]; } else if (!crossSigning && !isUser) { - if (isWarning) { - e2eTitle = _t("Some devices in this encrypted room are not trusted"); - } else if (isVerified) { - e2eTitle = _t("All devices in this encrypted room are trusted"); - } + e2eTitle = legacyRoomTitles[status]; } let style = null; - if (props.size) { - style = {width: `${props.size}px`, height: `${props.size}px`}; + if (size) { + style = {width: `${size}px`, height: `${size}px`}; } - const icon = (
); - if (props.onClick) { - return ({ icon }); + const icon = (
); + if (onClick) { + return ({ icon }); } else { return icon; } -} +}; + +E2EIcon.propTypes = { + isUser: PropTypes.bool, + status: PropTypes.oneOf(Object.values(E2E_STATE)), + className: PropTypes.string, + size: PropTypes.number, + onClick: PropTypes.func, +}; + +export default E2EIcon; From c251031dfbf2d432e6818f4c70c0f228f9a15eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 14:25:47 +0100 Subject: [PATCH 146/282] DisableEventIndex: Return back to the user settings after disabling. --- .../views/dialogs/eventindex/DisableEventIndex.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js index 159f8b3d95..3dcbeab454 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import * as sdk from '../../../../index'; import PropTypes from 'prop-types'; +import dis from "../../../../dispatcher"; import { _t } from '../../../../languageHandler'; import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; @@ -49,7 +50,8 @@ export default class ManageEventIndex extends React.Component { const eventIndex = EventIndexPeg.get(); await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false); await EventIndexPeg.deleteEventIndex(); - this.props.onFinished(true); + this.props.onFinished(); + dis.dispatch({ action: 'view_user_settings' }); } render() { From 5ac37c8694f8f42c6cadef7225195b35eaea5cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 14:26:35 +0100 Subject: [PATCH 147/282] ManageEventIndex: Remove the enable button, that one goes somewhere else. --- .../dialogs/eventindex/ManageEventIndex.js | 86 ++++++------------- 1 file changed, 25 insertions(+), 61 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index cf0e2bccc6..76fee228bf 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -45,8 +45,6 @@ export default class ManageEventIndex extends React.Component { eventCount: 0, roomCount: 0, currentRoom: null, - eventIndexingEnabled: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), }; } @@ -108,22 +106,14 @@ export default class ManageEventIndex extends React.Component { ); } - _onEnable = async () => { - this.props.onFinished(false); - } - _onDone = () => { this.props.onFinished(true); } render() { - let eventIndexingSettings = null; - let buttons; let crawlerState; - if (!this.state.eventIndexingEnabled) { - crawlerState = _t("Message search for encrypted rooms is disabled."); - } else if (this.state.currentRoom === null) { + if (this.state.currentRoom === null) { crawlerState = _t("Not downloading messages for any room."); } else { crawlerState = ( @@ -131,58 +121,32 @@ export default class ManageEventIndex extends React.Component { ); } - if (EventIndexPeg.get() !== null) { - eventIndexingSettings = ( -
- { - _t( "Riot is securely caching encrypted messages locally for them " + - "to appear in search results:", - ) - } -
- {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
- {_t("Indexed messages:")} {this.state.eventCount}
- {_t("Number of rooms:")} {this.state.roomCount}
- {crawlerState}
-
+ const eventIndexingSettings = ( +
+ { + _t( "Riot is securely caching encrypted messages locally for them " + + "to appear in search results:", + ) + } +
+ {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {_t("Indexed messages:")} {this.state.eventCount}
+ {_t("Number of rooms:")} {this.state.roomCount}
+ {crawlerState}
- ); +
+ ); - buttons = ( -
- - {_t("Disable")} - - - {_t("Done")} - -
- ); - } else if (!this.state.eventIndexingEnabled && this.state.eventIndexingInstalled) { - eventIndexingSettings = ( -
- {_t( "Securely cache encrypted messages locally for them to appear in search results.")} -
- ); - buttons = ( -
- - {_t("Enable")} - -
- ); - } else { - eventIndexingSettings = ( -
- { - _t( "Riot can't securely cache encrypted messages locally" + - "while running in a web browser. Use Riot Desktop for" + - "encrypted messages to appear in search results.", - ) - } -
- ); - } + const buttons = ( +
+ + {_t("Disable")} + + + {_t("Done")} + +
+ ); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); From 981acec0d2030efabec4cf4959aab352f0637c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 14:27:46 +0100 Subject: [PATCH 148/282] EventIndexPanel: Show the enable button if event indexing is disabled. --- .../views/settings/EventIndexPanel.js | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 8ed4b114e7..7cd0dbe753 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -18,6 +18,7 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import AccessibleButton from "../elements/AccessibleButton"; import {formatBytes} from "../../../utils/FormattingUtils"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; @@ -29,6 +30,8 @@ export default class EventIndexPanel extends React.Component { this.state = { eventIndexSize: 0, roomCount: 0, + eventIndexingEnabled: + SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), }; } @@ -51,10 +54,15 @@ export default class EventIndexPanel extends React.Component { } async componentWillMount(): void { + this.updateState(); + } + + async updateState() { let eventIndexSize = 0; let roomCount = 0; const eventIndex = EventIndexPeg.get(); + const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'); if (eventIndex !== null) { eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this)); @@ -67,6 +75,7 @@ export default class EventIndexPanel extends React.Component { this.setState({ eventIndexSize, roomCount, + eventIndexingEnabled, }); } @@ -79,6 +88,14 @@ export default class EventIndexPanel extends React.Component { ); } + _onEnable = async () => { + await EventIndexPeg.initEventIndex(); + await EventIndexPeg.get().addInitialCheckpoints(); + await EventIndexPeg.get().startCrawler(); + await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, true); + await this.updateState(); + } + render() { let eventIndexingSettings = null; @@ -98,12 +115,25 @@ export default class EventIndexPanel extends React.Component {
); + } else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) { + eventIndexingSettings = ( +
+
+ {_t( "Securely cache encrypted messages locally for them to appear in search results.")} +
+
+ + {_t("Enable")} + +
+
+ ); } else { eventIndexingSettings = (
{ - _t( "Riot can't securely cache encrypted messages locally" + - "while running in a web browser. Use Riot Desktop for" + + _t( "Riot can't securely cache encrypted messages locally " + + "while running in a web browser. Use Riot Desktop for " + "encrypted messages to appear in search results.", ) } From c25c1878b884d04e832c047c90d24acc3212a26a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 13:54:28 +0000 Subject: [PATCH 149/282] Move control of room initial state into createRoom This changes `createRoom` so it has more control of the room's initial state, and appends state for different features, rather resetting the entire state array. This makes room for also controlling encryption state in the next change. --- src/components/structures/MatrixChat.js | 4 ++-- src/components/views/dialogs/CreateRoomDialog.js | 9 +++++---- src/createRoom.js | 14 +++++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3ac8a93e3d..9afc79811d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -961,9 +961,9 @@ export default createReactClass({ const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog); - const [shouldCreate, createOpts] = await modal.finished; + const [shouldCreate, opts] = await modal.finished; if (shouldCreate) { - createRoom({createOpts}); + createRoom(opts); } }, diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 288074a891..9380226381 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -1,5 +1,6 @@ /* Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> +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. @@ -44,13 +45,13 @@ export default createReactClass({ }, _roomCreateOptions() { - const createOpts = {}; + const opts = {}; + const createOpts = opts.createOpts = {}; createOpts.name = this.state.name; if (this.state.isPublic) { createOpts.visibility = "public"; createOpts.preset = "public_chat"; - // to prevent createRoom from enabling guest access - createOpts['initial_state'] = []; + opts.guestAccess = false; const {alias} = this.state; const localPart = alias.substr(1, alias.indexOf(":") - 1); createOpts['room_alias_name'] = localPart; @@ -61,7 +62,7 @@ export default createReactClass({ if (this.state.noFederate) { createOpts.creation_content = {'m.federate': false}; } - return createOpts; + return opts; }, componentDidMount() { diff --git a/src/createRoom.js b/src/createRoom.js index cde9e8b03e..0fa8b1b241 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import {getAddressType} from "./UserAddress"; * @param {object=} opts.createOpts set of options to pass to createRoom call. * @param {bool=} opts.spinner True to show a modal spinner while the room is created. * Default: True + * @param {bool=} opts.guestAccess Whether to enable guest access. + * Default: True * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. @@ -39,6 +41,7 @@ import {getAddressType} from "./UserAddress"; export default function createRoom(opts) { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; + if (opts.guestAccess === undefined) opts.guestAccess = true; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const Loader = sdk.getComponent("elements.Spinner"); @@ -80,15 +83,16 @@ export default function createRoom(opts) { // Allow guests by default since the room is private and they'd // need an invite. This means clicking on a 3pid invite email can // actually drop you right in to a chat. - createOpts.initial_state = createOpts.initial_state || [ - { + createOpts.initial_state = createOpts.initial_state || []; + if (opts.guestAccess) { + createOpts.initial_state.push({ content: { guest_access: 'can_join', }, type: 'm.room.guest_access', state_key: '', - }, - ]; + }); + } let modal; if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); From 2b16b650fed350ccfca1730a4407d7100d88e408 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 14:05:38 +0000 Subject: [PATCH 150/282] Add encryption option to createRoom --- src/createRoom.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/createRoom.js b/src/createRoom.js index 0fa8b1b241..c25b618dc6 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -34,6 +34,8 @@ import {getAddressType} from "./UserAddress"; * Default: True * @param {bool=} opts.guestAccess Whether to enable guest access. * Default: True + * @param {bool=} opts.encryption Whether to enable encryption. + * Default: False * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. @@ -42,6 +44,7 @@ export default function createRoom(opts) { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; if (opts.guestAccess === undefined) opts.guestAccess = true; + if (opts.encryption === undefined) opts.encryption = false; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const Loader = sdk.getComponent("elements.Spinner"); @@ -80,17 +83,28 @@ export default function createRoom(opts) { opts.andView = true; } + createOpts.initial_state = createOpts.initial_state || []; + // Allow guests by default since the room is private and they'd // need an invite. This means clicking on a 3pid invite email can // actually drop you right in to a chat. - createOpts.initial_state = createOpts.initial_state || []; if (opts.guestAccess) { createOpts.initial_state.push({ + type: 'm.room.guest_access', + state_key: '', content: { guest_access: 'can_join', }, - type: 'm.room.guest_access', + }); + } + + if (opts.encryption) { + createOpts.initial_state.push({ + type: 'm.room.encryption', state_key: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, }); } From 3073ce55883dc96b2ed88ad1e586920a4fd2f4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 15:05:40 +0100 Subject: [PATCH 151/282] DisableEventIndex: Set the correct button kind and add a spinner. --- .../dialogs/eventindex/DisableEventIndex.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js index 3dcbeab454..6d0f9a43e8 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -41,12 +41,15 @@ export default class ManageEventIndex extends React.Component { super(props); this.state = { - eventIndexingEnabled: - SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'), + disabling: false, }; } _onDisable = async () => { + this.setState({ + disabling: true, + }); + const eventIndex = EventIndexPeg.get(); await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false); await EventIndexPeg.deleteEventIndex(); @@ -64,12 +67,15 @@ export default class ManageEventIndex extends React.Component { title={_t("Are you sure?")} > {_t("If disabled, messages form encrypted rooms won't appear in search results")} - +
+ + {_t("Cancel")} + + + {_t("Disable")} + + {this.state.enabling ? :
} +
); } From 251661388a9dee3d3f9ce394820ebb3fbd607513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 15:06:10 +0100 Subject: [PATCH 152/282] ManageEventIndex: Set the button kind to danger. --- .../views/dialogs/eventindex/ManageEventIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 76fee228bf..b53b4810aa 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -139,7 +139,7 @@ export default class ManageEventIndex extends React.Component { const buttons = (
- + {_t("Disable")} From a5a149933a0dd98e7ca86d08a382a9ff6b0cc48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 15:06:38 +0100 Subject: [PATCH 153/282] EventIndexPanel: Add a spinner when the index is being enabled. --- .../views/settings/EventIndexPanel.js | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 7cd0dbe753..f93ab489c7 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; import Modal from '../../../Modal'; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import AccessibleButton from "../elements/AccessibleButton"; @@ -28,6 +29,7 @@ export default class EventIndexPanel extends React.Component { super(); this.state = { + enabling: false, eventIndexSize: 0, roomCount: 0, eventIndexingEnabled: @@ -58,11 +60,12 @@ export default class EventIndexPanel extends React.Component { } async updateState() { - let eventIndexSize = 0; - let roomCount = 0; - const eventIndex = EventIndexPeg.get(); const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'); + const enabling = false; + + let eventIndexSize = 0; + let roomCount = 0; if (eventIndex !== null) { eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this)); @@ -73,6 +76,7 @@ export default class EventIndexPanel extends React.Component { } this.setState({ + enabling, eventIndexSize, roomCount, eventIndexingEnabled, @@ -89,6 +93,10 @@ export default class EventIndexPanel extends React.Component { } _onEnable = async () => { + this.setState({ + enabling: true, + }); + await EventIndexPeg.initEventIndex(); await EventIndexPeg.get().addInitialCheckpoints(); await EventIndexPeg.get().startCrawler(); @@ -98,6 +106,7 @@ export default class EventIndexPanel extends React.Component { render() { let eventIndexingSettings = null; + const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); if (EventIndexPeg.get() !== null) { eventIndexingSettings = ( @@ -119,12 +128,15 @@ export default class EventIndexPanel extends React.Component { eventIndexingSettings = (
- {_t( "Securely cache encrypted messages locally for them to appear in search results.")} + {_t( "Securely cache encrypted messages locally for them to " + + "appear in search results.")}
- + {_t("Enable")} + {this.state.enabling ? :
}
); From 381fe95f67919457cd1cdfa75ba7dd895dd69b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2020 15:22:26 +0100 Subject: [PATCH 154/282] EventIndex: Fix some lint errors. --- .../views/dialogs/eventindex/DisableEventIndex.js | 6 +----- .../views/dialogs/eventindex/ManageEventIndex.js | 3 --- src/indexing/EventIndex.js | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js index 6d0f9a43e8..064f55edc0 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -21,9 +21,6 @@ import dis from "../../../../dispatcher"; import { _t } from '../../../../languageHandler'; import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../../../../components/views/elements/LabelledToggleSwitch"; -import Field from "../../../../components/views/elements/Field"; -import {formatBytes} from "../../../../utils/FormattingUtils"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; @@ -50,7 +47,6 @@ export default class ManageEventIndex extends React.Component { disabling: true, }); - const eventIndex = EventIndexPeg.get(); await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false); await EventIndexPeg.deleteEventIndex(); this.props.onFinished(); @@ -59,7 +55,7 @@ export default class ManageEventIndex extends React.Component { render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); + const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); return ( Date: Thu, 23 Jan 2020 15:32:43 +0100 Subject: [PATCH 155/282] Update the translation file. --- src/i18n/strings/en_EN.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 31ba5581f3..3973ae442e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -415,7 +415,6 @@ "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", "Show previews/thumbnails for images": "Show previews/thumbnails for images", - "How long should the crawler wait between requests": "How long should the crawler wait between requests", "Enable message search in encrypted rooms": "Enable message search in encrypted rooms", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", @@ -559,7 +558,9 @@ " to store messages from ": " to store messages from ", "rooms.": "rooms.", "Manage": "Manage", - "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locallywhile running in a web browser. Use Riot Desktop forencrypted messages to appear in search results.", + "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", + "Enable": "Enable", + "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.", "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.", @@ -2038,15 +2039,12 @@ "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.": "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.", "If disabled, messages form encrypted rooms won't appear in search results": "If disabled, messages form encrypted rooms won't appear in search results", "Disable": "Disable", - "Message search for encrypted rooms is disabled.": "Message search for encrypted rooms is disabled.", "Not downloading messages for any room.": "Not downloading messages for any room.", "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", "Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot is securely caching encrypted messages locally for them to appear in search results:", "Space used:": "Space used:", "Indexed messages:": "Indexed messages:", "Number of rooms:": "Number of rooms:", - "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", - "Enable": "Enable", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" From 74b08ea4895a581ebb16a83a470e170213f7b24f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 14:38:17 +0000 Subject: [PATCH 156/282] Clean up E2EIcon for better maintainability --- src/components/views/rooms/E2EIcon.js | 13 +++---- src/hooks/useSettings.js | 52 +++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 5 +-- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/hooks/useSettings.js diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 36f230f472..7ac3b5af2d 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -1,5 +1,6 @@ /* Copyright 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. @@ -20,7 +21,7 @@ import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; -import SettingsStore from '../../../settings/SettingsStore'; +import {useFeatureEnabled} from "../../../hooks/useSettings"; export const E2E_STATE = { VERIFIED: "verified", @@ -35,11 +36,11 @@ const crossSigningUserTitles = { [E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."), }; const crossSigningRoomTitles = { - [E2E_STATE.WARNING]: _td("Some users in this encrypted room are not verified by you or they have not verified " + - "their own devices."), - [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " + - "own devices."), + [E2E_STATE.WARNING]: _td("Someone is using an unknown device"), + [E2E_STATE.NORMAL]: _td("This room is end-to-end encrypted"), + [E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"), }; + const legacyUserTitles = { [E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"), [E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"), @@ -58,7 +59,7 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => { }, className); let e2eTitle; - const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); + const crossSigning = useFeatureEnabled("feature_cross_signing"); if (crossSigning && isUser) { e2eTitle = crossSigningUserTitles[status]; } else if (crossSigning && !isUser) { diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js new file mode 100644 index 0000000000..151a6369de --- /dev/null +++ b/src/hooks/useSettings.js @@ -0,0 +1,52 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {useEffect, useState} from "react"; +import SettingsStore from '../settings/SettingsStore'; + +// Hook to fetch the value of a setting and dynamically update when it changes +export const useSettingValue = (settingName, roomId = null, excludeDefault = false) => { + const [value, setValue] = useState(SettingsStore.getValue(settingName, roomId, excludeDefault)); + + useEffect(() => { + const ref = SettingsStore.watchSetting(settingName, roomId, () => { + setValue(SettingsStore.getValue(settingName, roomId, excludeDefault)); + }); + // clean-up + return () => { + SettingsStore.unwatchSetting(ref); + }; + }, [settingName, roomId, excludeDefault]); + + return value; +}; + +// Hook to fetch whether a feature is enabled and dynamically update when that changes +export const useFeatureEnabled = (featureName, roomId = null) => { + const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId)); + + useEffect(() => { + const ref = SettingsStore.watchSetting(featureName, roomId, () => { + setEnabled(SettingsStore.isFeatureEnabled(featureName, roomId)); + }); + // clean-up + return () => { + SettingsStore.unwatchSetting(ref); + }; + }, [featureName, roomId]); + + return enabled; +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d19cbb9bfd..fb2898ae3d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -884,8 +884,9 @@ "This user has not verified all of their devices.": "This user has not verified all of their devices.", "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", - "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", - "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", + "Someone is using an unknown device": "Someone is using an unknown device", + "This room is end-to-end encrypted": "This room is end-to-end encrypted", + "Everyone in this room is verified": "Everyone in this room is verified", "Some devices for this user are not trusted": "Some devices for this user are not trusted", "All devices for this user are trusted": "All devices for this user are trusted", "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", From 662b34c8dbc2196b4061f08c8555c2a1e02cf5a2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 14:38:39 +0000 Subject: [PATCH 157/282] Update MessageComposer placeholder and e2e icon size --- res/css/views/rooms/_MessageComposer.scss | 2 + src/components/views/rooms/MessageComposer.js | 46 ++++++++++--------- src/i18n/strings/en_EN.json | 4 +- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 5efca51844..fae9d0dfe3 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -76,6 +76,8 @@ limitations under the License. left: 60px; margin-right: 0; // Counteract the E2EIcon class margin-left: 3px; // Counteract the E2EIcon class + width: 12px; + height: 12px; } .mx_MessageComposer_noperm_error { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 8d36f02d02..53e10fa750 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -26,6 +26,7 @@ import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import ContentMessages from '../../../ContentMessages'; import E2EIcon from './E2EIcon'; +import SettingsStore from "../../../settings/SettingsStore"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -168,7 +169,6 @@ export default class MessageComposer extends React.Component { constructor(props) { super(props); this.onInputStateChanged = this.onInputStateChanged.bind(this); - this.onEvent = this.onEvent.bind(this); this._onRoomStateEvents = this._onRoomStateEvents.bind(this); this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onTombstoneClick = this._onTombstoneClick.bind(this); @@ -182,11 +182,6 @@ export default class MessageComposer extends React.Component { } componentDidMount() { - // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler - // for 'event' fires *after* 'RoomEvent', and our room won't have yet been - // marked as encrypted. - // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something. - MatrixClientPeg.get().on("event", this.onEvent); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._waitForOwnMember(); @@ -210,7 +205,6 @@ export default class MessageComposer extends React.Component { componentWillUnmount() { if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("event", this.onEvent); MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents); } if (this._roomStoreToken) { @@ -218,13 +212,6 @@ export default class MessageComposer extends React.Component { } } - onEvent(event) { - if (event.getType() !== 'm.room.encryption') return; - if (event.getRoomId() !== this.props.room.roomId) return; - // TODO: put (encryption state??) in state - this.forceUpdate(); - } - _onRoomStateEvents(ev, state) { if (ev.getRoomId() !== this.props.room.roomId) return; @@ -282,18 +269,33 @@ export default class MessageComposer extends React.Component { } renderPlaceholderText() { - const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); - if (this.state.isQuoting) { - if (roomIsEncrypted) { - return _t('Send an encrypted reply…'); + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); + } else { + return _t('Send a reply…'); + } } else { - return _t('Send a reply (unencrypted)…'); + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message…'); + } } } else { - if (roomIsEncrypted) { - return _t('Send an encrypted message…'); + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); + } else { + return _t('Send a reply (unencrypted)…'); + } } else { - return _t('Send a message (unencrypted)…'); + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message (unencrypted)…'); + } } } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fb2898ae3d..20707a4f29 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -962,8 +962,10 @@ "Hangup": "Hangup", "Upload file": "Upload file", "Send an encrypted reply…": "Send an encrypted reply…", - "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", + "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", + "Send a message…": "Send a message…", + "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", "Send a message (unencrypted)…": "Send a message (unencrypted)…", "The conversation continues here.": "The conversation continues here.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", From e9dccd1460935cd8e68fe8f9595223cc7ebac4ba Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 23 Jan 2020 14:44:57 +0000 Subject: [PATCH 158/282] moving icons exactly one pixel to the right and one pixel down --- res/css/views/rooms/_RoomHeader.scss | 4 ++-- res/css/views/rooms/_RoomTile.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a235a47fdd..6bfcd437c1 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -21,8 +21,8 @@ limitations under the License. .mx_E2EIcon { margin: 0; position: absolute; - bottom: 0; - right: -1px; + bottom: -1px; + right: -2px; height: 10px; width: 10px } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index a36d781669..376f4370e3 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -105,8 +105,8 @@ limitations under the License. width: 10px; display: block; position: absolute; - bottom: -0px; - right: -1px; + bottom: -1px; + right: -2px; z-index: 1; margin: 0; } From 604e244265df6a4637cdb772ea779dd066f3d137 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 14:53:41 +0000 Subject: [PATCH 159/282] Attempt to fix Safari + VoiceOver misunderstanding the timeline list --- src/components/structures/ScrollPanel.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index bc7c400949..5121dd3f9d 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -877,11 +877,14 @@ export default createReactClass({ // TODO: the classnames on the div and ol could do with being updated to // reflect the fact that we don't necessarily contain a list of messages. // it's not obvious why we have a separate div and ol anyway. + + // give the
    an explicit role=list because Safari+VoiceOver seems to think an ordered-list with + // list-style-type: none; is no longer a list return (
    -
      +
        { this.props.children }
    From 8a523fbb594174f54608691cf3c172e6b2ba8364 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 23 Jan 2020 15:53:54 +0100 Subject: [PATCH 160/282] remove this log line, so we can remove the .event property this is likely the error Dave hit last week, because there was no .request event set yet for some other reasons that have been fixed already (the event being put in the wrong map, theirs instead of us) in the VerificationRequest object. --- src/components/structures/MatrixChat.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3ac8a93e3d..cc5695cf31 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1453,7 +1453,6 @@ export default createReactClass({ if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { cli.on("crypto.verification.request", request => { - console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId()); if (request.pending) { ToastStore.sharedInstance().addOrReplaceToast({ key: 'verifreq_' + request.channel.transactionId, From 1e25b32ba395f3bf714241f6f5eab202a2a49c31 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 23 Jan 2020 16:00:55 +0000 Subject: [PATCH 161/282] Enable encryption in DMs with device keys When the cross-signing lab is enabled, this changes DMs to use encryption as long as all invited users have uploaded device keys (which we're using as a proxy for "has some client that understands E2E"). Fixes https://github.com/vector-im/riot-web/issues/12005 --- src/components/views/dialogs/InviteDialog.js | 24 +++++++++++++++++--- src/components/views/right_panel/UserInfo.js | 24 +++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 703b0b5121..fde21791e7 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -33,6 +33,7 @@ import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; import createRoom from "../../../createRoom"; import {inviteMultipleToRoom} from "../../../RoomInvite"; +import SettingsStore from '../../../settings/SettingsStore'; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; @@ -493,7 +494,7 @@ export default class InviteDialog extends React.PureComponent { return false; } - _startDm = () => { + _startDm = async () => { this.setState({busy: true}); const targetIds = this.state.targets.map(t => t.userId); @@ -510,14 +511,31 @@ export default class InviteDialog extends React.PureComponent { return; } + const createRoomOptions = {}; + + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const client = MatrixClientPeg.get(); + const usersToDevicesMap = await client.downloadKeys(targetIds); + const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { + // `devices` is an object of the form { deviceId: deviceInfo, ... }. + return Object.keys(devices).length > 0; + }); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; + } + } + // Check if it's a traditional DM and create the room if required. // TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM let createRoomPromise = Promise.resolve(); if (targetIds.length === 1) { - createRoomPromise = createRoom({dmUserId: targetIds[0]}); + createRoomOptions.dmUserId = targetIds[0]; + createRoomPromise = createRoom(createRoomOptions); } else { // Create a boring room and try to invite the targets manually. - createRoomPromise = createRoom().then(roomId => { + createRoomPromise = createRoom(createRoomOptions).then(roomId => { return inviteMultipleToRoom(roomId, targetIds); }).then(result => { if (this._shouldAbortAfterInviteError(result)) { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index b08f07ace4..01d0002801 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -82,7 +82,7 @@ const _getE2EStatus = (cli, userId, devices) => { return "warning"; }; -function openDMForUser(matrixClient, userId) { +async function openDMForUser(matrixClient, userId) { const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId); const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => { const room = matrixClient.getRoom(roomId); @@ -100,9 +100,27 @@ function openDMForUser(matrixClient, userId) { action: 'view_room', room_id: lastActiveRoom.roomId, }); - } else { - createRoom({dmUserId: userId}); + return; } + + const createRoomOptions = { + dmUserId: userId, + }; + + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const usersToDevicesMap = await matrixClient.downloadKeys([userId]); + const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { + // `devices` is an object of the form { deviceId: deviceInfo, ... }. + return Object.keys(devices).length > 0; + }); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; + } + } + + createRoom(createRoomOptions); } function useIsEncrypted(cli, room) { From 237a0ed07121992faecff207d783c0c63634b260 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 23 Jan 2020 16:09:32 +0000 Subject: [PATCH 162/282] Make the entire User Info scrollable, sticky close button --- res/css/views/right_panel/_UserInfo.scss | 22 +++++--- src/components/views/right_panel/UserInfo.js | 57 ++++++++++---------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index e87fe06a94..d2d9d12c6d 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -23,15 +23,23 @@ limitations under the License. font-size: 12px; .mx_UserInfo_cancel { - height: 16px; - width: 16px; - padding: 10px 0 10px 10px; cursor: pointer; - mask-image: url('$(res)/img/minimise.svg'); - mask-repeat: no-repeat; - mask-position: 16px center; - background-color: $rightpanel-button-color; position: absolute; + top: 0; + border-radius: 4px; + background-color: $dark-panel-bg-color; + margin: 9px; + z-index: 1; // render on top of the right panel + + div { + height: 16px; + width: 16px; + padding: 4px; + mask-image: url('$(res)/img/minimise.svg'); + mask-repeat: no-repeat; + mask-position: 7px center; + background-color: $rightpanel-button-color; + } } h2 { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index b08f07ace4..31431d56a4 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1219,10 +1219,9 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { let closeButton; if (onClose) { - closeButton = ; + closeButton = +
    + ; } const memberDetails = ( @@ -1338,32 +1337,32 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { return (
    - { closeButton } - { avatarElement } - -
    -
    -
    -

    - { e2eIcon } - { displayName } -

    -
    -
    { user.userId }
    -
    - {presenceLabel} - {statusLabel} -
    -
    -
    - - { memberDetails &&
    -
    - { memberDetails } -
    -
    } - + { closeButton } + { avatarElement } + +
    +
    +
    +

    + { e2eIcon } + { displayName } +

    +
    +
    { user.userId }
    +
    + {presenceLabel} + {statusLabel} +
    +
    +
    + + { memberDetails &&
    +
    + { memberDetails } +
    +
    } + { securitySection } Date: Thu, 23 Jan 2020 18:14:08 +0000 Subject: [PATCH 163/282] Remove riot logo from the security setup screens With a little faff to make the rounded borders consistent again --- res/css/views/auth/_AuthBody.scss | 4 ++++ .../structures/auth/CompleteSecurity.js | 3 +-- src/components/views/auth/AuthBody.js | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index b05629003e..d342de6d75 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -95,6 +95,10 @@ limitations under the License. } } +.mx_AuthBody_noHeader { + border-radius: 4px; +} + .mx_AuthBody_editServerDetails { padding-left: 1em; font-size: 12px; diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index b64f368908..982360e8c1 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -161,8 +161,7 @@ export default class CompleteSecurity extends React.Component { return ( - - +

    {icon} {title} diff --git a/src/components/views/auth/AuthBody.js b/src/components/views/auth/AuthBody.js index 9a078efb52..db5c941b20 100644 --- a/src/components/views/auth/AuthBody.js +++ b/src/components/views/auth/AuthBody.js @@ -17,10 +17,26 @@ limitations under the License. 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; + +import classnames from 'classnames'; export default class AuthBody extends React.PureComponent { + static PropTypes = { + header: PropTypes.bool, + }; + + static defaultProps = { + header: true, + }; + render() { - return
    + const classes = { + 'mx_AuthBody': true, + 'mx_AuthBody_noHeader': !this.props.header, + }; + + return
    { this.props.children }
    ; } From a3026277c1c03a929f95308bcfd97b26cca83067 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jan 2020 18:24:03 +0000 Subject: [PATCH 164/282] Unused variable --- src/components/structures/auth/CompleteSecurity.js | 1 - src/components/views/settings/KeyBackupPanel.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 982360e8c1..d2cea9b00a 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -74,7 +74,6 @@ export default class CompleteSecurity extends React.Component { render() { const AuthPage = sdk.getComponent("auth.AuthPage"); - const AuthHeader = sdk.getComponent("auth.AuthHeader"); const AuthBody = sdk.getComponent("auth.AuthBody"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index fd166782d8..cd8cdfa5d9 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -127,7 +127,7 @@ export default class KeyBackupPanel extends React.PureComponent { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), { - secureSecretStorage: SettingsStore.isFeatureEnabled("feature_cross_signing"), + secureSecretStorage: false, onFinished: () => { this._loadBackupStatus(); }, From 74b678d0ff4444f1522c68b5e68628659628f9b5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jan 2020 19:19:12 +0000 Subject: [PATCH 165/282] Only say the session is verified if it is now verified --- src/components/structures/auth/CompleteSecurity.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index b64f368908..741306a68b 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -44,9 +44,12 @@ export default class CompleteSecurity extends React.Component { await accessSecretStorage(async () => { await cli.checkOwnCrossSigningTrust(); }); - this.setState({ - phase: PHASE_DONE, - }); + + if (cli.getCrossSigningId()) { + this.setState({ + phase: PHASE_DONE, + }); + } } catch (e) { // this will throw if the user hits cancel, so ignore } From 4cff50a11e0e4d4a7c008ac38835010e63aacff4 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 23 Jan 2020 18:29:15 +0000 Subject: [PATCH 166/282] Translated using Weblate (Turkish) Currently translated at 72.9% (1494 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 21ccf03a46..2656b00593 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1496,5 +1496,19 @@ "Create a public room": "Halka açık bir oda oluşturun", "Make this room public": "Bu odayı halka açık yap", "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 Riot to do this": "Sohbet tarihçesini kaybetmemek için, çıkmadan önce odanızın anahtarlarını dışarıya aktarın. Bunu yapabilmek için Riotun daha yeni sürümü gerekli. Ulaşmak için geri gitmeye ihtiyacınız var", - "Continue With Encryption Disabled": "Şifreleme Kapalı Şekilde Devam Et" + "Continue With Encryption Disabled": "Şifreleme Kapalı Şekilde Devam Et", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "%(fileName)s dosyası anasunucunun yükleme boyutu limitini aşıyor", + "Double check that your server supports the room version chosen and try again.": "Seçtiğiniz oda sürümünün sunucunuz tarafından desteklenip desteklenmediğini iki kez kontrol edin ve yeniden deneyin.", + "Changes your avatar in this current room only": "Sadece bu odadaki resminizi değiştirin", + "Please supply a https:// or http:// widget URL": "Lütfen bir https:// ya da http:// olarak bir görsel bileşen URL i belirtin", + "Sends the given emote coloured as a rainbow": "Verilen ifadeyi bir gökkuşağı gibi renklendirilmiş olarak gönderin", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s ekran adını %(displayName)s olarak değiştirdi.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s odayı adresi bilen herkesin girebileceği şekilde halka açık hale getirdi.", + "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s katılma kuralını %(rule)s şeklinde değiştirdi", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s misafir erişim kuralını %(rule)s şeklinde değiştirdi", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s bu odanın ana adresini %(address)s olarak ayarladı.", + "%(senderName)s placed a voice call.": "%(senderName)s bir çağrı yaptı.", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s bir çağrı başlattı. (Bu tarayıcı tarafından desteklenmiyor)", + "%(senderName)s placed a video call.": "%(senderName)s bir görüntülü çağrı yaptı.", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s bir görüntülü çağrı yaptı. (bu tarayıcı tarafından desteklenmiyor)" } From 6fc647c3d969aff9bba47e209e2b48bbebfc91a6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 14:59:52 -0700 Subject: [PATCH 167/282] Don't list every single alias when there's many --- src/TextForEvent.js | 24 ++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 27 insertions(+) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index a0d088affb..6a2744109b 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -275,6 +275,8 @@ function textForRoomAliasesEvent(ev) { // This feels a bit overkill though, and it's not clear the i18n really needs it // so instead it's landing as a simple textual event. + const maxShown = 3; + const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAliases = ev.getPrevContent().aliases || []; const newAliases = ev.getContent().aliases || []; @@ -287,18 +289,40 @@ function textForRoomAliasesEvent(ev) { } if (addedAliases.length && !removedAliases.length) { + if (addedAliases.length > maxShown) { + return _t("%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", { + senderName: senderName, + count: addedAliases.length - maxShown, + addedAddresses: addedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.', { senderName: senderName, count: addedAliases.length, addedAddresses: addedAliases.join(', '), }); } else if (!addedAliases.length && removedAliases.length) { + if (removedAliases.length > maxShown) { + return _t("%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", { + senderName: senderName, + count: removedAliases.length - maxShown, + removedAddresses: removedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.', { senderName: senderName, count: removedAliases.length, removedAddresses: removedAliases.join(', '), }); } else { + const combined = addedAliases.length + removedAliases.length; + if (combined > maxShown) { + return _t("%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", { + senderName: senderName, + countAdded: addedAliases.length, + countRemoved: removedAliases.length, + }); + } return _t( '%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.', { senderName: senderName, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9060279e54..c86640132a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -235,10 +235,13 @@ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", From 1ac82a9f165c145c12ea796eb29968a97e37a510 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 14:59:52 -0700 Subject: [PATCH 168/282] Revert "Don't list every single alias when there's many" This reverts commit 6fc647c3d969aff9bba47e209e2b48bbebfc91a6. --- src/TextForEvent.js | 24 ------------------------ src/i18n/strings/en_EN.json | 3 --- 2 files changed, 27 deletions(-) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 6a2744109b..a0d088affb 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -275,8 +275,6 @@ function textForRoomAliasesEvent(ev) { // This feels a bit overkill though, and it's not clear the i18n really needs it // so instead it's landing as a simple textual event. - const maxShown = 3; - const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAliases = ev.getPrevContent().aliases || []; const newAliases = ev.getContent().aliases || []; @@ -289,40 +287,18 @@ function textForRoomAliasesEvent(ev) { } if (addedAliases.length && !removedAliases.length) { - if (addedAliases.length > maxShown) { - return _t("%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", { - senderName: senderName, - count: addedAliases.length - maxShown, - addedAddresses: addedAliases.slice(0, maxShown).join(', '), - }); - } return _t('%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.', { senderName: senderName, count: addedAliases.length, addedAddresses: addedAliases.join(', '), }); } else if (!addedAliases.length && removedAliases.length) { - if (removedAliases.length > maxShown) { - return _t("%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", { - senderName: senderName, - count: removedAliases.length - maxShown, - removedAddresses: removedAliases.slice(0, maxShown).join(', '), - }); - } return _t('%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.', { senderName: senderName, count: removedAliases.length, removedAddresses: removedAliases.join(', '), }); } else { - const combined = addedAliases.length + removedAliases.length; - if (combined > maxShown) { - return _t("%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", { - senderName: senderName, - countAdded: addedAliases.length, - countRemoved: removedAliases.length, - }); - } return _t( '%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.', { senderName: senderName, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c86640132a..9060279e54 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -235,13 +235,10 @@ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", - "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", - "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", - "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", From 18f8f8858c92d11806bf08d5c1125f562f7871b2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 14:59:52 -0700 Subject: [PATCH 169/282] Don't list every single alias when there's many --- src/TextForEvent.js | 24 ++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 27 insertions(+) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index a0d088affb..6a2744109b 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -275,6 +275,8 @@ function textForRoomAliasesEvent(ev) { // This feels a bit overkill though, and it's not clear the i18n really needs it // so instead it's landing as a simple textual event. + const maxShown = 3; + const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAliases = ev.getPrevContent().aliases || []; const newAliases = ev.getContent().aliases || []; @@ -287,18 +289,40 @@ function textForRoomAliasesEvent(ev) { } if (addedAliases.length && !removedAliases.length) { + if (addedAliases.length > maxShown) { + return _t("%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", { + senderName: senderName, + count: addedAliases.length - maxShown, + addedAddresses: addedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.', { senderName: senderName, count: addedAliases.length, addedAddresses: addedAliases.join(', '), }); } else if (!addedAliases.length && removedAliases.length) { + if (removedAliases.length > maxShown) { + return _t("%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", { + senderName: senderName, + count: removedAliases.length - maxShown, + removedAddresses: removedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.', { senderName: senderName, count: removedAliases.length, removedAddresses: removedAliases.join(', '), }); } else { + const combined = addedAliases.length + removedAliases.length; + if (combined > maxShown) { + return _t("%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", { + senderName: senderName, + countAdded: addedAliases.length, + countRemoved: removedAliases.length, + }); + } return _t( '%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.', { senderName: senderName, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9060279e54..c86640132a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -235,10 +235,13 @@ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", From 6f6d149a7077064cfa953b33e9ec02891f55dd13 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 14:59:52 -0700 Subject: [PATCH 170/282] Don't list every single alias when there's many --- src/TextForEvent.js | 24 ++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 27 insertions(+) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index a0d088affb..6a2744109b 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -275,6 +275,8 @@ function textForRoomAliasesEvent(ev) { // This feels a bit overkill though, and it's not clear the i18n really needs it // so instead it's landing as a simple textual event. + const maxShown = 3; + const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAliases = ev.getPrevContent().aliases || []; const newAliases = ev.getContent().aliases || []; @@ -287,18 +289,40 @@ function textForRoomAliasesEvent(ev) { } if (addedAliases.length && !removedAliases.length) { + if (addedAliases.length > maxShown) { + return _t("%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", { + senderName: senderName, + count: addedAliases.length - maxShown, + addedAddresses: addedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.', { senderName: senderName, count: addedAliases.length, addedAddresses: addedAliases.join(', '), }); } else if (!addedAliases.length && removedAliases.length) { + if (removedAliases.length > maxShown) { + return _t("%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", { + senderName: senderName, + count: removedAliases.length - maxShown, + removedAddresses: removedAliases.slice(0, maxShown).join(', '), + }); + } return _t('%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.', { senderName: senderName, count: removedAliases.length, removedAddresses: removedAliases.join(', '), }); } else { + const combined = addedAliases.length + removedAliases.length; + if (combined > maxShown) { + return _t("%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", { + senderName: senderName, + countAdded: addedAliases.length, + countRemoved: removedAliases.length, + }); + } return _t( '%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.', { senderName: senderName, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4af203177c..853cd1f744 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -236,10 +236,13 @@ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room", "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", From 4aa3be9b42698e5b963c02494379ac60b9d67e8b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 15:38:06 -0700 Subject: [PATCH 171/282] Move & upgrade babel runtime into dependencies (like it wants) https://babeljs.io/docs/en/babel-runtime --- package.json | 4 ++-- yarn.lock | 42 +++++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 8f49eed4b5..0c1d66058a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" }, "dependencies": { + "@babel/runtime": "^7.8.3", "blueimp-canvas-to-blob": "^3.5.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", @@ -108,13 +109,12 @@ "@babel/plugin-proposal-numeric-separator": "^7.7.4", "@babel/plugin-proposal-object-rest-spread": "^7.7.4", "@babel/plugin-transform-flow-comments": "^7.7.4", - "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/plugin-transform-runtime": "^7.8.3", "@babel/preset-env": "^7.7.6", "@babel/preset-flow": "^7.7.4", "@babel/preset-react": "^7.7.4", "@babel/preset-typescript": "^7.7.4", "@babel/register": "^7.7.4", - "@babel/runtime": "^7.7.6", "@peculiar/webcrypto": "^1.0.22", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", diff --git a/yarn.lock b/yarn.lock index 0177629cd8..576cd019ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,6 +211,13 @@ dependencies: "@babel/types" "^7.7.4" +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + "@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5": version "7.7.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835" @@ -235,6 +242,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + "@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" @@ -735,13 +747,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-runtime@^7.7.6": - version "7.7.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz#4f2b548c88922fb98ec1c242afd4733ee3e12f61" - integrity sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A== +"@babel/plugin-transform-runtime@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz#c0153bc0a5375ebc1f1591cb7eea223adea9f169" + integrity sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ== dependencies: - "@babel/helper-module-imports" "^7.7.4" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" resolve "^1.8.1" semver "^5.5.1" @@ -894,7 +906,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.7.6": +"@babel/runtime@^7.0.0": version "7.7.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== @@ -908,6 +920,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" + integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.4.0", "@babel/template@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" @@ -941,6 +960,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" + integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" From a330ee186aafd758cc4f6ed274b92f7afe764c65 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 15:38:06 -0700 Subject: [PATCH 172/282] Move & upgrade babel runtime into dependencies (like it wants) https://babeljs.io/docs/en/babel-runtime --- package.json | 4 ++-- yarn.lock | 42 +++++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 9203b20bfa..dc619e50ef 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" }, "dependencies": { + "@babel/runtime": "^7.8.3", "blueimp-canvas-to-blob": "^3.5.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", @@ -108,13 +109,12 @@ "@babel/plugin-proposal-numeric-separator": "^7.7.4", "@babel/plugin-proposal-object-rest-spread": "^7.7.4", "@babel/plugin-transform-flow-comments": "^7.7.4", - "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/plugin-transform-runtime": "^7.8.3", "@babel/preset-env": "^7.7.6", "@babel/preset-flow": "^7.7.4", "@babel/preset-react": "^7.7.4", "@babel/preset-typescript": "^7.7.4", "@babel/register": "^7.7.4", - "@babel/runtime": "^7.7.6", "@peculiar/webcrypto": "^1.0.22", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", diff --git a/yarn.lock b/yarn.lock index 808721a188..75c5af6a1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,6 +211,13 @@ dependencies: "@babel/types" "^7.7.4" +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + "@babel/helper-module-transforms@^7.7.4", "@babel/helper-module-transforms@^7.7.5": version "7.7.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz#d044da7ffd91ec967db25cd6748f704b6b244835" @@ -235,6 +242,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + "@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" @@ -735,13 +747,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-runtime@^7.7.6": - version "7.7.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz#4f2b548c88922fb98ec1c242afd4733ee3e12f61" - integrity sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A== +"@babel/plugin-transform-runtime@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz#c0153bc0a5375ebc1f1591cb7eea223adea9f169" + integrity sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ== dependencies: - "@babel/helper-module-imports" "^7.7.4" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" resolve "^1.8.1" semver "^5.5.1" @@ -894,7 +906,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.7.6": +"@babel/runtime@^7.0.0": version "7.7.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== @@ -908,6 +920,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" + integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.4.0", "@babel/template@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" @@ -941,6 +960,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" + integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" From 915c4efeacbfa03dd6e20d66512d71bcf0e14db1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 15:53:32 -0700 Subject: [PATCH 173/282] Ensure a plaintext version of the composer ends up on the clipboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/vector-im/riot-web/issues/12018 --- src/components/views/rooms/BasicMessageComposer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 73c3d961ee..d2b6f8cbc8 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -209,6 +209,7 @@ export default class BasicMessageEditor extends React.Component { const range = getRangeForSelection(this._editorRef, model, selection); const selectedParts = range.parts.map(p => p.serialize()); event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts)); + event.clipboardData.setData("text/plain", text); // so plain copy/paste works if (type === "cut") { // Remove the text, updating the model as appropriate this._modifiedFlag = true; From 6adfa35445e3514c1f2323674949bce242b50da4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 15:53:32 -0700 Subject: [PATCH 174/282] Ensure a plaintext version of the composer ends up on the clipboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/vector-im/riot-web/issues/12018 --- src/components/views/rooms/BasicMessageComposer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 94904242c3..3c67b4ed60 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -208,6 +208,7 @@ export default class BasicMessageEditor extends React.Component { const range = getRangeForSelection(this._editorRef, model, selection); const selectedParts = range.parts.map(p => p.serialize()); event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts)); + event.clipboardData.setData("text/plain", text); // so plain copy/paste works if (type === "cut") { // Remove the text, updating the model as appropriate this._modifiedFlag = true; From c22bfa84b7498635916ad4afc8ce2f16f530bef3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 16:29:55 -0700 Subject: [PATCH 175/282] Hide password section if you can't change your password Fixes https://github.com/vector-im/riot-web/issues/11950 --- .../tabs/user/GeneralUserSettingsTab.js | 18 +++++++++++++++--- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index b9eaa3efa3..1325a1c583 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -70,7 +70,12 @@ export default class GeneralUserSettingsTab extends React.Component { const cli = MatrixClientPeg.get(); const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind(); - this.setState({serverSupportsSeparateAddAndBind}); + + const capabilities = await cli.getCapabilities(); // this is cached + const changePasswordCap = capabilities['m.change_password']; + const canChangePassword = changePasswordCap && changePasswordCap['enabled'] === false; + + this.setState({serverSupportsSeparateAddAndBind, canChangePassword}); this._getThreepidState(); } @@ -280,7 +285,7 @@ export default class GeneralUserSettingsTab extends React.Component { const PhoneNumbers = sdk.getComponent("views.settings.account.PhoneNumbers"); const Spinner = sdk.getComponent("views.elements.Spinner"); - const passwordChangeForm = ( + let passwordChangeForm = ( ; } + let passwordChangeText = _t("Set a new account password..."); + if (!this.state.canChangePassword) { + // Just don't show anything if you can't do anything. + passwordChangeText = null; + passwordChangeForm = null; + } + return (
    {_t("Account")}

    - {_t("Set a new account password...")} + {passwordChangeText}

    {passwordChangeForm} {threepidSection} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c86640132a..099b64dd49 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -665,8 +665,8 @@ "Profile": "Profile", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", - "Account": "Account", "Set a new account password...": "Set a new account password...", + "Account": "Account", "Language and region": "Language and region", "Theme": "Theme", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", From 1394331133c7aedcd8de471422fdcd35b855b922 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 16:33:33 -0700 Subject: [PATCH 176/282] Fix booleans --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 1325a1c583..2d56df6be4 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -73,7 +73,11 @@ export default class GeneralUserSettingsTab extends React.Component { const capabilities = await cli.getCapabilities(); // this is cached const changePasswordCap = capabilities['m.change_password']; - const canChangePassword = changePasswordCap && changePasswordCap['enabled'] === false; + + // You can change your password so long as the capability isn't explicitly disabled. The implicit + // behaviour is you can change your password when the capability is missing or has not-false as + // the enabled flag value. + const canChangePassword = !changePasswordCap || changePasswordCap['enabled'] !== false; this.setState({serverSupportsSeparateAddAndBind, canChangePassword}); From b5f22001b13292768ede9a20a275892e904f1433 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:22:39 -0700 Subject: [PATCH 177/282] Fix copy --- src/components/views/dialogs/InviteDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index a96028e71b..54a067d716 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -985,7 +985,7 @@ export default class InviteDialog extends React.PureComponent { title = _t("Direct Messages"); helpText = _t( - "If you can't find someone, ask them for their username, or share your " + + "If you can't find someone, ask them for their username, share your " + "username (%(userId)s) or profile link.", {userId}, {a: (sub) => {sub}}, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..e59805ccd7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1472,7 +1472,7 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", "Direct Messages": "Direct Messages", - "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", From 442ab9f30170743c26523ca867ac9d2821a979e4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:27:37 -0700 Subject: [PATCH 178/282] Clear the filter when a suggestion is accepted/added --- src/components/views/dialogs/InviteDialog.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 54a067d716..42319b1842 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -713,11 +713,16 @@ export default class InviteDialog extends React.PureComponent { }; _toggleMember = (member: Member) => { + let filterText = this.state.filterText; const targets = this.state.targets.map(t => t); // cheap clone for mutation const idx = targets.indexOf(member); - if (idx >= 0) targets.splice(idx, 1); - else targets.push(member); - this.setState({targets}); + if (idx >= 0) { + targets.splice(idx, 1); + } else { + targets.push(member); + filterText = ""; // clear the filter when the user accepts a suggestion + } + this.setState({targets, filterText}); }; _removeMember = (member: Member) => { @@ -917,7 +922,7 @@ export default class InviteDialog extends React.PureComponent { key={"input"} rows={1} onChange={this._updateFilter} - defaultValue={this.state.filterText} + value={this.state.filterText} ref={this._editorRef} onPaste={this._onPaste} /> From 32f9a4e6231cefbdb9790afcfcd10d2ab5f56ea9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 17:35:36 -0700 Subject: [PATCH 179/282] Add some debugging around the recently DM'd users The suggestions are relatively stable, but the recents have some issues. Adding logging to the suggestions would also destroy the console log with thousands of messages whereas recents aren't too bad. --- src/components/views/dialogs/InviteDialog.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 42319b1842..de11dbf9fa 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -338,19 +338,31 @@ export default class InviteDialog extends React.PureComponent { const recents = []; for (const userId in rooms) { // Filter out user IDs that are already in the room / should be excluded - if (excludedTargetIds.includes(userId)) continue; + if (excludedTargetIds.includes(userId)) { + console.warn(`[Invite:Recents] Excluding ${userId} from recents`); + continue; + } const room = rooms[userId]; const member = room.getMember(userId); - if (!member) continue; // just skip people who don't have memberships for some reason + if (!member) { + // just skip people who don't have memberships for some reason + console.warn(`[Invite:Recents] ${userId} is missing a member object in their own DM (${room.roomId})`); + continue; + } const lastEventTs = room.timeline && room.timeline.length ? room.timeline[room.timeline.length - 1].getTs() : 0; - if (!lastEventTs) continue; // something weird is going on with this room + if (!lastEventTs) { + // something weird is going on with this room + console.warn(`[Invite:Recents] ${userId} (${room.roomId}) has a weird last timestamp: ${lastEventTs}`); + continue; + } recents.push({userId, user: member, lastActive: lastEventTs}); } + if (!recents) console.warn("[Invite:Recents] No recents to suggest!"); // Sort the recents by last active to save us time later recents.sort((a, b) => b.lastActive - a.lastActive); From 1ec746c01411dafa8b2554d8463e2ec5bcd204eb Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 19:53:33 -0700 Subject: [PATCH 180/282] Add verification with QR codes --- src/MatrixClientPeg.js | 2 +- .../elements/crypto/VerificationQRCode.js | 59 +++++++++++++++++++ .../views/right_panel/VerificationPanel.js | 22 ++++++- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/crypto/VerificationQRCode.js diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index dbc570c872..450bec8e77 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -217,7 +217,7 @@ class _MatrixClientPeg { timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), - verificationMethods: [verificationMethods.SAS], + verificationMethods: [verificationMethods.SAS, verificationMethods.QR_CODE_SHOW], unstableClientRelationAggregation: true, identityServer: new IdentityAuthClient(), }; diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js new file mode 100644 index 0000000000..c49dd3faf5 --- /dev/null +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -0,0 +1,59 @@ +/* +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 {replaceableComponent} from "../../../../utils/replaceableComponent"; +import * as qs from "qs"; +import QRCode from "qrcode-react"; + +@replaceableComponent("views.elements.crypto.VerificationQRCode") +export default class VerificationQRCode extends React.PureComponent { + static propTypes = { + // Common for all kinds of QR codes + keys: PropTypes.array.isRequired, // array of [Key ID, Key] pairs + action: PropTypes.string.isRequired, + keyholderUserId: PropTypes.string.isRequired, + + // User verification use case only + secret: PropTypes.string, + otherUserKey: PropTypes.string, + verificationKey: PropTypes.string, + verificationAlgorithms: PropTypes.array, + requestEventId: PropTypes.string, + }; + + static defaultProps = { + action: "verify", + }; + + render() { + const query = { + request: this.props.requestEventId, + action: this.props.action, + verification_algorithms: this.props.verificationAlgorithms, + verification_key: this.props.verificationKey, + other_user_key: this.props.otherUserKey, + }; + for (const key of this.props.keys) { + query[`key_${key[0]}`] = key[1]; + } + + const uri = `https://matrix.to/#/${this.props.keyholderUserId}?${qs.stringify(query)}`; + + return + } +} diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 0d28e1568f..a2038087fc 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -17,6 +17,9 @@ limitations under the License. import React from 'react'; import * as sdk from '../../../index'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; +import VerificationQRCode from "../elements/crypto/VerificationQRCode"; +import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; export default class VerificationPanel extends React.PureComponent { constructor(props) { @@ -36,15 +39,30 @@ export default class VerificationPanel extends React.PureComponent { renderStatus() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const Spinner = sdk.getComponent('elements.Spinner'); - const {request} = this.props; + const {request: req} = this.props; + const request: VerificationRequest = req; if (request.requested) { return (

    Waiting for {request.otherUserId} to accept ...

    ); } else if (request.ready) { + const qrCodeKeys = [ + [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], + [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningKey("master")], + ]; + // TODO: Await a bunch of this + const otherCrossSigning = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + const otherUserKey = otherCrossSigning ? otherCrossSigning.getCrossSigningKey("master") : null; + const qrCode = ; const verifyButton = Verify by emoji ; - return (

    {request.otherUserId} is ready, start {verifyButton}

    ); + return (

    {request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

    ); } else if (request.started) { if (this.state.sasWaitingForOtherParty) { return

    Waiting for {request.otherUserId} to confirm ...

    ; From 586a240e41721651ae661abb216de865504672bd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 23:10:35 -0700 Subject: [PATCH 181/282] Don't use private keys for QR code --- src/components/views/right_panel/VerificationPanel.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index a2038087fc..34f7538b1c 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -45,17 +45,15 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

    Waiting for {request.otherUserId} to accept ...

    ); } else if (request.ready) { + const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningKey("master")], + [keyId, MatrixClientPeg.get().getCrossSigningId()], ]; - // TODO: Await a bunch of this - const otherCrossSigning = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); - const otherUserKey = otherCrossSigning ? otherCrossSigning.getCrossSigningKey("master") : null; const qrCode = ; From b682e7d908a8e6b3c84a0e77977604bd85709bf6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:05:32 -0700 Subject: [PATCH 182/282] Generate a QR code for apps to scan --- .../elements/crypto/VerificationQRCode.js | 8 ++--- .../views/right_panel/VerificationPanel.js | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index c49dd3faf5..fa4031da9f 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -24,15 +24,13 @@ import QRCode from "qrcode-react"; export default class VerificationQRCode extends React.PureComponent { static propTypes = { // Common for all kinds of QR codes - keys: PropTypes.array.isRequired, // array of [Key ID, Key] pairs + keys: PropTypes.array.isRequired, // array of [Key ID, Base64 Key] pairs action: PropTypes.string.isRequired, keyholderUserId: PropTypes.string.isRequired, // User verification use case only secret: PropTypes.string, - otherUserKey: PropTypes.string, - verificationKey: PropTypes.string, - verificationAlgorithms: PropTypes.array, + otherUserKey: PropTypes.string, // Base64 key being verified requestEventId: PropTypes.string, }; @@ -44,8 +42,6 @@ export default class VerificationQRCode extends React.PureComponent { const query = { request: this.props.requestEventId, action: this.props.action, - verification_algorithms: this.props.verificationAlgorithms, - verification_key: this.props.verificationKey, other_user_key: this.props.otherUserKey, }; for (const key of this.props.keys) { diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 34f7538b1c..d0c3d2df10 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -45,22 +45,28 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

    Waiting for {request.otherUserId} to accept ...

    ); } else if (request.ready) { - const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; - const qrCodeKeys = [ - [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [keyId, MatrixClientPeg.get().getCrossSigningId()], - ]; - const qrCode = ; const verifyButton = Verify by emoji ; - return (

    {request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

    ); + + if (request.requestEvent && request.requestEvent.getId()) { + const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; + const qrCodeKeys = [ + [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], + [keyId, MatrixClientPeg.get().getCrossSigningId()], + ]; + const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + const qrCode = ; + return (

    {request.otherUserId} is ready, start {verifyButton} or have them scan: {qrCode}

    ); + } + + return (

    {request.otherUserId} is ready, start {verifyButton}

    ); } else if (request.started) { if (this.state.sasWaitingForOtherParty) { return

    Waiting for {request.otherUserId} to confirm ...

    ; From ebcc4d573bacac10c991db7df788cc28b1b73a8a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:10:31 -0700 Subject: [PATCH 183/282] Add trace logging to figure out which component is causing weird events For https://github.com/vector-im/riot-web/issues/11120 --- src/components/views/rooms/EventTile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 634b77c9e1..155ed19821 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -578,6 +578,7 @@ export default createReactClass({ console.error("EventTile attempted to get relations for an event without an ID"); // Use event's special `toJSON` method to log key data. console.log(JSON.stringify(this.props.mxEvent, null, 4)); + console.trace("Stacktrace for https://github.com/vector-im/riot-web/issues/11120"); } return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); }, From 0ce9da5d365d4b2e1490206233c8ecfa6dc01eed Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:12:24 -0700 Subject: [PATCH 184/282] Appease the linter --- src/components/views/elements/crypto/VerificationQRCode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index fa4031da9f..9026e51096 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -50,6 +50,6 @@ export default class VerificationQRCode extends React.PureComponent { const uri = `https://matrix.to/#/${this.props.keyholderUserId}?${qs.stringify(query)}`; - return + return ; } } From b7e680ba5154935f3f8a394ceaeeb0bda307c29b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 23 Jan 2020 20:14:27 -0700 Subject: [PATCH 185/282] Fix key ID --- src/components/views/right_panel/VerificationPanel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index d0c3d2df10..a29546c8f7 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -50,10 +50,9 @@ export default class VerificationPanel extends React.PureComponent { ; if (request.requestEvent && request.requestEvent.getId()) { - const keyId = `ed25519:${MatrixClientPeg.get().getCrossSigningId()}`; const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [keyId, MatrixClientPeg.get().getCrossSigningId()], + [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningId()], ]; const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); const qrCode = Date: Thu, 23 Jan 2020 20:19:17 -0700 Subject: [PATCH 186/282] Actually add the secret to the QR code url --- src/components/views/elements/crypto/VerificationQRCode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index 9026e51096..1cb5647317 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -43,6 +43,7 @@ export default class VerificationQRCode extends React.PureComponent { request: this.props.requestEventId, action: this.props.action, other_user_key: this.props.otherUserKey, + secret: this.props.secret, }; for (const key of this.props.keys) { query[`key_${key[0]}`] = key[1]; From 86a098fcd983d546b18f1a1eba9e2d1e1f1e6f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:35:31 +0100 Subject: [PATCH 187/282] DisableEventIndex: Remove a blank line and rewrite a doc comment. --- .../views/dialogs/eventindex/DisableEventIndex.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js index 064f55edc0..5720c26e40 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -24,10 +24,8 @@ import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; - /* - * Walks the user through the process of creating an e2e key backup - * on the server. + * Allows the user to disable the Event Index. */ export default class ManageEventIndex extends React.Component { static propTypes = { From d30fd3eac04ecedd1bbd8dda8b0a38b49f77444d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:39:56 +0100 Subject: [PATCH 188/282] DisableEventIndex: Rename the class. --- .../views/dialogs/eventindex/DisableEventIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js index 5720c26e40..820bcbe047 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndex.js @@ -27,7 +27,7 @@ import AccessibleButton from "../../../../components/views/elements/AccessibleBu /* * Allows the user to disable the Event Index. */ -export default class ManageEventIndex extends React.Component { +export default class DisableEventIndexDialog extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, } From 4ea2d4f90eff09481a75366740274cecb5326b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:45:29 +0100 Subject: [PATCH 189/282] ManageEventIndex: Rewrite the docs and rename the dialog class. --- .../views/dialogs/eventindex/ManageEventIndex.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index d0f3c2cf50..5d75f9b168 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -24,12 +24,10 @@ import {formatBytes} from "../../../../utils/FormattingUtils"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; - /* - * Walks the user through the process of creating an e2e key backup - * on the server. + * Allows the user to introspect the event index state and disable it. */ -export default class ManageEventIndex extends React.Component { +export default class ManageEventIndexDialog extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, } From f763ae3c7bec35d78097249e58859cb55bfaadda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:50:58 +0100 Subject: [PATCH 190/282] DisableEventIndex: Rename the file to contain the Dialog suffix. --- .../{DisableEventIndex.js => DisableEventIndexDialog.js} | 0 .../views/dialogs/eventindex/ManageEventIndex.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/async-components/views/dialogs/eventindex/{DisableEventIndex.js => DisableEventIndexDialog.js} (100%) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndex.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js similarity index 100% rename from src/async-components/views/dialogs/eventindex/DisableEventIndex.js rename to src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js index 5d75f9b168..c9c4fc8ae6 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndex.js @@ -96,7 +96,7 @@ export default class ManageEventIndexDialog extends React.Component { _onDisable = async () => { Modal.createTrackedDialogAsync("Disable message search", "Disable message search", - import("./DisableEventIndex"), + import("./DisableEventIndexDialog"), null, null, /* priority = */ false, /* static = */ true, ); } From 93facca47963ce17ae706f80150333a98ff4dfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:54:46 +0100 Subject: [PATCH 191/282] ManageEventIndex: Rename the file to contain the Dialog suffix. --- .../{ManageEventIndex.js => ManageEventIndexDialog.js} | 0 src/components/views/settings/EventIndexPanel.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/async-components/views/dialogs/eventindex/{ManageEventIndex.js => ManageEventIndexDialog.js} (100%) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndex.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js similarity index 100% rename from src/async-components/views/dialogs/eventindex/ManageEventIndex.js rename to src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index f93ab489c7..785ce2a4ca 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -85,7 +85,7 @@ export default class EventIndexPanel extends React.Component { _onManage = async () => { Modal.createTrackedDialogAsync('Message search', 'Message search', - import('../../../async-components/views/dialogs/eventindex/ManageEventIndex'), + import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'), { onFinished: () => {}, }, null, /* priority = */ false, /* static = */ true, From b59863781f703997058ab8bd4af9acab349d8e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:58:17 +0100 Subject: [PATCH 192/282] DisableEventIndexDialog: Fix a typo. --- .../views/dialogs/eventindex/DisableEventIndexDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 820bcbe047..7f46b54f65 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -60,7 +60,7 @@ export default class DisableEventIndexDialog extends React.Component { onFinished={this.props.onFinished} title={_t("Are you sure?")} > - {_t("If disabled, messages form encrypted rooms won't appear in search results")} + {_t("If disabled, messages from encrypted rooms won't appear in search results.")}
    {_t("Cancel")} From 72a58d0c2c274ee6b7a874205e3a8ee13ade4f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 09:59:26 +0100 Subject: [PATCH 193/282] DisableEventIndexDialog: Properly indent the content of the BaseDialog. --- .../eventindex/DisableEventIndexDialog.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 7f46b54f65..7097ffb3bf 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -60,16 +60,16 @@ export default class DisableEventIndexDialog extends React.Component { onFinished={this.props.onFinished} title={_t("Are you sure?")} > - {_t("If disabled, messages from encrypted rooms won't appear in search results.")} -
    - - {_t("Cancel")} - - - {_t("Disable")} - - {this.state.enabling ? :
    } -
    + {_t("If disabled, messages from encrypted rooms won't appear in search results.")} +
    + + {_t("Cancel")} + + + {_t("Disable")} + + {this.state.enabling ? :
    } +
    ); } From cd4e75c378d748d6cf42acf2fb0b43e6328b1ed1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 09:36:22 +0000 Subject: [PATCH 194/282] Unnecessary blank line --- src/components/views/auth/AuthBody.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/auth/AuthBody.js b/src/components/views/auth/AuthBody.js index db5c941b20..fe20d76afb 100644 --- a/src/components/views/auth/AuthBody.js +++ b/src/components/views/auth/AuthBody.js @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; - import classnames from 'classnames'; export default class AuthBody extends React.PureComponent { From 99252e08b0eb3800b9aa79be9e000f803b04aeb0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 09:37:43 +0000 Subject: [PATCH 195/282] Unintentional commit --- src/components/views/settings/KeyBackupPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index cd8cdfa5d9..fd166782d8 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -127,7 +127,7 @@ export default class KeyBackupPanel extends React.PureComponent { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), { - secureSecretStorage: false, + secureSecretStorage: SettingsStore.isFeatureEnabled("feature_cross_signing"), onFinished: () => { this._loadBackupStatus(); }, From 169852adec208ce8451072ff9a08c5e9021920b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 24 Jan 2020 08:23:03 +0000 Subject: [PATCH 196/282] Translated using Weblate (French) Currently translated at 100.0% (2053 of 2053 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 472cd02e6f..5d02717b0c 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2085,5 +2085,22 @@ "Unrecognised command: %(commandText)s": "Commande non reconnue : %(commandText)s", "You can use /help to list available commands. Did you mean to send this as a message?": "Vous pouvez utiliser /help pour obtenir la liste des commandes disponibles. Vouliez-vous envoyer un message ?", "Hint: Begin your message with // to start it with a slash.": "Astuce : Votre message doit démarrer par // pour commencer par une barre oblique.", - "Send as message": "Envoyer comme message" + "Send as message": "Envoyer comme message", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s a ajouté %(addedAddresses)s et %(count)s autres adresses à ce salon", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s a supprimé %(removedAddresses)s et %(count)s autres adresses de ce salon", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s a supprimé %(countRemoved)s et ajouté %(countAdded)s adresses à ce salon", + "Reject & Ignore user": "Rejeter et ignorer l’utilisateur", + "Enter your account password to confirm the upgrade:": "Saisissez le mot de passe de votre compte pour confirmer la mise à niveau :", + "You'll need to authenticate with the server to confirm the upgrade.": "Vous devrez vous identifier avec le serveur pour confirmer la mise à niveau.", + "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Mettez à niveau cet appareil pour lui permettre de vérifier d’autres appareils, qui pourront alors accéder aux messages chiffrés et seront vus comme fiables par les autres utilisateurs.", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Configurez le chiffrement sur cet appareil pour lui permettre de vérifier d’autres appareils, qui pourront alors accéder aux messages chiffrés et seront vus comme fiables par les autres utilisateurs.", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Sécurisez vos clés de chiffrement avec une phrase de passe. Pour une sécurité maximale, elle devrait être différente du mot de passe de votre compte :", + "Enter a passphrase": "Saisissez une phrase de passe", + "Enter your passphrase a second time to confirm it.": "Saisissez votre phrase de passe une seconde fois pour la confirmer.", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Cet appareil peut à présent vérifier d’autres appareils, qui pourront alors accéder aux messages chiffrés et seront vus comme fiables par les autres utilisateurs.", + "Verify other users in their profile.": "Vérifiez d’autres utilisateurs dans leur profil.", + "Upgrade your encryption": "Mettre à niveau votre chiffrement", + "Set up encryption": "Configurer le chiffrement", + "Encryption upgraded": "Chiffrement mis à niveau", + "Encryption setup complete": "Configuration du chiffrement terminé" } From cba7764784e1b60365cfcac0ea8f5ea3310dcaa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:00:28 +0100 Subject: [PATCH 197/282] DisableEventIndexDialog: Use the DialogButtons element for the buttons. --- .../eventindex/DisableEventIndexDialog.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 7097ffb3bf..2e0f97c705 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -53,7 +53,8 @@ export default class DisableEventIndexDialog extends React.Component { render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); + const Spinner = sdk.getComponent('elements.Spinner'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return ( {_t("If disabled, messages from encrypted rooms won't appear in search results.")} -
    - - {_t("Cancel")} - - - {_t("Disable")} - - {this.state.enabling ? :
    } -
    + {this.state.disabling ? :
    } + + ); } From 71024d14188cb70965eb69b64c7f8fcf384a39eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:03:40 +0100 Subject: [PATCH 198/282] ManageEventIndexDialog: Properly indent the content of the BaseDialog. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index c9c4fc8ae6..f435cf050f 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -150,8 +150,8 @@ export default class ManageEventIndexDialog extends React.Component { onFinished={this.props.onFinished} title={_t("Message search")} > - {eventIndexingSettings} - {buttons} + {eventIndexingSettings} + {buttons} ); } From 128c0b73004e27cb6d5ff16da01f48a99785d72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:11:53 +0100 Subject: [PATCH 199/282] ManageEventIndexDialog: Use formatCount to format the message and room count. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index f435cf050f..a8fc7dce3a 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -20,7 +20,7 @@ import PropTypes from 'prop-types'; import { _t } from '../../../../languageHandler'; import Modal from '../../../../Modal'; -import {formatBytes} from "../../../../utils/FormattingUtils"; +import {formatBytes, formatCount} from "../../../../utils/FormattingUtils"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; @@ -125,8 +125,8 @@ export default class ManageEventIndexDialog extends React.Component { }
    {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
    - {_t("Indexed messages:")} {this.state.eventCount}
    - {_t("Number of rooms:")} {this.state.roomCount}
    + {_t("Indexed messages:")} {formatCount(this.state.eventCount)}
    + {_t("Number of rooms:")} {formatCount(this.state.roomCount)}
    {crawlerState}
    From b72ab57e1b33d443b40a56e73645044233a736f5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 24 Jan 2020 10:13:03 +0000 Subject: [PATCH 200/282] add to --- src/components/views/rooms/E2EIcon.js | 40 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 7ac3b5af2d..df5fe204d4 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -15,13 +15,14 @@ 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 classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import AccessibleButton from '../elements/AccessibleButton'; import {useFeatureEnabled} from "../../../hooks/useSettings"; +import AccessibleButton from "../elements/AccessibleButton"; +import Tooltip from "../elements/Tooltip"; export const E2E_STATE = { VERIFIED: "verified", @@ -51,7 +52,9 @@ const legacyRoomTitles = { }; const E2EIcon = ({isUser, status, className, size, onClick}) => { - const e2eIconClasses = classNames({ + const [hover, setHover] = useState(false); + + const classes = classNames({ mx_E2EIcon: true, mx_E2EIcon_warning: status === E2E_STATE.WARNING, mx_E2EIcon_normal: status === E2E_STATE.NORMAL, @@ -70,17 +73,36 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => { e2eTitle = legacyRoomTitles[status]; } - let style = null; + let style; if (size) { style = {width: `${size}px`, height: `${size}px`}; } - const icon = (
    ); - if (onClick) { - return ({ icon }); - } else { - return icon; + const onMouseOver = () => setHover(true); + const onMouseOut = () => setHover(false); + + let tip; + if (hover) { + tip = ; } + + if (onClick) { + return ( + + { tip } + + ); + } + + return
    + { tip } +
    ; }; E2EIcon.propTypes = { From 660240e2c047a964f2556cd2d63145f15ddc963e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:13:09 +0100 Subject: [PATCH 201/282] EventIndexPanel: Use formatCount to format the room count. --- src/components/views/settings/EventIndexPanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 785ce2a4ca..13063d9305 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -21,7 +21,7 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import AccessibleButton from "../elements/AccessibleButton"; -import {formatBytes} from "../../../utils/FormattingUtils"; +import {formatBytes, formatCount} from "../../../utils/FormattingUtils"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; export default class EventIndexPanel extends React.Component { @@ -115,7 +115,7 @@ export default class EventIndexPanel extends React.Component { {_t( "Securely cache encrypted messages locally for them " + "to appear in search results, using ") } {formatBytes(this.state.eventIndexSize, 0)} - {_t( " to store messages from ")} {this.state.roomCount} {_t("rooms.")} + {_t( " to store messages from ")} {formatCount(this.state.roomCount)} {_t("rooms.")}
    From d9e933c915534456fead4641841e5b5ff9632a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:15:57 +0100 Subject: [PATCH 202/282] EventIndex: Style fixes for the docstrings. --- src/indexing/EventIndex.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index d466c6acba..53f47148b9 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -69,7 +69,8 @@ export default class EventIndex { client.removeListener('Room.timelineReset', this.onTimelineReset); } - /** Get crawler checkpoints for the encrypted rooms and store them in the index. + /** + * Get crawler checkpoints for the encrypted rooms and store them in the index. */ async addInitialCheckpoints() { const indexManager = PlatformPeg.get().getEventIndexingManager(); @@ -80,7 +81,7 @@ export default class EventIndex { return client.isRoomEncrypted(room.roomId); }; - // We only care to crawl the encrypted rooms, non-encrypted. + // We only care to crawl the encrypted rooms, non-encrypted // rooms can use the search provided by the homeserver. const encryptedRooms = rooms.filter(isRoomEncrypted); From 825b6f7b7d6848b4963dcbfc335c829779dfd40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:16:49 +0100 Subject: [PATCH 203/282] EventIndexPeg: Style fix for a docstring. --- src/indexing/EventIndexPeg.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index c8c8cbefe9..067aea3a7f 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -62,7 +62,8 @@ class EventIndexPeg { return this.initEventIndex(); } - /* Initialize the event index. + /** + * Initialize the event index. * * @returns {boolean} True if the event index was succesfully initialized, * false otherwise. From 6f919eaeec0aaf96bd5cea50e9a714f3b2c023d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:27:56 +0100 Subject: [PATCH 204/282] DisableEventIndexDialog: Use the correct spinner. --- .../views/dialogs/eventindex/DisableEventIndexDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 2e0f97c705..f031c5a11f 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -62,7 +62,7 @@ export default class DisableEventIndexDialog extends React.Component { title={_t("Are you sure?")} > {_t("If disabled, messages from encrypted rooms won't appear in search results.")} - {this.state.disabling ? :
    } + {this.state.disabling ? :
    } Date: Fri, 24 Jan 2020 11:28:33 +0100 Subject: [PATCH 205/282] EventIndex: Subclass the event emitter instead of putting one in a property. --- src/indexing/EventIndex.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 53f47148b9..85e75cfc82 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -22,8 +22,9 @@ import {EventEmitter} from "events"; /* * Event indexing class that wraps the platform specific event indexing. */ -export default class EventIndex { +export default class EventIndex extends EventEmitter { constructor() { + super(); this.crawlerCheckpoints = []; // The time in ms that the crawler will wait loop iterations if there // have not been any checkpoints to consume in the last iteration. @@ -35,7 +36,6 @@ export default class EventIndex { this._crawler = null; this._currentCheckpoint = null; this.liveEventsForIndex = new Set(); - this._eventEmitter = new EventEmitter(); } async init() { @@ -188,7 +188,7 @@ export default class EventIndex { } emitNewCheckpoint() { - this._eventEmitter.emit("changedCheckpoint", this.currentRoom()); + this.emit("changedCheckpoint", this.currentRoom()); } async crawlerFunc() { @@ -459,12 +459,4 @@ export default class EventIndex { return client.getRoom(this.crawlerCheckpoints[0].roomId); } } - - on(eventName, callback) { - this._eventEmitter.on(eventName, callback); - } - - removeListener(eventName, callback) { - this._eventEmitter.removeListener(eventName, callback); - } } From ffe5d411db9a7ae02ead2f5e78a8741be08ec5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:44:56 +0100 Subject: [PATCH 206/282] EventIndexPanel: Add a link to the download page of Riot Desktop. --- src/components/views/settings/EventIndexPanel.js | 8 ++++++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 13063d9305..a6f21fa114 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -145,8 +145,12 @@ export default class EventIndexPanel extends React.Component {
    { _t( "Riot can't securely cache encrypted messages locally " + - "while running in a web browser. Use Riot Desktop for " + - "encrypted messages to appear in search results.", + "while running in a web browser. Use Riot Desktop " + + "for encrypted messages to appear in search results.", + {}, + { + 'riotLink': (sub) => {sub}, + } ) }
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3973ae442e..fceb299131 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -560,7 +560,7 @@ "Manage": "Manage", "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", "Enable": "Enable", - "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.", + "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.", "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.", @@ -2037,7 +2037,7 @@ "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", "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.": "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.", - "If disabled, messages form encrypted rooms won't appear in search results": "If disabled, messages form encrypted rooms won't appear in search results", + "If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.", "Disable": "Disable", "Not downloading messages for any room.": "Not downloading messages for any room.", "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", From 0c3d507455bbf77d956e2f6c303e08ef6d9d8c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:46:46 +0100 Subject: [PATCH 207/282] EventIndex: Cancel the crawler early after a message request. If we're cancelling the crawler nowadays this means that we're likely deleting the index. Processing these messages is wasted effort in that case so break early. --- src/indexing/EventIndex.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 85e75cfc82..723d516a29 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -266,6 +266,11 @@ export default class EventIndex extends EventEmitter { continue; } + if (cancelled) { + this.crawlerCheckpoints.push(checkpoint); + break; + } + if (res.chunk.length === 0) { console.log("EventIndex: Done with the checkpoint", checkpoint); // We got to the start/end of our timeline, lets just From ee133a9c715559e33c058c58ce8776574816fafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:56:19 +0100 Subject: [PATCH 208/282] DisableEventIndexDialog: Remove an unused import. --- .../views/dialogs/eventindex/DisableEventIndexDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index f031c5a11f..c2b7e2933e 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -22,7 +22,6 @@ import { _t } from '../../../../languageHandler'; import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; -import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; /* * Allows the user to disable the Event Index. From 029369a04bd08f2fdb8944ff72be50e79050784a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 11:56:43 +0100 Subject: [PATCH 209/282] EventIndexPanel: Small style fix. --- src/components/views/settings/EventIndexPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index a6f21fa114..5edc25bbab 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -150,7 +150,7 @@ export default class EventIndexPanel extends React.Component { {}, { 'riotLink': (sub) => {sub}, - } + }, ) }
    From 2483337e8993463f87be946b078ffa08a6f514ca Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 11:57:57 +0100 Subject: [PATCH 210/282] don't use removed .event property anymore on verification request --- .../messages/MKeyVerificationConclusion.js | 2 +- .../views/messages/MKeyVerificationRequest.js | 10 +++--- .../views/toasts/VerificationRequestToast.js | 33 +++++++++---------- src/utils/KeyVerificationStateObserver.js | 7 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.js index f51b97786b..a17dcd8ab0 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.js @@ -93,7 +93,7 @@ export default class MKeyVerificationConclusion extends React.Component { } if (title) { - const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent); + const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId()); const classes = classNames("mx_EventTile_bubble", "mx_KeyVerification", "mx_KeyVerification_icon", { mx_KeyVerification_icon_verified: request.done, }); diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index ae793556d8..8caff322aa 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -85,7 +85,7 @@ export default class MKeyVerificationRequest extends React.Component { if (userId === myUserId) { return _t("You accepted"); } else { - return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())}); } } @@ -95,7 +95,7 @@ export default class MKeyVerificationRequest extends React.Component { if (userId === myUserId) { return _t("You cancelled"); } else { - return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())}); } } @@ -129,9 +129,9 @@ export default class MKeyVerificationRequest extends React.Component { if (!request.initiatedByMe) { title = (
    { - _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}
    ); + _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId())})}
    ); subtitle = (
    { - userLabelForEventRoom(request.requestingUserId, mxEvent)}
    ); + userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}
    ); if (request.requested && !request.observeOnly) { stateNode = (
    @@ -142,7 +142,7 @@ export default class MKeyVerificationRequest extends React.Component { title = (
    { _t("You sent a verification request")}
    ); subtitle = (
    { - userLabelForEventRoom(request.receivingUserId, mxEvent)}
    ); + userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId())}
    ); } if (title) { diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 479a3e3f93..274085c0fd 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -65,22 +65,21 @@ export default class VerificationRequestToast extends React.PureComponent { accept = async () => { ToastStore.sharedInstance().dismissToast(this.props.toastKey); const {request} = this.props; - const {event} = request; // no room id for to_device requests - if (event.getRoomId()) { - dis.dispatch({ - action: 'view_room', - room_id: event.getRoomId(), - should_peek: false, - }); - } try { - await request.accept(); - dis.dispatch({ - action: "set_right_panel_phase", - phase: RIGHT_PANEL_PHASES.EncryptionPanel, - refireParams: {verificationRequest: request}, - }); + if (request.channel.roomId) { + dis.dispatch({ + action: 'view_room', + room_id: request.channel.roomId, + should_peek: false, + }); + await request.accept(); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {verificationRequest: request}, + }); + } } catch (err) { console.error(err.message); } @@ -89,13 +88,13 @@ export default class VerificationRequestToast extends React.PureComponent { render() { const FormButton = sdk.getComponent("elements.FormButton"); const {request} = this.props; - const {event} = request; const userId = request.otherUserId; - let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId; + const roomId = request.channel.roomId; + let nameLabel = roomId ? userLabelForEventRoom(userId, roomId) : userId; // for legacy to_device verification requests if (nameLabel === userId) { const client = MatrixClientPeg.get(); - const user = client.getUser(event.getSender()); + const user = client.getUser(userId); if (user && user.displayName) { nameLabel = _t("%(name)s (%(userId)s)", {name: user.displayName, userId}); } diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 1a35319186..7da532109c 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -17,16 +17,15 @@ limitations under the License. import {MatrixClientPeg} from '../MatrixClientPeg'; import { _t } from '../languageHandler'; -export function getNameForEventRoom(userId, mxEvent) { - const roomId = mxEvent.getRoomId(); +export function getNameForEventRoom(userId, roomId) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); const member = room.getMember(userId); return member ? member.name : userId; } -export function userLabelForEventRoom(userId, mxEvent) { - const name = getNameForEventRoom(userId, mxEvent); +export function userLabelForEventRoom(userId, roomId) { + const name = getNameForEventRoom(userId, roomId); if (name !== userId) { return _t("%(name)s (%(userId)s)", {name, userId}); } else { From 512bd4b0b2afb62d50cf6bb179b6bc8b619887c2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 11:59:17 +0100 Subject: [PATCH 211/282] show to_device verification in dialog, not right panel this also makes it work again, as the VerificationPanel doens't know how to deal with requests that are already in PHASE_STARTED, which was breaking verifying your own devices. --- src/components/views/toasts/VerificationRequestToast.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 274085c0fd..f912984486 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -23,6 +23,7 @@ import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import dis from "../../../dispatcher"; import ToastStore from "../../../stores/ToastStore"; +import Modal from "../../../Modal"; export default class VerificationRequestToast extends React.PureComponent { constructor(props) { @@ -79,6 +80,12 @@ export default class VerificationRequestToast extends React.PureComponent { phase: RIGHT_PANEL_PHASES.EncryptionPanel, refireParams: {verificationRequest: request}, }); + } else if (request.channel.deviceId && request.verifier) { + // show to_device verifications in dialog still + const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { + verifier: request.verifier, + }, null, /* priority = */ false, /* static = */ true); } } catch (err) { console.error(err.message); From 395c82b1e5042e915cf3d1184ace4d23ea2ad240 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 11:04:40 +0000 Subject: [PATCH 212/282] Update src/components/structures/RoomView.js Co-Authored-By: J. Ryan Stinnett --- src/components/structures/RoomView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index f10c98dd98..4c88f533f0 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -796,7 +796,7 @@ export default createReactClass({ return; } - // Duplication betwen here and _updateE2eStatus in RoomTile + // Duplication between here and _updateE2eStatus in RoomTile /* At this point, the user has encryption on and cross-signing on */ const e2eMembers = await room.getEncryptionTargetMembers(); const verified = []; From ecfecfe559e2eda9c8cc326913a5103d19fb9ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 12:07:03 +0100 Subject: [PATCH 213/282] EventIndex: Fix a small style issue. --- src/indexing/EventIndex.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 93c640cf8e..2b432ab1a1 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,7 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -308,7 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 6cce65a2a59544ef21b77744856ad2875abfb1ad Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 12:08:47 +0100 Subject: [PATCH 214/282] fix lint --- src/components/views/messages/MKeyVerificationRequest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 8caff322aa..49f871d16e 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -128,8 +128,9 @@ export default class MKeyVerificationRequest extends React.Component { } if (!request.initiatedByMe) { + const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()); title = (
    { - _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId())})}
    ); + _t("%(name)s wants to verify", {name})}
    ); subtitle = (
    { userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}
    ); if (request.requested && !request.observeOnly) { From a6fcbcacf68d0d01ead7edaad9ad33dbbd1dfbcb Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 12:04:08 +0000 Subject: [PATCH 215/282] Seperates out the padlock icon, and adds a tooltip Fixes https://github.com/vector-im/riot-web/issues/11840 --- res/css/_components.scss | 1 + res/css/views/rooms/_InviteOnlyIcon.scss | 38 +++++++++++++++ res/css/views/rooms/_RoomHeader.scss | 21 -------- res/css/views/rooms/_RoomTile.scss | 25 +--------- src/components/views/rooms/InviteOnlyIcon.js | 51 ++++++++++++++++++++ src/components/views/rooms/RoomHeader.js | 12 +++-- src/components/views/rooms/RoomTile.js | 6 ++- src/i18n/strings/en_EN.json | 1 + 8 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 res/css/views/rooms/_InviteOnlyIcon.scss create mode 100644 src/components/views/rooms/InviteOnlyIcon.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 60f749de9c..07e92bdc7b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -148,6 +148,7 @@ @import "./views/rooms/_AuxPanel.scss"; @import "./views/rooms/_BasicMessageComposer.scss"; @import "./views/rooms/_E2EIcon.scss"; +@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; diff --git a/res/css/views/rooms/_InviteOnlyIcon.scss b/res/css/views/rooms/_InviteOnlyIcon.scss new file mode 100644 index 0000000000..e70586bb73 --- /dev/null +++ b/res/css/views/rooms/_InviteOnlyIcon.scss @@ -0,0 +1,38 @@ +/* +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_InviteOnlyIcon { + width: 12px; + height: 12px; + position: relative; + display: block !important; + // Align the padlock with unencrypted room names + margin-left: 6px; + + &::before { + background-color: $roomtile-name-color; + mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 6bfcd437c1..0ac2e99b97 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -269,24 +269,3 @@ limitations under the License. .mx_RoomHeader_pinsIndicatorUnread { background-color: $pinned-unread-color; } - -.mx_RoomHeader_PrivateIcon.mx_RoomHeader_isPrivate { - width: 12px; - height: 12px; - position: relative; - display: block !important; - - &::before { - background-color: $roomtile-name-color; - mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 376f4370e3..a24fdf2629 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -215,30 +215,7 @@ limitations under the License. flex: 1; } -.mx_RoomTile.mx_RoomTile.mx_RoomTile_isPrivate .mx_RoomTile_name { +.mx_InviteOnlyIcon + .mx_RoomTile_nameContainer .mx_RoomTile_name { // Scoot the padding in a bit from 6px to make it look better padding-left: 3px; } - -.mx_RoomTile.mx_RoomTile_isPrivate .mx_RoomTile_PrivateIcon { - width: 12px; - height: 12px; - position: relative; - display: block !important; - // Align the padlock with unencrypted room names - margin-left: 6px; - - &::before { - background-color: $roomtile-name-color; - mask-image: url('$(res)/img/feather-customised/lock-solid.svg'); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} diff --git a/src/components/views/rooms/InviteOnlyIcon.js b/src/components/views/rooms/InviteOnlyIcon.js new file mode 100644 index 0000000000..5afaa7f0f2 --- /dev/null +++ b/src/components/views/rooms/InviteOnlyIcon.js @@ -0,0 +1,51 @@ +/* +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 { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; + +export default class InviteOnlyIcon extends React.Component { + constructor() { + super(); + + this.state = { + hover: false, + }; + } + + onHoverStart = () => { + this.setState({hover: true}); + }; + + onHoverEnd = () => { + this.setState({hover: false}); + }; + + render() { + const Tooltip = sdk.getComponent("elements.Tooltip"); + let tooltip; + if (this.state.hover) { + tooltip = ; + } + return (
    + { tooltip } +
    ); + } +} diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 15f0daa200..8a427e1c06 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -32,6 +32,7 @@ import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; import E2EIcon from './E2EIcon'; +import InviteOnlyIcon from './InviteOnlyIcon'; export default createReactClass({ displayName: 'RoomHeader', @@ -162,11 +163,12 @@ export default createReactClass({ const joinRules = this.props.room && this.props.room.currentState.getStateEvents("m.room.join_rules", ""); const joinRule = joinRules && joinRules.getContent().join_rule; - const joinRuleClass = classNames("mx_RoomHeader_PrivateIcon", - {"mx_RoomHeader_isPrivate": joinRule === "invite"}); - const privateIcon = SettingsStore.isFeatureEnabled("feature_cross_signing") ? -
    : - undefined; + let privateIcon; + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (joinRule == "invite") { + privateIcon = ; + } + } if (this.props.onCancelClick) { cancelButton = ; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 0b50d85ff6..4e1a4c7b23 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -34,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {_t} from "../../../languageHandler"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import E2EIcon from './E2EIcon'; +import InviteOnlyIcon from './InviteOnlyIcon'; // eslint-disable-next-line camelcase import rate_limited_func from '../../../ratelimitedfunc'; @@ -411,7 +412,6 @@ export default createReactClass({ 'mx_RoomTile_noBadges': !badges, 'mx_RoomTile_transparent': this.props.transparent, 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, - 'mx_RoomTile_isPrivate': this.state.joinRule == "invite" && !dmUserId, }); const avatarClasses = classNames({ @@ -523,7 +523,9 @@ export default createReactClass({ let privateIcon = null; if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - privateIcon =
    ; + if (this.state.joinRule == "invite" && !dmUserId) { + privateIcon = ; + } } let e2eIcon = null; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..456d48a94c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -908,6 +908,7 @@ "Unencrypted": "Unencrypted", "Encrypted by a deleted device": "Encrypted by a deleted device", "Please select the destination room for this message": "Please select the destination room for this message", + "Invite only": "Invite only", "Scroll to bottom of page": "Scroll to bottom of page", "Close preview": "Close preview", "device id: ": "device id: ", From 688f7029d292dcbcaace4eefeec28837e2978041 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:28:03 +0000 Subject: [PATCH 216/282] Split AsyncWrapper out from Modal So we can use it outside of modals & dialogs --- src/AsyncWrapper.js | 93 +++++++++++++++++++++++++++++++++++++++++++++ src/Modal.js | 77 +------------------------------------ 2 files changed, 95 insertions(+), 75 deletions(-) create mode 100644 src/AsyncWrapper.js diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js new file mode 100644 index 0000000000..63b856a882 --- /dev/null +++ b/src/AsyncWrapper.js @@ -0,0 +1,93 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +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 createReactClass from 'create-react-class'; +import Analytics from './Analytics'; +import * as sdk from './index'; +import PropTypes from 'prop-types'; +import { _t } from './languageHandler'; + +/** + * Wrap an asynchronous loader function with a react component which shows a + * spinner until the real component loads. + */ +export default createReactClass({ + propTypes: { + /** A promise which resolves with the real component + */ + prom: PropTypes.object.isRequired, + }, + + getInitialState: function() { + return { + component: null, + error: null, + }; + }, + + componentWillMount: function() { + this._unmounted = false; + // XXX: temporary logging to try to diagnose + // https://github.com/vector-im/riot-web/issues/3148 + console.log('Starting load of AsyncWrapper for modal'); + this.props.prom.then((result) => { + if (this._unmounted) { + return; + } + // Take the 'default' member if it's there, then we support + // passing in just an import()ed module, since ES6 async import + // always returns a module *namespace*. + const component = result.default ? result.default : result; + this.setState({component}); + }).catch((e) => { + console.warn('AsyncWrapper promise failed', e); + this.setState({error: e}); + }); + }, + + componentWillUnmount: function() { + this._unmounted = true; + }, + + _onWrapperCancelClick: function() { + this.props.onFinished(false); + }, + + render: function() { + if (this.state.component) { + const Component = this.state.component; + return ; + } else if (this.state.error) { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return + {_t("Unable to load! Check your network connectivity and try again.")} + + ; + } else { + // show a spinner until the component is loaded. + const Spinner = sdk.getComponent("elements.Spinner"); + return ; + } + }, +}); + diff --git a/src/Modal.js b/src/Modal.js index 29d3af2e74..b6215b2b5a 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -17,87 +17,14 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Analytics from './Analytics'; -import * as sdk from './index'; import dis from './dispatcher'; -import { _t } from './languageHandler'; -import {defer} from "./utils/promise"; +import {defer} from './utils/promise'; +import AsyncWrapper from './AsyncWrapper'; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; -/** - * Wrap an asynchronous loader function with a react component which shows a - * spinner until the real component loads. - */ -const AsyncWrapper = createReactClass({ - propTypes: { - /** A promise which resolves with the real component - */ - prom: PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - component: null, - error: null, - }; - }, - - componentWillMount: function() { - this._unmounted = false; - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log('Starting load of AsyncWrapper for modal'); - this.props.prom.then((result) => { - if (this._unmounted) { - return; - } - // Take the 'default' member if it's there, then we support - // passing in just an import()ed module, since ES6 async import - // always returns a module *namespace*. - const component = result.default ? result.default : result; - this.setState({component}); - }).catch((e) => { - console.warn('AsyncWrapper promise failed', e); - this.setState({error: e}); - }); - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - _onWrapperCancelClick: function() { - this.props.onFinished(false); - }, - - render: function() { - if (this.state.component) { - const Component = this.state.component; - return ; - } else if (this.state.error) { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return - {_t("Unable to load! Check your network connectivity and try again.")} - - ; - } else { - // show a spinner until the component is loaded. - const Spinner = sdk.getComponent("elements.Spinner"); - return ; - } - }, -}); - class ModalManager { constructor() { this._counter = 0; From dbf1c9a02a4550fba98930f51d6058e4566ada47 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:34:03 +0000 Subject: [PATCH 217/282] Apparently i18n... --- src/i18n/strings/en_EN.json | 6 +- src/i18n/strings/en_EN.json.orig | 2040 ++++++++++++++++++++++++++++++ 2 files changed, 2043 insertions(+), 3 deletions(-) create mode 100644 src/i18n/strings/en_EN.json.orig diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..610f1546e7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -21,6 +21,9 @@ "Analytics": "Analytics", "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", + "Error": "Error", + "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", + "Dismiss": "Dismiss", "Call Failed": "Call Failed", "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", "Review Devices": "Review Devices", @@ -105,9 +108,6 @@ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", "Trust": "Trust", - "Error": "Error", - "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", - "Dismiss": "Dismiss", "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", "Unable to enable Notifications": "Unable to enable Notifications", diff --git a/src/i18n/strings/en_EN.json.orig b/src/i18n/strings/en_EN.json.orig new file mode 100644 index 0000000000..f0eab6b12d --- /dev/null +++ b/src/i18n/strings/en_EN.json.orig @@ -0,0 +1,2040 @@ +{ + "This email address is already in use": "This email address is already in use", + "This phone number is already in use": "This phone number is already in use", + "Add Email Address": "Add Email Address", + "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", + "Add Phone Number": "Add Phone Number", + "The platform you're on": "The platform you're on", + "The version of Riot.im": "The version of Riot.im", + "Whether or not you're logged in (we don't record your username)": "Whether or not you're logged in (we don't record your username)", + "Your language of choice": "Your language of choice", + "Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)", + "Your homeserver's URL": "Your homeserver's URL", + "Your identity server's URL": "Your identity server's URL", + "e.g. %(exampleValue)s": "e.g. %(exampleValue)s", + "Every page you use in the app": "Every page you use in the app", + "e.g. ": "e.g. ", + "Your User Agent": "Your User Agent", + "Your device resolution": "Your device resolution", + "Analytics": "Analytics", + "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", + "Call Failed": "Call Failed", + "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", + "Review Devices": "Review Devices", + "Call Anyway": "Call Anyway", + "Answer Anyway": "Answer Anyway", + "Call": "Call", + "Answer": "Answer", + "Call Timeout": "Call Timeout", + "The remote side failed to pick up": "The remote side failed to pick up", + "Call failed due to misconfigured server": "Call failed due to misconfigured server", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.", + "Try using turn.matrix.org": "Try using turn.matrix.org", + "OK": "OK", + "Unable to capture screen": "Unable to capture screen", + "Existing Call": "Existing Call", + "You are already in a call.": "You are already in a call.", + "VoIP is unsupported": "VoIP is unsupported", + "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", + "You cannot place a call with yourself.": "You cannot place a call with yourself.", + "Could not connect to the integration server": "Could not connect to the integration server", + "A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available", + "Call in Progress": "Call in Progress", + "A call is currently being placed!": "A call is currently being placed!", + "A call is already in progress!": "A call is already in progress!", + "Permission Required": "Permission Required", + "You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room", + "Replying With Files": "Replying With Files", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?", + "Continue": "Continue", + "The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", + "Upload Failed": "Upload Failed", + "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", + "The server does not support the room version specified.": "The server does not support the room version specified.", + "Failure to create room": "Failure to create room", + "Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver", + "Send anyway": "Send anyway", + "Send": "Send", + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "May", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "PM": "PM", + "AM": "AM", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "New Session": "New Session", + "Who would you like to add to this community?": "Who would you like to add to this community?", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", + "Invite new community members": "Invite new community members", + "Name or Matrix ID": "Name or Matrix ID", + "Invite to Community": "Invite to Community", + "Which rooms would you like to add to this community?": "Which rooms would you like to add to this community?", + "Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?", + "Add rooms to the community": "Add rooms to the community", + "Room name or alias": "Room name or alias", + "Add to community": "Add to community", + "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", + "Failed to invite users to community": "Failed to invite users to community", + "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", + "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", + "Unnamed Room": "Unnamed Room", + "Identity server has no terms of service": "Identity server has no terms of service", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", + "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", + "Trust": "Trust", + "Error": "Error", + "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", + "Dismiss": "Dismiss", + "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", + "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", + "Unable to enable Notifications": "Unable to enable Notifications", + "This email address was not found": "This email address was not found", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.", + "Registration Required": "Registration Required", + "You need to register to do this. Would you like to register now?": "You need to register to do this. Would you like to register now?", + "Register": "Register", + "Default": "Default", + "Restricted": "Restricted", + "Moderator": "Moderator", + "Admin": "Admin", + "Custom (%(level)s)": "Custom (%(level)s)", + "Start a chat": "Start a chat", + "Who would you like to communicate with?": "Who would you like to communicate with?", + "Email, name or Matrix ID": "Email, name or Matrix ID", + "Start Chat": "Start Chat", + "Invite new room members": "Invite new room members", + "Send Invites": "Send Invites", + "Failed to start chat": "Failed to start chat", + "Operation failed": "Operation failed", + "Failed to invite": "Failed to invite", + "Failed to invite users to the room:": "Failed to invite users to the room:", + "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", + "You need to be logged in.": "You need to be logged in.", + "You need to be able to invite users to do that.": "You need to be able to invite users to do that.", + "Unable to create widget.": "Unable to create widget.", + "Missing roomId.": "Missing roomId.", + "Failed to send request.": "Failed to send request.", + "This room is not recognised.": "This room is not recognised.", + "Power level must be positive integer.": "Power level must be positive integer.", + "You are not in this room.": "You are not in this room.", + "You do not have permission to do that in this room.": "You do not have permission to do that in this room.", + "Missing room_id in request": "Missing room_id in request", + "Room %(roomId)s not visible": "Room %(roomId)s not visible", + "Missing user_id in request": "Missing user_id in request", + "Messages": "Messages", + "Actions": "Actions", + "Advanced": "Advanced", + "Other": "Other", + "Usage": "Usage", + "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", + "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", + "/ddg is not a command": "/ddg is not a command", + "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", + "Upgrades a room to a new version": "Upgrades a room to a new version", + "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", + "Error upgrading room": "Error upgrading room", + "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", + "Changes your display nickname": "Changes your display nickname", + "Changes your display nickname in the current room only": "Changes your display nickname in the current room only", + "Changes the avatar of the current room": "Changes the avatar of the current room", + "Changes your avatar in this current room only": "Changes your avatar in this current room only", + "Changes your avatar in all rooms": "Changes your avatar in all rooms", + "Gets or sets the room topic": "Gets or sets the room topic", + "This room has no topic.": "This room has no topic.", + "Sets the room name": "Sets the room name", + "Invites user with given id to current room": "Invites user with given id to current room", + "Use an identity server": "Use an identity server", + "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.", + "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", + "Joins room with given alias": "Joins room with given alias", + "Leave room": "Leave room", + "Unrecognised room alias:": "Unrecognised room alias:", + "Kicks user with given id": "Kicks user with given id", + "Bans user with given id": "Bans user with given id", + "Unbans user with given ID": "Unbans user with given ID", + "Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you", + "Ignored user": "Ignored user", + "You are now ignoring %(userId)s": "You are now ignoring %(userId)s", + "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward", + "Unignored user": "Unignored user", + "You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s", + "Define the power level of a user": "Define the power level of a user", + "Deops user with given id": "Deops user with given id", + "Opens the Developer Tools dialog": "Opens the Developer Tools dialog", + "Adds a custom widget by URL to the room": "Adds a custom widget by URL to the room", + "Please supply a https:// or http:// widget URL": "Please supply a https:// or http:// widget URL", + "You cannot modify widgets in this room.": "You cannot modify widgets in this room.", + "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", + "Unknown (user, device) pair:": "Unknown (user, device) pair:", + "Device already verified!": "Device already verified!", + "WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", + "Verified key": "Verified key", + "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", + "Displays action": "Displays action", + "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", + "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", + "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", + "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", + "Unrecognised command:": "Unrecognised command:", + "Reason": "Reason", + "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", + "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", + "%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.", + "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", + "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", + "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", + "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", + "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", + "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", + "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", + "%(senderName)s made no change.": "%(senderName)s made no change.", + "VoIP conference started.": "VoIP conference started.", + "%(targetName)s joined the room.": "%(targetName)s joined the room.", + "VoIP conference finished.": "VoIP conference finished.", + "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", + "%(targetName)s left the room.": "%(targetName)s left the room.", + "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", + "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", + "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.", + "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", + "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", + "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", + "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", + "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", + "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", + "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", + "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", + "Someone": "Someone", + "(not supported by this browser)": "(not supported by this browser)", + "%(senderName)s answered the call.": "%(senderName)s answered the call.", + "(could not connect media)": "(could not connect media)", + "(no answer)": "(no answer)", + "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", + "%(senderName)s ended the call.": "%(senderName)s ended the call.", + "%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)", + "%(senderName)s placed a video call.": "%(senderName)s placed a video call.", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s placed a video call. (not supported by this browser)", + "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", + "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", + "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", + "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", + "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", + "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removed the rule banning users matching %(glob)s", + "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removed the rule banning rooms matching %(glob)s", + "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removed the rule banning servers matching %(glob)s", + "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removed a ban rule matching %(glob)s", + "%(senderName)s updated an invalid ban rule": "%(senderName)s updated an invalid ban rule", + "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", + "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", + "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", + "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", + "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", + "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s created a ban rule matching %(glob)s for %(reason)s", + "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", + "Light theme": "Light theme", + "Dark theme": "Dark theme", + "%(displayName)s is typing …": "%(displayName)s is typing …", + "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", + "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", + "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", + "Cannot reach homeserver": "Cannot reach homeserver", + "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", + "Your Riot is misconfigured": "Your Riot is misconfigured", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Ask your Riot admin to check your config for incorrect or duplicate entries.", + "Cannot reach identity server": "Cannot reach identity server", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", + "No homeserver URL provided": "No homeserver URL provided", + "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", + "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", + "The message you are trying to send is too large.": "The message you are trying to send is too large.", + "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", + "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", + "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", + "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", + "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", + "%(items)s and %(count)s others|one": "%(items)s and one other", + "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", + "a few seconds ago": "a few seconds ago", + "about a minute ago": "about a minute ago", + "%(num)s minutes ago": "%(num)s minutes ago", + "about an hour ago": "about an hour ago", + "%(num)s hours ago": "%(num)s hours ago", + "about a day ago": "about a day ago", + "%(num)s days ago": "%(num)s days ago", + "a few seconds from now": "a few seconds from now", + "about a minute from now": "about a minute from now", + "%(num)s minutes from now": "%(num)s minutes from now", + "about an hour from now": "about an hour from now", + "%(num)s hours from now": "%(num)s hours from now", + "about a day from now": "about a day from now", + "%(num)s days from now": "%(num)s days from now", + "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", + "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", + "Not a valid Riot keyfile": "Not a valid Riot keyfile", + "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", + "Unrecognised address": "Unrecognised address", + "You do not have permission to invite people to this room.": "You do not have permission to invite people to this room.", + "User %(userId)s is already in the room": "User %(userId)s is already in the room", + "User %(user_id)s does not exist": "User %(user_id)s does not exist", + "User %(user_id)s may or may not exist": "User %(user_id)s may or may not exist", + "The user must be unbanned before they can be invited.": "The user must be unbanned before they can be invited.", + "The user's homeserver does not support the version of the room.": "The user's homeserver does not support the version of the room.", + "Unknown server error": "Unknown server error", + "Use a few words, avoid common phrases": "Use a few words, avoid common phrases", + "No need for symbols, digits, or uppercase letters": "No need for symbols, digits, or uppercase letters", + "Use a longer keyboard pattern with more turns": "Use a longer keyboard pattern with more turns", + "Avoid repeated words and characters": "Avoid repeated words and characters", + "Avoid sequences": "Avoid sequences", + "Avoid recent years": "Avoid recent years", + "Avoid years that are associated with you": "Avoid years that are associated with you", + "Avoid dates and years that are associated with you": "Avoid dates and years that are associated with you", + "Capitalization doesn't help very much": "Capitalization doesn't help very much", + "All-uppercase is almost as easy to guess as all-lowercase": "All-uppercase is almost as easy to guess as all-lowercase", + "Reversed words aren't much harder to guess": "Reversed words aren't much harder to guess", + "Predictable substitutions like '@' instead of 'a' don't help very much": "Predictable substitutions like '@' instead of 'a' don't help very much", + "Add another word or two. Uncommon words are better.": "Add another word or two. Uncommon words are better.", + "Repeats like \"aaa\" are easy to guess": "Repeats like \"aaa\" are easy to guess", + "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"", + "Sequences like abc or 6543 are easy to guess": "Sequences like abc or 6543 are easy to guess", + "Recent years are easy to guess": "Recent years are easy to guess", + "Dates are often easy to guess": "Dates are often easy to guess", + "This is a top-10 common password": "This is a top-10 common password", + "This is a top-100 common password": "This is a top-100 common password", + "This is a very common password": "This is a very common password", + "This is similar to a commonly used password": "This is similar to a commonly used password", + "A word by itself is easy to guess": "A word by itself is easy to guess", + "Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess", + "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", + "There was an error joining the room": "There was an error joining the room", + "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", + "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", + "Failed to join room": "Failed to join room", + "Message Pinning": "Message Pinning", + "Custom user status messages": "Custom user status messages", + "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", + "Render simple counters in room header": "Render simple counters in room header", + "Multiple integration managers": "Multiple integration managers", + "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", + "New invite dialog": "New invite dialog", + "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", + "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", + "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", + "Show info about bridges in room settings": "Show info about bridges in room settings", + "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", + "Use compact timeline layout": "Use compact timeline layout", + "Show a placeholder for removed messages": "Show a placeholder for removed messages", + "Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)", + "Show avatar changes": "Show avatar changes", + "Show display name changes": "Show display name changes", + "Show read receipts sent by other users": "Show read receipts sent by other users", + "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", + "Always show encryption icons": "Always show encryption icons", + "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", + "Send typing notifications": "Send typing notifications", + "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", + "Mirror local video feed": "Mirror local video feed", + "Enable Community Filter Panel": "Enable Community Filter Panel", + "Match system theme": "Match system theme", + "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", + "Send analytics data": "Send analytics data", + "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", + "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", + "Enable inline URL previews by default": "Enable inline URL previews by default", + "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", + "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", + "Room Colour": "Room Colour", + "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", + "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", + "Show developer tools": "Show developer tools", + "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", + "Show recently visited rooms above the room list": "Show recently visited rooms above the room list", + "Show hidden events in timeline": "Show hidden events in timeline", + "Low bandwidth mode": "Low bandwidth mode", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", + "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", + "Show previews/thumbnails for images": "Show previews/thumbnails for images", + "Collecting app version information": "Collecting app version information", + "Collecting logs": "Collecting logs", + "Uploading report": "Uploading report", + "Waiting for response from server": "Waiting for response from server", + "Messages containing my display name": "Messages containing my display name", + "Messages containing my username": "Messages containing my username", + "Messages containing @room": "Messages containing @room", + "Messages in one-to-one chats": "Messages in one-to-one chats", + "Encrypted messages in one-to-one chats": "Encrypted messages in one-to-one chats", + "Messages in group chats": "Messages in group chats", + "Encrypted messages in group chats": "Encrypted messages in group chats", + "When I'm invited to a room": "When I'm invited to a room", + "Call invitation": "Call invitation", + "Messages sent by bot": "Messages sent by bot", + "When rooms are upgraded": "When rooms are upgraded", + "My Ban List": "My Ban List", + "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", + "Active call (%(roomName)s)": "Active call (%(roomName)s)", + "unknown caller": "unknown caller", + "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", + "Incoming video call from %(name)s": "Incoming video call from %(name)s", + "Incoming call from %(name)s": "Incoming call from %(name)s", + "Decline": "Decline", + "Accept": "Accept", + "The other party cancelled the verification.": "The other party cancelled the verification.", + "Verified!": "Verified!", + "You've successfully verified this user.": "You've successfully verified this user.", + "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.", + "Got It": "Got It", + "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", + "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", + "Unable to find a supported verification method.": "Unable to find a supported verification method.", + "Cancel": "Cancel", + "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.", + "Dog": "Dog", + "Cat": "Cat", + "Lion": "Lion", + "Horse": "Horse", + "Unicorn": "Unicorn", + "Pig": "Pig", + "Elephant": "Elephant", + "Rabbit": "Rabbit", + "Panda": "Panda", + "Rooster": "Rooster", + "Penguin": "Penguin", + "Turtle": "Turtle", + "Fish": "Fish", + "Octopus": "Octopus", + "Butterfly": "Butterfly", + "Flower": "Flower", + "Tree": "Tree", + "Cactus": "Cactus", + "Mushroom": "Mushroom", + "Globe": "Globe", + "Moon": "Moon", + "Cloud": "Cloud", + "Fire": "Fire", + "Banana": "Banana", + "Apple": "Apple", + "Strawberry": "Strawberry", + "Corn": "Corn", + "Pizza": "Pizza", + "Cake": "Cake", + "Heart": "Heart", + "Smiley": "Smiley", + "Robot": "Robot", + "Hat": "Hat", + "Glasses": "Glasses", + "Spanner": "Spanner", + "Santa": "Santa", + "Thumbs up": "Thumbs up", + "Umbrella": "Umbrella", + "Hourglass": "Hourglass", + "Clock": "Clock", + "Gift": "Gift", + "Light bulb": "Light bulb", + "Book": "Book", + "Pencil": "Pencil", + "Paperclip": "Paperclip", + "Scissors": "Scissors", + "Lock": "Lock", + "Key": "Key", + "Hammer": "Hammer", + "Telephone": "Telephone", + "Flag": "Flag", + "Train": "Train", + "Bicycle": "Bicycle", + "Aeroplane": "Aeroplane", + "Rocket": "Rocket", + "Trophy": "Trophy", + "Ball": "Ball", + "Guitar": "Guitar", + "Trumpet": "Trumpet", + "Bell": "Bell", + "Anchor": "Anchor", + "Headphones": "Headphones", + "Folder": "Folder", + "Pin": "Pin", + "Other users may not trust it": "Other users may not trust it", + "Later": "Later", + "Verify": "Verify", + "Decline (%(counter)s)": "Decline (%(counter)s)", + "Accept to continue:": "Accept to continue:", + "Upload": "Upload", + "Remove": "Remove", + "Failed to upload profile picture!": "Failed to upload profile picture!", + "Upload new:": "Upload new:", + "No display name": "No display name", + "New passwords don't match": "New passwords don't match", + "Passwords can't be empty": "Passwords can't be empty", + "Warning!": "Warning!", + "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", + "Export E2E room keys": "Export E2E room keys", + "Do you want to set an email address?": "Do you want to set an email address?", + "Current password": "Current password", + "Password": "Password", + "New Password": "New Password", + "Confirm password": "Confirm password", + "Change Password": "Change Password", + "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.", + "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", + "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", + "Cross-signing public keys:": "Cross-signing public keys:", + "on device": "on device", + "not found": "not found", + "Cross-signing private keys:": "Cross-signing private keys:", + "in secret storage": "in secret storage", + "Secret storage public key:": "Secret storage public key:", + "in account data": "in account data", + "Your homeserver does not support device management.": "Your homeserver does not support device management.", + "Unable to load device list": "Unable to load device list", + "Authentication": "Authentication", + "Delete %(count)s devices|other": "Delete %(count)s devices", + "Delete %(count)s devices|one": "Delete device", + "ID": "ID", + "Public Name": "Public Name", + "Last seen": "Last seen", + "Failed to set display name": "Failed to set display name", + "Disable Notifications": "Disable Notifications", + "Enable Notifications": "Enable Notifications", + "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 device is backing up your keys. ": "This device is backing up your keys. ", + "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", + "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.", + "Connect this device to Key Backup": "Connect this device 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 device with ID %(deviceId)s": "Backup has a signature from unknown device with ID %(deviceId)s", + "Backup has a valid signature from this device": "Backup has a valid signature from this device", + "Backup has an invalid signature from this device": "Backup has an invalid signature from this device", + "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", + "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", + "Backup has an invalid signature from verified device ": "Backup has an invalid signature from verified device ", + "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", + "Backup is not signed by any of your devices": "Backup is not signed by any of your devices", + "This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device", + "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.", + "Backup version: ": "Backup version: ", + "Algorithm: ": "Algorithm: ", + "Backup key stored: ": "Backup key stored: ", + "Your keys are not being backed up from this device.": "Your keys are not being backed up from this device.", + "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", + "Enter keywords separated by a comma:": "Enter keywords separated by a comma:", + "Failed to change settings": "Failed to change settings", + "Can't update user notification settings": "Can't update user notification settings", + "Failed to update keywords": "Failed to update keywords", + "Messages containing keywords": "Messages containing keywords", + "Notify for all other messages/rooms": "Notify for all other messages/rooms", + "Notify me for anything else": "Notify me for anything else", + "Enable notifications for this account": "Enable notifications for this account", + "Clear notifications": "Clear notifications", + "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.", + "Add an email address to configure email notifications": "Add an email address to configure email notifications", + "Enable email notifications": "Enable email notifications", + "Notifications on the following keywords follow rules which can’t be displayed here:": "Notifications on the following keywords follow rules which can’t be displayed here:", + "Unable to fetch notification target list": "Unable to fetch notification target list", + "Notification targets": "Notification targets", + "Advanced notification settings": "Advanced notification settings", + "There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here", + "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply", + "Enable desktop notifications for this device": "Enable desktop notifications for this device", + "Show message in desktop notification": "Show message in desktop notification", + "Enable audible notifications for this device": "Enable audible notifications for this device", + "Off": "Off", + "On": "On", + "Noisy": "Noisy", + "Upgrade to your own domain": "Upgrade to your own domain", + "Display Name": "Display Name", + "Profile picture": "Profile picture", + "Save": "Save", + "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", + "Checking server": "Checking server", + "Change identity server": "Change identity server", + "Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?", + "Terms of service not accepted or the identity server is invalid.": "Terms of service not accepted or the identity server is invalid.", + "The identity server you have chosen does not have any terms of service.": "The identity server you have chosen does not have any terms of service.", + "Disconnect identity server": "Disconnect identity server", + "Disconnect from the identity server ?": "Disconnect from the identity server ?", + "Disconnect": "Disconnect", + "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.", + "You should:": "You should:", + "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "check your browser plugins for anything that might block the identity server (such as Privacy Badger)", + "contact the administrators of identity server ": "contact the administrators of identity server ", + "wait and try again later": "wait and try again later", + "Disconnect anyway": "Disconnect anyway", + "You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .", + "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.", + "Go back": "Go back", + "Identity Server (%(server)s)": "Identity Server (%(server)s)", + "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", + "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.", + "Identity Server": "Identity Server", + "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.", + "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.": "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.", + "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.": "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.", + "Do not use an identity server": "Do not use an identity server", + "Enter a new identity server": "Enter a new identity server", + "Change": "Change", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", + "Manage integrations": "Manage integrations", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.", + "Flair": "Flair", + "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", + "Success": "Success", + "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", + "Profile": "Profile", + "Email addresses": "Email addresses", + "Phone numbers": "Phone numbers", + "Account": "Account", + "Set a new account password...": "Set a new account password...", + "Language and region": "Language and region", + "Theme": "Theme", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", + "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", + "Warning": "Warning", + "General": "General", + "Discovery": "Discovery", + "Deactivate account": "Deactivate account", + "Legal": "Legal", + "Credits": "Credits", + "For help with using Riot, click here.": "For help with using Riot, click here.", + "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.", + "Chat with Riot Bot": "Chat with Riot Bot", + "Check for update": "Check for update", + "Help & About": "Help & About", + "Bug reporting": "Bug reporting", + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", + "Submit debug logs": "Submit debug logs", + "Clear cache and reload": "Clear cache and reload", + "FAQ": "FAQ", + "Versions": "Versions", + "matrix-react-sdk version:": "matrix-react-sdk version:", + "riot-web version:": "riot-web version:", + "olm version:": "olm version:", + "Homeserver is": "Homeserver is", + "Identity Server is": "Identity Server is", + "Access Token:": "Access Token:", + "click to reveal": "click to reveal", + "Labs": "Labs", + "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", + "Ignored/Blocked": "Ignored/Blocked", + "Error adding ignored user/server": "Error adding ignored user/server", + "Something went wrong. Please try again or view your console for hints.": "Something went wrong. Please try again or view your console for hints.", + "Error subscribing to list": "Error subscribing to list", + "Please verify the room ID or alias and try again.": "Please verify the room ID or alias and try again.", + "Error removing ignored user/server": "Error removing ignored user/server", + "Error unsubscribing from list": "Error unsubscribing from list", + "Please try again or view your console for hints.": "Please try again or view your console for hints.", + "None": "None", + "Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s", + "Server rules": "Server rules", + "User rules": "User rules", + "Close": "Close", + "You have not ignored anyone.": "You have not ignored anyone.", + "You are currently ignoring:": "You are currently ignoring:", + "You are not subscribed to any lists": "You are not subscribed to any lists", + "Unsubscribe": "Unsubscribe", + "View rules": "View rules", + "You are currently subscribed to:": "You are currently subscribed to:", + "Ignored users": "Ignored users", + "⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.", + "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.", + "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.": "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.", + "Personal ban list": "Personal ban list", + "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.": "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.", + "Server or user ID to ignore": "Server or user ID to ignore", + "eg: @bot:* or example.org": "eg: @bot:* or example.org", + "Ignore": "Ignore", + "Subscribed lists": "Subscribed lists", + "Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!", + "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", + "Room ID or alias of ban list": "Room ID or alias of ban list", + "Subscribe": "Subscribe", + "Notifications": "Notifications", + "Start automatically after system login": "Start automatically after system login", + "Always show the window menu bar": "Always show the window menu bar", + "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", + "Preferences": "Preferences", + "Composer": "Composer", + "Timeline": "Timeline", + "Room list": "Room list", + "Autocomplete delay (ms)": "Autocomplete delay (ms)", + "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", + "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", + "Unignore": "Unignore", + "": "", + "Import E2E room keys": "Import E2E room keys", + "Cryptography": "Cryptography", + "Device ID:": "Device ID:", + "Device key:": "Device key:", + "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", + "Cross-signing": "Cross-signing", + "Security & Privacy": "Security & Privacy", + "Devices": "Devices", + "A device's public name is visible to people you communicate with": "A device's public name is visible to people you communicate with", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot 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.", + "No media permissions": "No media permissions", + "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", + "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", + "Request media permissions": "Request media permissions", + "No Audio Outputs detected": "No Audio Outputs detected", + "No Microphones detected": "No Microphones detected", + "No Webcams detected": "No Webcams detected", + "Default Device": "Default Device", + "Audio Output": "Audio Output", + "Microphone": "Microphone", + "Camera": "Camera", + "Voice & Video": "Voice & Video", + "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", + "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", + "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", + "this room": "this room", + "View older messages in %(roomName)s.": "View older messages in %(roomName)s.", + "Room information": "Room information", + "Internal room ID:": "Internal room ID:", + "Room version": "Room version", + "Room version:": "Room version:", + "Developer options": "Developer options", + "Open Devtools": "Open Devtools", + "This bridge was provisioned by ": "This bridge was provisioned by ", + "This bridge is managed by .": "This bridge is managed by .", + "Bridged into , on ": "Bridged into , on ", + "Connected to on ": "Connected to on ", + "Connected via %(protocolName)s": "Connected via %(protocolName)s", + "Bridge Info": "Bridge Info", + "Below is a list of bridges connected to this room.": "Below is a list of bridges connected to this room.", + "Room Addresses": "Room Addresses", + "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", + "URL Previews": "URL Previews", + "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", + "Change room name": "Change room name", + "Change main address for the room": "Change main address for the room", + "Change history visibility": "Change history visibility", + "Change permissions": "Change permissions", + "Change topic": "Change topic", + "Upgrade the room": "Upgrade the room", + "Enable room encryption": "Enable room encryption", + "Modify widgets": "Modify widgets", + "Failed to unban": "Failed to unban", + "Unban": "Unban", + "Banned by %(displayName)s": "Banned by %(displayName)s", + "Error changing power level requirement": "Error changing power level requirement", + "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.", + "Error changing power level": "Error changing power level", + "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.", + "Default role": "Default role", + "Send messages": "Send messages", + "Invite users": "Invite users", + "Change settings": "Change settings", + "Kick users": "Kick users", + "Ban users": "Ban users", + "Remove messages": "Remove messages", + "Notify everyone": "Notify everyone", + "No users have specific privileges in this room": "No users have specific privileges in this room", + "Privileged Users": "Privileged Users", + "Muted Users": "Muted Users", + "Banned users": "Banned users", + "Send %(eventType)s events": "Send %(eventType)s events", + "Roles & Permissions": "Roles & Permissions", + "Permissions": "Permissions", + "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", + "Enable encryption?": "Enable encryption?", + "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.": "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.", + "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", + "Click here to fix": "Click here to fix", + "To link to this room, please add an alias.": "To link to this room, please add an alias.", + "Only people who have been invited": "Only people who have been invited", + "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", + "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", + "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.", + "Anyone": "Anyone", + "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", + "Members only (since they were invited)": "Members only (since they were invited)", + "Members only (since they joined)": "Members only (since they joined)", + "Encryption": "Encryption", + "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", + "Encrypted": "Encrypted", + "Who can access this room?": "Who can access this room?", + "Who can read history?": "Who can read history?", + "Unable to revoke sharing for email address": "Unable to revoke sharing for email address", + "Unable to share email address": "Unable to share email address", + "Your email address hasn't been verified yet": "Your email address hasn't been verified yet", + "Click the link in the email you received to verify and then click continue again.": "Click the link in the email you received to verify and then click continue again.", + "Unable to verify email address.": "Unable to verify email address.", + "Verify the link in your inbox": "Verify the link in your inbox", + "Complete": "Complete", + "Revoke": "Revoke", + "Share": "Share", + "Discovery options will appear once you have added an email above.": "Discovery options will appear once you have added an email above.", + "Unable to revoke sharing for phone number": "Unable to revoke sharing for phone number", + "Unable to share phone number": "Unable to share phone number", + "Unable to verify phone number.": "Unable to verify phone number.", + "Incorrect verification code": "Incorrect verification code", + "Please enter verification code sent via text.": "Please enter verification code sent via text.", + "Verification code": "Verification code", + "Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.", + "Unable to remove contact information": "Unable to remove contact information", + "Remove %(email)s?": "Remove %(email)s?", + "Invalid Email Address": "Invalid Email Address", + "This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address", + "Unable to add email address": "Unable to add email address", + "Add": "Add", + "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.", + "Email Address": "Email Address", + "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", + " (unsupported)": " (unsupported)", + "Join as voice or video.": "Join as voice or video.", + "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", + "This user has not verified all of their devices.": "This user has not verified all of their devices.", + "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", + "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", + "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", + "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", + "Some devices for this user are not trusted": "Some devices for this user are not trusted", + "All devices for this user are trusted": "All devices for this user are trusted", + "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", + "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", + "Edit message": "Edit message", + "This event could not be displayed": "This event could not be displayed", + "%(senderName)s sent an image": "%(senderName)s sent an image", + "%(senderName)s sent a video": "%(senderName)s sent a video", + "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", + "Your key share request has been sent - please check your other devices for key share requests.": "Your key share request has been sent - please check your other devices for key share requests.", + "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.", + "If your other devices do not have the key for this message you will not be able to decrypt them.": "If your other devices do not have the key for this message you will not be able to decrypt them.", + "Key request sent.": "Key request sent.", + "Re-request encryption keys from your other devices.": "Re-request encryption keys from your other devices.", + "This message cannot be decrypted": "This message cannot be decrypted", + "Encrypted by an unverified device": "Encrypted by an unverified device", + "Unencrypted": "Unencrypted", + "Please select the destination room for this message": "Please select the destination room for this message", + "Scroll to bottom of page": "Scroll to bottom of page", + "Close preview": "Close preview", + "device id: ": "device id: ", + "Disinvite": "Disinvite", + "Kick": "Kick", + "Disinvite this user?": "Disinvite this user?", + "Kick this user?": "Kick this user?", + "Failed to kick": "Failed to kick", + "Ban": "Ban", + "Unban this user?": "Unban this user?", + "Ban this user?": "Ban this user?", + "Failed to ban user": "Failed to ban user", + "No recent messages by %(user)s found": "No recent messages by %(user)s found", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", + "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", + "Remove %(count)s messages|other": "Remove %(count)s messages", + "Remove %(count)s messages|one": "Remove 1 message", + "Demote yourself?": "Demote yourself?", + "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.": "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.", + "Demote": "Demote", + "Failed to mute user": "Failed to mute user", + "Failed to toggle moderator status": "Failed to toggle moderator status", + "Deactivate user?": "Deactivate user?", + "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", + "Deactivate user": "Deactivate user", + "Failed to deactivate user": "Failed to deactivate user", + "Failed to change power level": "Failed to change power level", + "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.", + "Are you sure?": "Are you sure?", + "No devices with registered encryption keys": "No devices with registered encryption keys", + "Jump to read receipt": "Jump to read receipt", + "Mention": "Mention", + "Invite": "Invite", + "Share Link to User": "Share Link to User", + "User Options": "User Options", + "Direct chats": "Direct chats", + "Remove recent messages": "Remove recent messages", + "Unmute": "Unmute", + "Mute": "Mute", + "Revoke Moderator": "Revoke Moderator", + "Make Moderator": "Make Moderator", + "Admin Tools": "Admin Tools", + "and %(count)s others...|other": "and %(count)s others...", + "and %(count)s others...|one": "and one other...", + "Invite to this room": "Invite to this room", + "Invited": "Invited", + "Filter room members": "Filter room members", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", + "Voice call": "Voice call", + "Video call": "Video call", + "Hangup": "Hangup", + "Upload file": "Upload file", + "Send an encrypted reply…": "Send an encrypted reply…", + "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", + "Send an encrypted message…": "Send an encrypted message…", + "Send a message (unencrypted)…": "Send a message (unencrypted)…", + "The conversation continues here.": "The conversation continues here.", + "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", + "You do not have permission to post to this room": "You do not have permission to post to this room", + "Bold": "Bold", + "Italics": "Italics", + "Strikethrough": "Strikethrough", + "Code block": "Code block", + "Quote": "Quote", + "No pinned messages.": "No pinned messages.", + "Loading...": "Loading...", + "Pinned Messages": "Pinned Messages", + "Unpin Message": "Unpin Message", + "Jump to message": "Jump to message", + "%(duration)ss": "%(duration)ss", + "%(duration)sm": "%(duration)sm", + "%(duration)sh": "%(duration)sh", + "%(duration)sd": "%(duration)sd", + "Online for %(duration)s": "Online for %(duration)s", + "Idle for %(duration)s": "Idle for %(duration)s", + "Offline for %(duration)s": "Offline for %(duration)s", + "Unknown for %(duration)s": "Unknown for %(duration)s", + "Online": "Online", + "Idle": "Idle", + "Offline": "Offline", + "Unknown": "Unknown", + "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", + "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", + "Replying": "Replying", + "Direct Chat": "Direct Chat", + "Room %(name)s": "Room %(name)s", + "Recent rooms": "Recent rooms", + "No rooms to show": "No rooms to show", + "Unnamed room": "Unnamed room", + "World readable": "World readable", + "Guests can join": "Guests can join", + "(~%(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", + "Community Invites": "Community Invites", + "Invites": "Invites", + "Favourites": "Favourites", + "People": "People", + "Start chat": "Start chat", + "Rooms": "Rooms", + "Low priority": "Low priority", + "Historical": "Historical", + "System Alerts": "System Alerts", + "This room": "This room", + "Joining room …": "Joining room …", + "Loading …": "Loading …", + "Rejecting invite …": "Rejecting invite …", + "Join the conversation with an account": "Join the conversation with an account", + "Sign Up": "Sign Up", + "Sign In": "Sign In", + "Loading room preview": "Loading room preview", + "You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s", + "Reason: %(reason)s": "Reason: %(reason)s", + "Forget this room": "Forget this room", + "Re-join": "Re-join", + "You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s", + "Something went wrong with your invite to %(roomName)s": "Something went wrong with your invite to %(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.": "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.", + "unknown error code": "unknown error code", + "You can only join it with a working invite.": "You can only join it with a working invite.", + "Try to join anyway": "Try to join anyway", + "You can still join it because this is a public room.": "You can still join it because this is a public room.", + "Join the discussion": "Join the discussion", + "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "This invite to %(roomName)s was sent to %(email)s which is not associated with your account", + "Link this email with your account in Settings to receive invites directly in Riot.": "Link this email with your account in Settings to receive invites directly in Riot.", + "This invite to %(roomName)s was sent to %(email)s": "This invite to %(roomName)s was sent to %(email)s", + "Use an identity server in Settings to receive invites directly in Riot.": "Use an identity server in Settings to receive invites directly in Riot.", + "Share this email in Settings to receive invites directly in Riot.": "Share this email in Settings to receive invites directly in Riot.", + "Do you want to chat with %(user)s?": "Do you want to chat with %(user)s?", + " wants to chat": " wants to chat", + "Start chatting": "Start chatting", + "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", + " invited you": " invited you", + "Reject": "Reject", + "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", + "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", + "%(roomName)s does not exist.": "%(roomName)s does not exist.", + "This room doesn't exist. Are you sure you're at the right place?": "This room doesn't exist. Are you sure you're at the right place?", + "%(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.", + "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", + "Options": "Options", + "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", + "%(count)s unread messages including mentions.|one": "1 unread mention.", + "%(count)s unread messages.|other": "%(count)s unread messages.", + "%(count)s unread messages.|one": "1 unread message.", + "Unread mentions.": "Unread mentions.", + "Unread messages.": "Unread messages.", + "Add a topic": "Add a topic", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", + "This room has already been upgraded.": "This room has already been upgraded.", + "This room is running room version , which this homeserver has marked as unstable.": "This room is running room version , which this homeserver has marked as unstable.", + "Only room administrators will see this warning": "Only room administrators will see this warning", + "This Room": "This Room", + "All Rooms": "All Rooms", + "Search…": "Search…", + "Server error": "Server error", + "Command error": "Command error", + "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", + "Failed to connect to integration manager": "Failed to connect to integration manager", + "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", + "Add some now": "Add some now", + "Stickerpack": "Stickerpack", + "Hide Stickers": "Hide Stickers", + "Show Stickers": "Show Stickers", + "Failed to revoke invite": "Failed to revoke invite", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.", + "Revoke invite": "Revoke invite", + "Invited by %(sender)s": "Invited by %(sender)s", + "Jump to first unread message.": "Jump to first unread message.", + "Error updating main address": "Error updating main address", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", + "Error creating alias": "Error creating alias", + "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", + "Error removing alias": "Error removing alias", + "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", + "Main address": "Main address", + "not specified": "not specified", + "Remote addresses for this room:": "Remote addresses for this room:", + "Local addresses for this room:": "Local addresses for this room:", + "This room has no local addresses": "This room has no local addresses", + "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", + "Error updating flair": "Error updating flair", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", + "Invalid community ID": "Invalid community ID", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", + "Showing flair for these communities:": "Showing flair for these communities:", + "This room is not showing flair for any communities": "This room is not showing flair for any communities", + "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", + "Room Name": "Room Name", + "Room Topic": "Room Topic", + "Room avatar": "Room avatar", + "You have enabled URL previews by default.": "You have enabled URL previews by default.", + "You have disabled URL previews by default.": "You have disabled URL previews by default.", + "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", + "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.", + "Verify User": "Verify User", + "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.", + "For maximum security, do this in person.": "For maximum security, do this in person.", + "Start Verification": "Start Verification", + "Members": "Members", + "Files": "Files", + "Trusted": "Trusted", + "Not trusted": "Not trusted", + "Hide verified sessions": "Hide verified sessions", + "%(count)s verified sessions|other": "%(count)s verified sessions", + "%(count)s verified sessions|one": "1 verified session", + "Direct message": "Direct message", + "Remove from community": "Remove from community", + "Disinvite this user from community?": "Disinvite this user from community?", + "Remove this user from community?": "Remove this user from community?", + "Failed to withdraw invitation": "Failed to withdraw invitation", + "Failed to remove user from community": "Failed to remove user from community", + "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", + "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", + "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", + "Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.", + "Security": "Security", + "Sunday": "Sunday", + "Monday": "Monday", + "Tuesday": "Tuesday", + "Wednesday": "Wednesday", + "Thursday": "Thursday", + "Friday": "Friday", + "Saturday": "Saturday", + "Today": "Today", + "Yesterday": "Yesterday", + "View Source": "View Source", + "Error decrypting audio": "Error decrypting audio", + "React": "React", + "Reply": "Reply", + "Edit": "Edit", + "Message Actions": "Message Actions", + "Attachment": "Attachment", + "Error decrypting attachment": "Error decrypting attachment", + "Decrypt %(text)s": "Decrypt %(text)s", + "Download %(text)s": "Download %(text)s", + "Invalid file%(extra)s": "Invalid file%(extra)s", + "Error decrypting image": "Error decrypting image", + "Show image": "Show image", + "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", + "You verified %(name)s": "You verified %(name)s", + "You cancelled verifying %(name)s": "You cancelled verifying %(name)s", + "%(name)s cancelled verifying": "%(name)s cancelled verifying", + "You accepted": "You accepted", + "%(name)s accepted": "%(name)s accepted", + "You cancelled": "You cancelled", + "%(name)s cancelled": "%(name)s cancelled", + "%(name)s wants to verify": "%(name)s wants to verify", + "You sent a verification request": "You sent a verification request", + "Error decrypting video": "Error decrypting video", + "Show all": "Show all", + "Reactions": "Reactions", + " reacted with %(content)s": " reacted with %(content)s", + "reacted with %(shortName)s": "reacted with %(shortName)s", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", + "This room is a continuation of another conversation.": "This room is a continuation of another conversation.", + "Click here to see older messages.": "Click here to see older messages.", + "Copied!": "Copied!", + "Failed to copy": "Failed to copy", + "Add an Integration": "Add an 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?": "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?", + "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", + "edited": "edited", + "Removed or unknown message type": "Removed or unknown message type", + "Message removed by %(userId)s": "Message removed by %(userId)s", + "Message removed": "Message removed", + "Failed to load group members": "Failed to load group members", + "Filter community members": "Filter community members", + "Invite to this community": "Invite to this community", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", + "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.", + "Failed to remove room from community": "Failed to remove room from community", + "Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s", + "Something went wrong!": "Something went wrong!", + "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.", + "Visibility in Room List": "Visibility in Room List", + "Visible to everyone": "Visible to everyone", + "Only visible to community members": "Only visible to community members", + "Add rooms to this community": "Add rooms to this community", + "Filter community rooms": "Filter community rooms", + "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", + "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", + "You're not currently a member of any communities.": "You're not currently a member of any communities.", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).", + "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.", + "Yes, I want to help!": "Yes, I want to help!", + "You are not receiving desktop notifications": "You are not receiving desktop notifications", + "Enable them now": "Enable them now", + "What's New": "What's New", + "Update": "Update", + "What's new?": "What's new?", + "A new version of Riot is available.": "A new version of Riot is available.", + "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", + "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.", + "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.", + "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.", + "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", + "Checking for an update...": "Checking for an update...", + "No update available.": "No update available.", + "Downloading update...": "Downloading update...", + "Frequently Used": "Frequently Used", + "Smileys & People": "Smileys & People", + "Animals & Nature": "Animals & Nature", + "Food & Drink": "Food & Drink", + "Activities": "Activities", + "Travel & Places": "Travel & Places", + "Objects": "Objects", + "Symbols": "Symbols", + "Flags": "Flags", + "Quick Reactions": "Quick Reactions", + "Cancel search": "Cancel search", + "Unknown Address": "Unknown Address", + "Any of the following data may be shared:": "Any of the following data may be shared:", + "Your display name": "Your display name", + "Your avatar URL": "Your avatar URL", + "Your user ID": "Your user ID", + "Your theme": "Your theme", + "Riot URL": "Riot URL", + "Room ID": "Room ID", + "Widget ID": "Widget ID", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", + "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", + "Widgets do not use message encryption.": "Widgets do not use message encryption.", + "Widget added by": "Widget added by", + "This widget may use cookies.": "This widget may use cookies.", + "Delete Widget": "Delete Widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", + "Delete widget": "Delete widget", + "Failed to remove widget": "Failed to remove widget", + "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", + "Minimize apps": "Minimize apps", + "Maximize apps": "Maximize apps", + "Popout widget": "Popout widget", + "More options": "More options", + "Create new room": "Create new room", + "Unblacklist": "Unblacklist", + "Blacklist": "Blacklist", + "Unverify": "Unverify", + "Verify...": "Verify...", + "Join": "Join", + "No results": "No results", + "Yes": "Yes", + "No": "No", + "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", + "collapse": "collapse", + "expand": "expand", + "Communities": "Communities", + "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", + "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", + "Rotate Left": "Rotate Left", + "Rotate counter-clockwise": "Rotate counter-clockwise", + "Rotate Right": "Rotate Right", + "Rotate clockwise": "Rotate clockwise", + "Download this file": "Download this file", + "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", + "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", + "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", + "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", + "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", + "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", + "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", + "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", + "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", + "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", + "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", + "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", + "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", + "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", + "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", + "were invited %(count)s times|other": "were invited %(count)s times", + "were invited %(count)s times|one": "were invited", + "was invited %(count)s times|other": "was invited %(count)s times", + "was invited %(count)s times|one": "was invited", + "were banned %(count)s times|other": "were banned %(count)s times", + "were banned %(count)s times|one": "were banned", + "was banned %(count)s times|other": "was banned %(count)s times", + "was banned %(count)s times|one": "was banned", + "were unbanned %(count)s times|other": "were unbanned %(count)s times", + "were unbanned %(count)s times|one": "were unbanned", + "was unbanned %(count)s times|other": "was unbanned %(count)s times", + "was unbanned %(count)s times|one": "was unbanned", + "were kicked %(count)s times|other": "were kicked %(count)s times", + "were kicked %(count)s times|one": "were kicked", + "was kicked %(count)s times|other": "was kicked %(count)s times", + "was kicked %(count)s times|one": "was kicked", + "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", + "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", + "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", + "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", + "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)smade no changes %(count)s times", + "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes", + "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times", + "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", + "Power level": "Power level", + "Custom level": "Custom level", + "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", + "In reply to ": "In reply to ", + "Room alias": "Room alias", + "e.g. my-room": "e.g. my-room", + "Some characters not allowed": "Some characters not allowed", + "Please provide a room alias": "Please provide a room alias", + "This alias is available to use": "This alias is available to use", + "This alias is already in use": "This alias is already in use", + "Room directory": "Room directory", + "And %(count)s more...|other": "And %(count)s more...", + "ex. @bob:example.com": "ex. @bob:example.com", + "Add User": "Add User", + "Matrix ID": "Matrix ID", + "Matrix Room ID": "Matrix Room ID", + "email address": "email address", + "That doesn't look like a valid email address": "That doesn't look like a valid email address", + "You have entered an invalid address.": "You have entered an invalid address.", + "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.", + "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", + "The following users may not exist": "The following users may not exist", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", + "Invite anyway and never warn me again": "Invite anyway and never warn me again", + "Invite anyway": "Invite anyway", + "Close dialog": "Close dialog", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", + "Preparing to send logs": "Preparing to send logs", + "Logs sent": "Logs sent", + "Thank you!": "Thank you!", + "Failed to send logs: ": "Failed to send logs: ", + "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.": "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.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.", + "GitHub issue": "GitHub issue", + "Notes": "Notes", + "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.": "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.", + "Send logs": "Send logs", + "Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s", + "Unavailable": "Unavailable", + "Changelog": "Changelog", + "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", + "Removing…": "Removing…", + "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 on this device?": "Clear all data on this device?", + "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.", + "Clear all data": "Clear all data", + "Community IDs cannot be empty.": "Community IDs cannot be empty.", + "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'", + "Something went wrong whilst creating your community": "Something went wrong whilst creating your community", + "Create Community": "Create Community", + "Community Name": "Community Name", + "Example": "Example", + "Community ID": "Community ID", + "example": "example", + "Create": "Create", + "Please enter a name for the room": "Please enter a name for the room", + "Set a room alias to easily share your room with other people.": "Set a room alias to easily share your room with other people.", + "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", + "Create a public room": "Create a public room", + "Create a private room": "Create a private room", + "Name": "Name", + "Topic (optional)": "Topic (optional)", + "Make this room public": "Make this room public", + "Hide advanced": "Hide advanced", + "Show advanced": "Show advanced", + "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", + "Create Room": "Create Room", + "Sign out": "Sign out", + "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 Riot to do this": "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 Riot to do this", + "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", + "Incompatible Database": "Incompatible Database", + "Continue With Encryption Disabled": "Continue With Encryption Disabled", + "Unknown error": "Unknown error", + "Incorrect password": "Incorrect password", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.", + "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)", + "To continue, please enter your password:": "To continue, please enter your password:", + "Verify device": "Verify device", + "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", + "Verify by comparing a short text string.": "Verify by comparing a short text string.", + "Begin Verifying": "Begin Verifying", + "Waiting for partner to accept...": "Waiting for partner to accept...", + "Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .", + "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", + "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:", + "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:", + "Use two-way text verification": "Use two-way text verification", + "Device name": "Device name", + "Device ID": "Device ID", + "Device key": "Device key", + "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.", + "I verify that the keys match": "I verify that the keys match", + "Back": "Back", + "Send Custom Event": "Send Custom Event", + "You must specify an event type!": "You must specify an event type!", + "Event sent!": "Event sent!", + "Failed to send custom event.": "Failed to send custom event.", + "Event Type": "Event Type", + "State Key": "State Key", + "Event Content": "Event Content", + "Send Account Data": "Send Account Data", + "Filter results": "Filter results", + "Explore Room State": "Explore Room State", + "Explore Account Data": "Explore Account Data", + "View Servers in Room": "View Servers in Room", + "Toolbox": "Toolbox", + "Developer Tools": "Developer Tools", + "An error has occurred.": "An error has occurred.", + "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", + "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", + "Waiting for partner to confirm...": "Waiting for partner to confirm...", + "Incoming Verification Request": "Incoming Verification Request", + "Integrations are disabled": "Integrations are disabled", + "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", + "Integrations not allowed": "Integrations not allowed", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", + "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.", + "Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.", + "Failed to find the following users": "Failed to find the following users", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s", + "Recent Conversations": "Recent Conversations", + "Suggestions": "Suggestions", + "Recently Direct Messaged": "Recently Direct Messaged", + "Show more": "Show more", + "Direct Messages": "Direct Messages", + "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", + "Go": "Go", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", + "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", + "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", + "Start verification": "Start verification", + "Share without verifying": "Share without verifying", + "Ignore request": "Ignore request", + "Loading device info...": "Loading device info...", + "Encryption key request": "Encryption key request", + "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.", + "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.", + "Incompatible local cache": "Incompatible local cache", + "Clear cache and resync": "Clear cache and resync", + "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", + "Updating Riot": "Updating Riot", + "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", + "Are you sure you want to sign out?": "Are you sure you want to sign out?", + "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", + "Message edits": "Message edits", + "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.", + "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.", + "Report bugs & give feedback": "Report bugs & give feedback", + "Please fill why you're reporting.": "Please fill why you're reporting.", + "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", + "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.", + "Send report": "Send report", + "Room Settings - %(roomName)s": "Room Settings - %(roomName)s", + "Failed to upgrade room": "Failed to upgrade room", + "The room upgrade could not be completed": "The room upgrade could not be completed", + "Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s", + "Upgrade Room Version": "Upgrade Room Version", + "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:", + "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar", + "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", + "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", + "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages", + "Automatically invite users": "Automatically invite users", + "Upgrade private room": "Upgrade private room", + "Upgrade public room": "Upgrade public room", + "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", + "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.", + "You'll upgrade this room from to .": "You'll upgrade this room from to .", + "Upgrade": "Upgrade", + "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", + "Clear Storage and Sign Out": "Clear Storage and Sign Out", + "Send Logs": "Send Logs", + "Refresh": "Refresh", + "Unable to restore session": "Unable to restore session", + "We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.", + "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", + "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.", + "Verification Pending": "Verification Pending", + "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", + "Email address": "Email address", + "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", + "Skip": "Skip", + "A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'", + "Username not available": "Username not available", + "Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s", + "An error occurred: %(error_string)s": "An error occurred: %(error_string)s", + "Checking...": "Checking...", + "Username available": "Username available", + "To get started, please pick a username!": "To get started, please pick a username!", + "This will be your account name on the homeserver, or you can pick a different server.": "This will be your account name on the homeserver, or you can pick a different server.", + "If you already have a Matrix account you can log in instead.": "If you already have a Matrix account you can log in instead.", + "You have successfully set a password!": "You have successfully set a password!", + "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", + "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.", + "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", + "(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)", + "Please set a password!": "Please set a password!", + "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", + "Share Room": "Share Room", + "Link to most recent message": "Link to most recent message", + "Share User": "Share User", + "Share Community": "Share Community", + "Share Room Message": "Share Room Message", + "Link to selected message": "Link to selected message", + "COPY": "COPY", + "Command Help": "Command Help", + "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", + "Missing session data": "Missing session data", + "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", + "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", + "Integration Manager": "Integration Manager", + "Find others by phone or email": "Find others by phone or email", + "Be found by phone or email": "Be found by phone or email", + "Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs", + "Terms of Service": "Terms of Service", + "To continue you need to accept the terms of this service.": "To continue you need to accept the terms of this service.", + "Service": "Service", + "Summary": "Summary", + "Document": "Document", + "Next": "Next", + "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", + "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", + "Room contains unknown devices": "Room contains unknown devices", + "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", + "Unknown devices": "Unknown devices", + "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", + "Upload files": "Upload files", + "Upload all": "Upload all", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", + "Upload %(count)s other files|other": "Upload %(count)s other files", + "Upload %(count)s other files|one": "Upload %(count)s other file", + "Cancel All": "Cancel All", + "Upload Error": "Upload Error", + "A widget would like to verify your identity": "A widget would like to verify your identity", + "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.": "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.", + "Remember my selection for this widget": "Remember my selection for this widget", + "Allow": "Allow", + "Deny": "Deny", + "Enter secret storage passphrase": "Enter secret storage passphrase", + "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.", + "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.", + "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.", + "Enter secret storage recovery key": "Enter secret storage recovery key", + "This looks like a valid recovery key!": "This looks like a valid recovery key!", + "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", + "Not a valid recovery key": "Not a valid recovery key", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.", + "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", + "Unable to load backup status": "Unable to load backup status", + "Recovery Key Mismatch": "Recovery Key Mismatch", + "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", + "Incorrect Recovery Passphrase": "Incorrect Recovery Passphrase", + "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", + "Unable to restore backup": "Unable to restore backup", + "No backup found!": "No backup found!", + "Backup Restored": "Backup Restored", + "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", + "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", + "Enter Recovery Passphrase": "Enter Recovery Passphrase", + "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", + "Enter Recovery Key": "Enter Recovery Key", + "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", + "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", + "Private Chat": "Private Chat", + "Public Chat": "Public Chat", + "Custom": "Custom", + "Alias (optional)": "Alias (optional)", + "Reject invitation": "Reject invitation", + "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", + "Unable to reject invite": "Unable to reject invite", + "Resend": "Resend", + "Resend edit": "Resend edit", + "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", + "Resend removal": "Resend removal", + "Cancel Sending": "Cancel Sending", + "Forward Message": "Forward Message", + "Pin Message": "Pin Message", + "View Decrypted Source": "View Decrypted Source", + "Unhide Preview": "Unhide Preview", + "Share Permalink": "Share Permalink", + "Share Message": "Share Message", + "Source URL": "Source URL", + "Collapse Reply Thread": "Collapse Reply Thread", + "End-to-end encryption information": "End-to-end encryption information", + "Report Content": "Report Content", + "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", + "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", + "Notification settings": "Notification settings", + "All messages (noisy)": "All messages (noisy)", + "All messages": "All messages", + "Mentions only": "Mentions only", + "Leave": "Leave", + "Forget": "Forget", + "Favourite": "Favourite", + "Low Priority": "Low Priority", + "Clear status": "Clear status", + "Update status": "Update status", + "Set status": "Set status", + "Set a new status...": "Set a new status...", + "View Community": "View Community", + "Hide": "Hide", + "Home": "Home", + "Sign in": "Sign in", + "Help": "Help", + "Reload": "Reload", + "Take picture": "Take picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "User Status": "User Status", + "powered by Matrix": "powered by Matrix", + "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", + "Country Dropdown": "Country Dropdown", + "Custom Server Options": "Custom Server Options", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", + "To continue, please enter your password.": "To continue, please enter your password.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", + "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", + "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", + "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", + "Please check your email to continue registration.": "Please check your email to continue registration.", + "Token incorrect": "Token incorrect", + "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", + "Please enter the code it contains:": "Please enter the code it contains:", + "Code": "Code", + "Submit": "Submit", + "Start authentication": "Start authentication", + "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", + "Your Modular server": "Your Modular server", + "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.", + "Server Name": "Server Name", + "The email field must not be blank.": "The email field must not be blank.", + "The username field must not be blank.": "The username field must not be blank.", + "The phone number field must not be blank.": "The phone number field must not be blank.", + "The password field must not be blank.": "The password field must not be blank.", + "Email": "Email", + "Username": "Username", + "Phone": "Phone", + "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", + "Sign in with": "Sign in with", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", + "Use an email address to recover your account": "Use an email address to recover your account", + "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", + "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Enter password": "Enter password", + "Password is allowed, but unsafe": "Password is allowed, but unsafe", + "Nice, strong password!": "Nice, strong password!", + "Keep going...": "Keep going...", + "Passwords don't match": "Passwords don't match", + "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", + "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", + "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", + "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", + "Enter username": "Enter username", + "Email (optional)": "Email (optional)", + "Confirm": "Confirm", + "Phone (optional)": "Phone (optional)", + "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", + "Create your Matrix account on ": "Create your Matrix account on ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", + "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", + "Homeserver URL": "Homeserver URL", + "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", + "Identity Server URL": "Identity Server URL", + "Other servers": "Other servers", + "Free": "Free", + "Join millions for free on the largest public server": "Join millions for free on the largest public server", + "Premium": "Premium", + "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", + "Find other public servers or use a custom server": "Find other public servers or use a custom server", + "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", + "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", + "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", + "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", + "Please install Chrome, Firefox, or Safari for the best experience.": "Please install Chrome, Firefox, or Safari for the best experience.", + "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!": "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!", + "I understand the risks and wish to continue": "I understand the risks and wish to continue", + "Couldn't load page": "Couldn't load page", + "You must register to use this functionality": "You must register to use this functionality", + "You must join the room to see its files": "You must join the room to see its files", + "There are no visible files in this room": "There are no visible files in this room", + "

    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 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", + "Add rooms to the community summary": "Add rooms to the community summary", + "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", + "Add to summary": "Add to summary", + "Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:", + "Add a Room": "Add a Room", + "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", + "The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.", + "Add users to the community summary": "Add users to the community summary", + "Who would you like to add to this summary?": "Who would you like to add to this summary?", + "Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:", + "Add a User": "Add a User", + "Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s", + "The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.", + "Failed to upload image": "Failed to upload image", + "Failed to update community": "Failed to update community", + "Unable to accept invite": "Unable to accept invite", + "Unable to join community": "Unable to join community", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.", + "Leave Community": "Leave Community", + "Leave %(groupName)s?": "Leave %(groupName)s?", + "Unable to leave community": "Unable to leave community", + "Community Settings": "Community Settings", + "Want more than a community? Get your own server": "Want more than a community? Get your own server", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", + "Featured Rooms:": "Featured Rooms:", + "Featured Users:": "Featured Users:", + "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", + "Join this community": "Join this community", + "Leave this community": "Leave this community", + "You are an administrator of this community": "You are an administrator of this community", + "You are a member of this community": "You are a member of this community", + "Who can join this community?": "Who can join this community?", + "Everyone": "Everyone", + "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!": "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!", + "Long Description (HTML)": "Long Description (HTML)", + "Upload avatar": "Upload avatar", + "Description": "Description", + "Community %(groupId)s not found": "Community %(groupId)s not found", + "This homeserver does not support communities": "This homeserver does not support communities", + "Failed to load %(groupId)s": "Failed to load %(groupId)s", + "Explore": "Explore", + "Filter": "Filter", + "Filter rooms…": "Filter rooms…", + "Failed to reject invitation": "Failed to reject invitation", + "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", + "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", + "Failed to leave room": "Failed to leave room", + "Can't leave Server Notices room": "Can't leave Server Notices room", + "This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.", + "Signed Out": "Signed Out", + "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", + "Terms and Conditions": "Terms and Conditions", + "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.", + "Review terms and conditions": "Review terms and conditions", + "Old cryptography data detected": "Old cryptography data detected", + "Data from an older version of Riot 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 from an older version of Riot 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.", + "Verification Request": "Verification Request", + "Logout": "Logout", + "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", + "Your Communities": "Your Communities", + "Did you know: you can use communities to filter your Riot.im experience!": "Did you know: you can use communities to filter your Riot.im experience!", + "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.": "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.", + "Error whilst fetching joined communities": "Error whilst fetching joined communities", + "Create a new community": "Create a new community", + "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", + "You have no visible notifications": "You have no visible notifications", + "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", + "Riot failed to get the public room list.": "Riot failed to get the public room list.", + "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", + "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?", + "Remove %(name)s from the directory?": "Remove %(name)s from the directory?", + "Remove from Directory": "Remove from Directory", + "remove %(name)s from the directory.": "remove %(name)s from the directory.", + "delete the alias.": "delete the alias.", + "The server may be unavailable or overloaded": "The server may be unavailable or overloaded", + "Unable to join network": "Unable to join network", + "Riot does not know how to join a room on this network": "Riot does not know how to join a room on this network", + "Room not found": "Room not found", + "Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room", + "Fetching third party location failed": "Fetching third party location failed", + "Unable to look up room ID from server": "Unable to look up room ID from server", + "Preview": "Preview", + "View": "View", + "Find a room…": "Find a room…", + "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", + "Explore rooms": "Explore rooms", + "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", + "Show devices, send anyway or cancel.": "Show devices, send anyway or cancel.", + "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", + "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", + "%(count)s of your messages have not been sent.|one": "Your message was not sent.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", + "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", + "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", + "Active call": "Active call", + "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", + "Add room": "Add room", + "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", + "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", + "Search failed": "Search failed", + "Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(", + "No more results": "No more results", + "Unknown room %(roomId)s": "Unknown room %(roomId)s", + "Room": "Room", + "Failed to reject invite": "Failed to reject invite", + "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", + "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", + "Fill screen": "Fill screen", + "Click to unmute video": "Click to unmute video", + "Click to mute video": "Click to mute video", + "Click to unmute audio": "Click to unmute audio", + "Click to mute audio": "Click to mute audio", + "Clear filter": "Clear filter", + "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", + "Failed to load timeline position": "Failed to load timeline position", + " (1/%(totalCount)s)": " (1/%(totalCount)s)", + "Guest": "Guest", + "Your profile": "Your profile", + "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", + "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", + "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", + "Could not load user profile": "Could not load user profile", + "Complete security": "Complete security", + "Verify this session to grant it access to encrypted messages.": "Verify this session to grant it access to encrypted messages.", + "Start": "Start", + "Session verified": "Session verified", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", + "Done": "Done", + "Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.", + "Go Back": "Go Back", + "Failed to send email": "Failed to send email", + "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", + "A new password must be entered.": "A new password must be entered.", + "New passwords must match each other.": "New passwords must match each other.", + "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", + "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", + "Your Matrix account on ": "Your Matrix account on ", + "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", + "Sign in instead": "Sign in instead", + "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", + "Send Reset Email": "Send Reset Email", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", + "I have verified my email address": "I have verified my email address", + "Your password has been reset.": "Your password has been reset.", + "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.", + "Return to login screen": "Return to login screen", + "Set a new password": "Set a new password", + "Invalid homeserver discovery response": "Invalid homeserver discovery response", + "Failed to get autodiscovery configuration from server": "Failed to get autodiscovery configuration from server", + "Invalid base_url for m.homeserver": "Invalid base_url for m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver URL does not appear to be a valid Matrix homeserver", + "Invalid identity server discovery response": "Invalid identity server discovery response", + "Invalid base_url for m.identity_server": "Invalid base_url for m.identity_server", + "Identity server URL does not appear to be a valid identity server": "Identity server URL does not appear to be a valid identity server", + "General failure": "General failure", + "This homeserver does not support login using email address.": "This homeserver does not support login using email address.", + "Please contact your service administrator to continue using this service.": "Please contact your service administrator to continue using this service.", + "This account has been deactivated.": "This account has been deactivated.", + "Incorrect username and/or password.": "Incorrect username and/or password.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", + "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", + "The phone number entered looks invalid": "The phone number entered looks invalid", + "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", + "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", + "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.": "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.", + "Sign in with single sign-on": "Sign in with single sign-on", + "Create account": "Create account", + "Failed to fetch avatar URL": "Failed to fetch avatar URL", + "Set a display name:": "Set a display name:", + "Upload an avatar:": "Upload an avatar:", + "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", + "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", + "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", + "Continue with previous account": "Continue with previous account", + "Log in to your new account.": "Log in to your new account.", + "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", + "Registration Successful": "Registration Successful", + "Create your account": "Create your account", + "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", + "Failed to re-authenticate": "Failed to re-authenticate", + "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.", + "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.", + "Forgotten your password?": "Forgotten your password?", + "Sign in and regain access to your account.": "Sign in and regain access to your account.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", + "You're signed out": "You're signed out", + "Clear personal data": "Clear personal data", + "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.", + "Commands": "Commands", + "Command Autocomplete": "Command Autocomplete", + "Community Autocomplete": "Community Autocomplete", + "Results from DuckDuckGo": "Results from DuckDuckGo", + "DuckDuckGo Results": "DuckDuckGo Results", + "Emoji": "Emoji", + "Emoji Autocomplete": "Emoji Autocomplete", + "Notify the whole room": "Notify the whole room", + "Room Notification": "Room Notification", + "Notification Autocomplete": "Notification Autocomplete", + "Room Autocomplete": "Room Autocomplete", + "Users": "Users", + "User Autocomplete": "User Autocomplete", + "unknown device": "unknown device", + "NOT verified": "NOT verified", + "Blacklisted": "Blacklisted", + "verified": "verified", + "Verification": "Verification", + "Ed25519 fingerprint": "Ed25519 fingerprint", + "User ID": "User ID", + "Curve25519 identity key": "Curve25519 identity key", + "none": "none", + "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", + "Algorithm": "Algorithm", + "unencrypted": "unencrypted", + "Decryption error": "Decryption error", + "Session ID": "Session ID", + "Event information": "Event information", + "Sender device information": "Sender device information", + "Passphrases must match": "Passphrases must match", + "Passphrase must not be empty": "Passphrase must not be empty", + "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", + "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.", + "Restore": "Restore", + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.", + "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", + "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", + "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Enter a passphrase...": "Enter a passphrase...", + "Set up with a recovery key": "Set up with a recovery key", + "That matches!": "That matches!", + "That doesn't match.": "That doesn't match.", + "Go back to set it again.": "Go back to set it again.", + "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", + "Repeat your passphrase...": "Repeat your passphrase...", + "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.", + "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", + "Keep your recovery key somewhere very secure, like a password manager (or a safe).": "Keep your recovery key somewhere very secure, like a password manager (or a safe).", + "Your Recovery Key": "Your Recovery Key", + "Copy to clipboard": "Copy to clipboard", + "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 access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.", + "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.", + "Set up secret storage": "Set up secret storage", + "Restore your Key Backup": "Restore your Key Backup", + "Migrate from Key Backup": "Migrate from Key Backup", + "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase", + "Confirm your passphrase": "Confirm your passphrase", + "Recovery key": "Recovery key", + "Keep it safe": "Keep it safe", + "Storing secrets...": "Storing secrets...", + "Success!": "Success!", + "Unable to set up secret storage": "Unable to set up secret storage", + "Retry": "Retry", + "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", + "Set up with a Recovery Key": "Set up with a Recovery Key", + "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.", + "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.", + "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 device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.", + "Set up Secure Message Recovery": "Set up Secure Message Recovery", + "Secure your backup with a passphrase": "Secure your backup with a passphrase", + "Starting backup...": "Starting backup...", + "Create Key Backup": "Create Key Backup", + "Unable to create key backup": "Unable to create key backup", + "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.", + "Set up": "Set up", + "Don't ask again": "Don't ask again", + "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.", + "This device is encrypting history using the new recovery method.": "This device is encrypting history using the new recovery method.", + "Go to Settings": "Go to Settings", + "Set up Secure Messages": "Set up Secure Messages", + "Recovery Method Removed": "Recovery Method Removed", + "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", + "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", + "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.": "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.", + "Failed to set direct chat tag": "Failed to set direct chat tag", + "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" +} From c5ecd83bc18bc59382be69cff459dfc81347c1e3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 12:34:50 +0000 Subject: [PATCH 218/282] remove .orig file --- src/i18n/strings/en_EN.json.orig | 2040 ------------------------------ 1 file changed, 2040 deletions(-) delete mode 100644 src/i18n/strings/en_EN.json.orig diff --git a/src/i18n/strings/en_EN.json.orig b/src/i18n/strings/en_EN.json.orig deleted file mode 100644 index f0eab6b12d..0000000000 --- a/src/i18n/strings/en_EN.json.orig +++ /dev/null @@ -1,2040 +0,0 @@ -{ - "This email address is already in use": "This email address is already in use", - "This phone number is already in use": "This phone number is already in use", - "Add Email Address": "Add Email Address", - "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", - "Add Phone Number": "Add Phone Number", - "The platform you're on": "The platform you're on", - "The version of Riot.im": "The version of Riot.im", - "Whether or not you're logged in (we don't record your username)": "Whether or not you're logged in (we don't record your username)", - "Your language of choice": "Your language of choice", - "Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)", - "Your homeserver's URL": "Your homeserver's URL", - "Your identity server's URL": "Your identity server's URL", - "e.g. %(exampleValue)s": "e.g. %(exampleValue)s", - "Every page you use in the app": "Every page you use in the app", - "e.g. ": "e.g. ", - "Your User Agent": "Your User Agent", - "Your device resolution": "Your device resolution", - "Analytics": "Analytics", - "The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.", - "Call Failed": "Call Failed", - "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", - "Review Devices": "Review Devices", - "Call Anyway": "Call Anyway", - "Answer Anyway": "Answer Anyway", - "Call": "Call", - "Answer": "Answer", - "Call Timeout": "Call Timeout", - "The remote side failed to pick up": "The remote side failed to pick up", - "Call failed due to misconfigured server": "Call failed due to misconfigured server", - "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.", - "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.", - "Try using turn.matrix.org": "Try using turn.matrix.org", - "OK": "OK", - "Unable to capture screen": "Unable to capture screen", - "Existing Call": "Existing Call", - "You are already in a call.": "You are already in a call.", - "VoIP is unsupported": "VoIP is unsupported", - "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", - "You cannot place a call with yourself.": "You cannot place a call with yourself.", - "Could not connect to the integration server": "Could not connect to the integration server", - "A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available", - "Call in Progress": "Call in Progress", - "A call is currently being placed!": "A call is currently being placed!", - "A call is already in progress!": "A call is already in progress!", - "Permission Required": "Permission Required", - "You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room", - "Replying With Files": "Replying With Files", - "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?", - "Continue": "Continue", - "The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.", - "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", - "Upload Failed": "Upload Failed", - "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", - "The server does not support the room version specified.": "The server does not support the room version specified.", - "Failure to create room": "Failure to create room", - "Send cross-signing keys to homeserver": "Send cross-signing keys to homeserver", - "Send anyway": "Send anyway", - "Send": "Send", - "Sun": "Sun", - "Mon": "Mon", - "Tue": "Tue", - "Wed": "Wed", - "Thu": "Thu", - "Fri": "Fri", - "Sat": "Sat", - "Jan": "Jan", - "Feb": "Feb", - "Mar": "Mar", - "Apr": "Apr", - "May": "May", - "Jun": "Jun", - "Jul": "Jul", - "Aug": "Aug", - "Sep": "Sep", - "Oct": "Oct", - "Nov": "Nov", - "Dec": "Dec", - "PM": "PM", - "AM": "AM", - "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", - "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", - "New Session": "New Session", - "Who would you like to add to this community?": "Who would you like to add to this community?", - "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", - "Invite new community members": "Invite new community members", - "Name or Matrix ID": "Name or Matrix ID", - "Invite to Community": "Invite to Community", - "Which rooms would you like to add to this community?": "Which rooms would you like to add to this community?", - "Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?", - "Add rooms to the community": "Add rooms to the community", - "Room name or alias": "Room name or alias", - "Add to community": "Add to community", - "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", - "Failed to invite users to community": "Failed to invite users to community", - "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", - "Unnamed Room": "Unnamed Room", - "Identity server has no terms of service": "Identity server has no terms of service", - "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", - "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", - "Trust": "Trust", - "Error": "Error", - "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", - "Dismiss": "Dismiss", - "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", - "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", - "Unable to enable Notifications": "Unable to enable Notifications", - "This email address was not found": "This email address was not found", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.", - "Registration Required": "Registration Required", - "You need to register to do this. Would you like to register now?": "You need to register to do this. Would you like to register now?", - "Register": "Register", - "Default": "Default", - "Restricted": "Restricted", - "Moderator": "Moderator", - "Admin": "Admin", - "Custom (%(level)s)": "Custom (%(level)s)", - "Start a chat": "Start a chat", - "Who would you like to communicate with?": "Who would you like to communicate with?", - "Email, name or Matrix ID": "Email, name or Matrix ID", - "Start Chat": "Start Chat", - "Invite new room members": "Invite new room members", - "Send Invites": "Send Invites", - "Failed to start chat": "Failed to start chat", - "Operation failed": "Operation failed", - "Failed to invite": "Failed to invite", - "Failed to invite users to the room:": "Failed to invite users to the room:", - "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", - "You need to be logged in.": "You need to be logged in.", - "You need to be able to invite users to do that.": "You need to be able to invite users to do that.", - "Unable to create widget.": "Unable to create widget.", - "Missing roomId.": "Missing roomId.", - "Failed to send request.": "Failed to send request.", - "This room is not recognised.": "This room is not recognised.", - "Power level must be positive integer.": "Power level must be positive integer.", - "You are not in this room.": "You are not in this room.", - "You do not have permission to do that in this room.": "You do not have permission to do that in this room.", - "Missing room_id in request": "Missing room_id in request", - "Room %(roomId)s not visible": "Room %(roomId)s not visible", - "Missing user_id in request": "Missing user_id in request", - "Messages": "Messages", - "Actions": "Actions", - "Advanced": "Advanced", - "Other": "Other", - "Usage": "Usage", - "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", - "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", - "/ddg is not a command": "/ddg is not a command", - "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", - "Upgrades a room to a new version": "Upgrades a room to a new version", - "You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.", - "Error upgrading room": "Error upgrading room", - "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", - "Changes your display nickname": "Changes your display nickname", - "Changes your display nickname in the current room only": "Changes your display nickname in the current room only", - "Changes the avatar of the current room": "Changes the avatar of the current room", - "Changes your avatar in this current room only": "Changes your avatar in this current room only", - "Changes your avatar in all rooms": "Changes your avatar in all rooms", - "Gets or sets the room topic": "Gets or sets the room topic", - "This room has no topic.": "This room has no topic.", - "Sets the room name": "Sets the room name", - "Invites user with given id to current room": "Invites user with given id to current room", - "Use an identity server": "Use an identity server", - "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.", - "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", - "Joins room with given alias": "Joins room with given alias", - "Leave room": "Leave room", - "Unrecognised room alias:": "Unrecognised room alias:", - "Kicks user with given id": "Kicks user with given id", - "Bans user with given id": "Bans user with given id", - "Unbans user with given ID": "Unbans user with given ID", - "Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you", - "Ignored user": "Ignored user", - "You are now ignoring %(userId)s": "You are now ignoring %(userId)s", - "Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward", - "Unignored user": "Unignored user", - "You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s", - "Define the power level of a user": "Define the power level of a user", - "Deops user with given id": "Deops user with given id", - "Opens the Developer Tools dialog": "Opens the Developer Tools dialog", - "Adds a custom widget by URL to the room": "Adds a custom widget by URL to the room", - "Please supply a https:// or http:// widget URL": "Please supply a https:// or http:// widget URL", - "You cannot modify widgets in this room.": "You cannot modify widgets in this room.", - "Verifies a user, device, and pubkey tuple": "Verifies a user, device, and pubkey tuple", - "Unknown (user, device) pair:": "Unknown (user, device) pair:", - "Device already verified!": "Device already verified!", - "WARNING: Device already verified, but keys do NOT MATCH!": "WARNING: Device already verified, but keys do NOT MATCH!", - "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", - "Verified key": "Verified key", - "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", - "Displays action": "Displays action", - "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", - "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", - "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", - "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", - "Unrecognised command:": "Unrecognised command:", - "Reason": "Reason", - "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", - "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", - "%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.", - "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", - "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", - "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", - "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", - "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", - "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", - "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", - "%(senderName)s made no change.": "%(senderName)s made no change.", - "VoIP conference started.": "VoIP conference started.", - "%(targetName)s joined the room.": "%(targetName)s joined the room.", - "VoIP conference finished.": "VoIP conference finished.", - "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", - "%(targetName)s left the room.": "%(targetName)s left the room.", - "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", - "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", - "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", - "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", - "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", - "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.", - "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.", - "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.", - "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s", - "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", - "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", - "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", - "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", - "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", - "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", - "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", - "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", - "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", - "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.", - "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.", - "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", - "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", - "Someone": "Someone", - "(not supported by this browser)": "(not supported by this browser)", - "%(senderName)s answered the call.": "%(senderName)s answered the call.", - "(could not connect media)": "(could not connect media)", - "(no answer)": "(no answer)", - "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", - "%(senderName)s ended the call.": "%(senderName)s ended the call.", - "%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.", - "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)", - "%(senderName)s placed a video call.": "%(senderName)s placed a video call.", - "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s placed a video call. (not supported by this browser)", - "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.", - "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", - "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", - "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", - "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", - "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", - "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", - "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", - "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removed the rule banning users matching %(glob)s", - "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removed the rule banning rooms matching %(glob)s", - "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removed the rule banning servers matching %(glob)s", - "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removed a ban rule matching %(glob)s", - "%(senderName)s updated an invalid ban rule": "%(senderName)s updated an invalid ban rule", - "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", - "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", - "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", - "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", - "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", - "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s created a ban rule matching %(glob)s for %(reason)s", - "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", - "Light theme": "Light theme", - "Dark theme": "Dark theme", - "%(displayName)s is typing …": "%(displayName)s is typing …", - "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", - "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", - "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", - "Cannot reach homeserver": "Cannot reach homeserver", - "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", - "Your Riot is misconfigured": "Your Riot is misconfigured", - "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Ask your Riot admin to check your config for incorrect or duplicate entries.", - "Cannot reach identity server": "Cannot reach identity server", - "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.", - "No homeserver URL provided": "No homeserver URL provided", - "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", - "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", - "The message you are trying to send is too large.": "The message you are trying to send is too large.", - "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", - "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", - "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", - "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", - "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", - "%(items)s and %(count)s others|one": "%(items)s and one other", - "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", - "a few seconds ago": "a few seconds ago", - "about a minute ago": "about a minute ago", - "%(num)s minutes ago": "%(num)s minutes ago", - "about an hour ago": "about an hour ago", - "%(num)s hours ago": "%(num)s hours ago", - "about a day ago": "about a day ago", - "%(num)s days ago": "%(num)s days ago", - "a few seconds from now": "a few seconds from now", - "about a minute from now": "about a minute from now", - "%(num)s minutes from now": "%(num)s minutes from now", - "about an hour from now": "about an hour from now", - "%(num)s hours from now": "%(num)s hours from now", - "about a day from now": "about a day from now", - "%(num)s days from now": "%(num)s days from now", - "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", - "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", - "Not a valid Riot keyfile": "Not a valid Riot keyfile", - "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", - "Unrecognised address": "Unrecognised address", - "You do not have permission to invite people to this room.": "You do not have permission to invite people to this room.", - "User %(userId)s is already in the room": "User %(userId)s is already in the room", - "User %(user_id)s does not exist": "User %(user_id)s does not exist", - "User %(user_id)s may or may not exist": "User %(user_id)s may or may not exist", - "The user must be unbanned before they can be invited.": "The user must be unbanned before they can be invited.", - "The user's homeserver does not support the version of the room.": "The user's homeserver does not support the version of the room.", - "Unknown server error": "Unknown server error", - "Use a few words, avoid common phrases": "Use a few words, avoid common phrases", - "No need for symbols, digits, or uppercase letters": "No need for symbols, digits, or uppercase letters", - "Use a longer keyboard pattern with more turns": "Use a longer keyboard pattern with more turns", - "Avoid repeated words and characters": "Avoid repeated words and characters", - "Avoid sequences": "Avoid sequences", - "Avoid recent years": "Avoid recent years", - "Avoid years that are associated with you": "Avoid years that are associated with you", - "Avoid dates and years that are associated with you": "Avoid dates and years that are associated with you", - "Capitalization doesn't help very much": "Capitalization doesn't help very much", - "All-uppercase is almost as easy to guess as all-lowercase": "All-uppercase is almost as easy to guess as all-lowercase", - "Reversed words aren't much harder to guess": "Reversed words aren't much harder to guess", - "Predictable substitutions like '@' instead of 'a' don't help very much": "Predictable substitutions like '@' instead of 'a' don't help very much", - "Add another word or two. Uncommon words are better.": "Add another word or two. Uncommon words are better.", - "Repeats like \"aaa\" are easy to guess": "Repeats like \"aaa\" are easy to guess", - "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"", - "Sequences like abc or 6543 are easy to guess": "Sequences like abc or 6543 are easy to guess", - "Recent years are easy to guess": "Recent years are easy to guess", - "Dates are often easy to guess": "Dates are often easy to guess", - "This is a top-10 common password": "This is a top-10 common password", - "This is a top-100 common password": "This is a top-100 common password", - "This is a very common password": "This is a very common password", - "This is similar to a commonly used password": "This is similar to a commonly used password", - "A word by itself is easy to guess": "A word by itself is easy to guess", - "Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess", - "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", - "There was an error joining the room": "There was an error joining the room", - "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", - "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", - "Failed to join room": "Failed to join room", - "Message Pinning": "Message Pinning", - "Custom user status messages": "Custom user status messages", - "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", - "Render simple counters in room header": "Render simple counters in room header", - "Multiple integration managers": "Multiple integration managers", - "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "New invite dialog": "New invite dialog", - "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", - "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", - "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", - "Show info about bridges in room settings": "Show info about bridges in room settings", - "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", - "Use compact timeline layout": "Use compact timeline layout", - "Show a placeholder for removed messages": "Show a placeholder for removed messages", - "Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)", - "Show avatar changes": "Show avatar changes", - "Show display name changes": "Show display name changes", - "Show read receipts sent by other users": "Show read receipts sent by other users", - "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", - "Always show encryption icons": "Always show encryption icons", - "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", - "Send typing notifications": "Send typing notifications", - "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", - "Mirror local video feed": "Mirror local video feed", - "Enable Community Filter Panel": "Enable Community Filter Panel", - "Match system theme": "Match system theme", - "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", - "Send analytics data": "Send analytics data", - "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", - "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", - "Enable inline URL previews by default": "Enable inline URL previews by default", - "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", - "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", - "Room Colour": "Room Colour", - "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", - "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", - "Show developer tools": "Show developer tools", - "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", - "Show recently visited rooms above the room list": "Show recently visited rooms above the room list", - "Show hidden events in timeline": "Show hidden events in timeline", - "Low bandwidth mode": "Low bandwidth mode", - "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)", - "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", - "Show previews/thumbnails for images": "Show previews/thumbnails for images", - "Collecting app version information": "Collecting app version information", - "Collecting logs": "Collecting logs", - "Uploading report": "Uploading report", - "Waiting for response from server": "Waiting for response from server", - "Messages containing my display name": "Messages containing my display name", - "Messages containing my username": "Messages containing my username", - "Messages containing @room": "Messages containing @room", - "Messages in one-to-one chats": "Messages in one-to-one chats", - "Encrypted messages in one-to-one chats": "Encrypted messages in one-to-one chats", - "Messages in group chats": "Messages in group chats", - "Encrypted messages in group chats": "Encrypted messages in group chats", - "When I'm invited to a room": "When I'm invited to a room", - "Call invitation": "Call invitation", - "Messages sent by bot": "Messages sent by bot", - "When rooms are upgraded": "When rooms are upgraded", - "My Ban List": "My Ban List", - "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", - "Active call (%(roomName)s)": "Active call (%(roomName)s)", - "unknown caller": "unknown caller", - "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", - "Incoming video call from %(name)s": "Incoming video call from %(name)s", - "Incoming call from %(name)s": "Incoming call from %(name)s", - "Decline": "Decline", - "Accept": "Accept", - "The other party cancelled the verification.": "The other party cancelled the verification.", - "Verified!": "Verified!", - "You've successfully verified this user.": "You've successfully verified this user.", - "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.", - "Got It": "Got It", - "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", - "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", - "Unable to find a supported verification method.": "Unable to find a supported verification method.", - "Cancel": "Cancel", - "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.", - "Dog": "Dog", - "Cat": "Cat", - "Lion": "Lion", - "Horse": "Horse", - "Unicorn": "Unicorn", - "Pig": "Pig", - "Elephant": "Elephant", - "Rabbit": "Rabbit", - "Panda": "Panda", - "Rooster": "Rooster", - "Penguin": "Penguin", - "Turtle": "Turtle", - "Fish": "Fish", - "Octopus": "Octopus", - "Butterfly": "Butterfly", - "Flower": "Flower", - "Tree": "Tree", - "Cactus": "Cactus", - "Mushroom": "Mushroom", - "Globe": "Globe", - "Moon": "Moon", - "Cloud": "Cloud", - "Fire": "Fire", - "Banana": "Banana", - "Apple": "Apple", - "Strawberry": "Strawberry", - "Corn": "Corn", - "Pizza": "Pizza", - "Cake": "Cake", - "Heart": "Heart", - "Smiley": "Smiley", - "Robot": "Robot", - "Hat": "Hat", - "Glasses": "Glasses", - "Spanner": "Spanner", - "Santa": "Santa", - "Thumbs up": "Thumbs up", - "Umbrella": "Umbrella", - "Hourglass": "Hourglass", - "Clock": "Clock", - "Gift": "Gift", - "Light bulb": "Light bulb", - "Book": "Book", - "Pencil": "Pencil", - "Paperclip": "Paperclip", - "Scissors": "Scissors", - "Lock": "Lock", - "Key": "Key", - "Hammer": "Hammer", - "Telephone": "Telephone", - "Flag": "Flag", - "Train": "Train", - "Bicycle": "Bicycle", - "Aeroplane": "Aeroplane", - "Rocket": "Rocket", - "Trophy": "Trophy", - "Ball": "Ball", - "Guitar": "Guitar", - "Trumpet": "Trumpet", - "Bell": "Bell", - "Anchor": "Anchor", - "Headphones": "Headphones", - "Folder": "Folder", - "Pin": "Pin", - "Other users may not trust it": "Other users may not trust it", - "Later": "Later", - "Verify": "Verify", - "Decline (%(counter)s)": "Decline (%(counter)s)", - "Accept to continue:": "Accept to continue:", - "Upload": "Upload", - "Remove": "Remove", - "Failed to upload profile picture!": "Failed to upload profile picture!", - "Upload new:": "Upload new:", - "No display name": "No display name", - "New passwords don't match": "New passwords don't match", - "Passwords can't be empty": "Passwords can't be empty", - "Warning!": "Warning!", - "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", - "Export E2E room keys": "Export E2E room keys", - "Do you want to set an email address?": "Do you want to set an email address?", - "Current password": "Current password", - "Password": "Password", - "New Password": "New Password", - "Confirm password": "Confirm password", - "Change Password": "Change Password", - "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", - "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.", - "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", - "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", - "Cross-signing public keys:": "Cross-signing public keys:", - "on device": "on device", - "not found": "not found", - "Cross-signing private keys:": "Cross-signing private keys:", - "in secret storage": "in secret storage", - "Secret storage public key:": "Secret storage public key:", - "in account data": "in account data", - "Your homeserver does not support device management.": "Your homeserver does not support device management.", - "Unable to load device list": "Unable to load device list", - "Authentication": "Authentication", - "Delete %(count)s devices|other": "Delete %(count)s devices", - "Delete %(count)s devices|one": "Delete device", - "ID": "ID", - "Public Name": "Public Name", - "Last seen": "Last seen", - "Failed to set display name": "Failed to set display name", - "Disable Notifications": "Disable Notifications", - "Enable Notifications": "Enable Notifications", - "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 device is backing up your keys. ": "This device is backing up your keys. ", - "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", - "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.", - "Connect this device to Key Backup": "Connect this device 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 device with ID %(deviceId)s": "Backup has a signature from unknown device with ID %(deviceId)s", - "Backup has a valid signature from this device": "Backup has a valid signature from this device", - "Backup has an invalid signature from this device": "Backup has an invalid signature from this device", - "Backup has a valid signature from verified device ": "Backup has a valid signature from verified device ", - "Backup has a valid signature from unverified device ": "Backup has a valid signature from unverified device ", - "Backup has an invalid signature from verified device ": "Backup has an invalid signature from verified device ", - "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", - "Backup is not signed by any of your devices": "Backup is not signed by any of your devices", - "This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device", - "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", - "Your keys are not being backed up from this device.": "Your keys are not being backed up from this device.", - "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", - "Enter keywords separated by a comma:": "Enter keywords separated by a comma:", - "Failed to change settings": "Failed to change settings", - "Can't update user notification settings": "Can't update user notification settings", - "Failed to update keywords": "Failed to update keywords", - "Messages containing keywords": "Messages containing keywords", - "Notify for all other messages/rooms": "Notify for all other messages/rooms", - "Notify me for anything else": "Notify me for anything else", - "Enable notifications for this account": "Enable notifications for this account", - "Clear notifications": "Clear notifications", - "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.", - "Add an email address to configure email notifications": "Add an email address to configure email notifications", - "Enable email notifications": "Enable email notifications", - "Notifications on the following keywords follow rules which can’t be displayed here:": "Notifications on the following keywords follow rules which can’t be displayed here:", - "Unable to fetch notification target list": "Unable to fetch notification target list", - "Notification targets": "Notification targets", - "Advanced notification settings": "Advanced notification settings", - "There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here", - "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply", - "Enable desktop notifications for this device": "Enable desktop notifications for this device", - "Show message in desktop notification": "Show message in desktop notification", - "Enable audible notifications for this device": "Enable audible notifications for this device", - "Off": "Off", - "On": "On", - "Noisy": "Noisy", - "Upgrade to your own domain": "Upgrade to your own domain", - "Display Name": "Display Name", - "Profile picture": "Profile picture", - "Save": "Save", - "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", - "Checking server": "Checking server", - "Change identity server": "Change identity server", - "Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?", - "Terms of service not accepted or the identity server is invalid.": "Terms of service not accepted or the identity server is invalid.", - "The identity server you have chosen does not have any terms of service.": "The identity server you have chosen does not have any terms of service.", - "Disconnect identity server": "Disconnect identity server", - "Disconnect from the identity server ?": "Disconnect from the identity server ?", - "Disconnect": "Disconnect", - "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.", - "You should:": "You should:", - "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "check your browser plugins for anything that might block the identity server (such as Privacy Badger)", - "contact the administrators of identity server ": "contact the administrators of identity server ", - "wait and try again later": "wait and try again later", - "Disconnect anyway": "Disconnect anyway", - "You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .", - "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.", - "Go back": "Go back", - "Identity Server (%(server)s)": "Identity Server (%(server)s)", - "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", - "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.", - "Identity Server": "Identity Server", - "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.", - "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.": "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.", - "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.": "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.", - "Do not use an identity server": "Do not use an identity server", - "Enter a new identity server": "Enter a new identity server", - "Change": "Change", - "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.", - "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", - "Manage integrations": "Manage integrations", - "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.", - "Flair": "Flair", - "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", - "Success": "Success", - "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", - "Profile": "Profile", - "Email addresses": "Email addresses", - "Phone numbers": "Phone numbers", - "Account": "Account", - "Set a new account password...": "Set a new account password...", - "Language and region": "Language and region", - "Theme": "Theme", - "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", - "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", - "Warning": "Warning", - "General": "General", - "Discovery": "Discovery", - "Deactivate account": "Deactivate account", - "Legal": "Legal", - "Credits": "Credits", - "For help with using Riot, click here.": "For help with using Riot, click here.", - "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.", - "Chat with Riot Bot": "Chat with Riot Bot", - "Check for update": "Check for update", - "Help & About": "Help & About", - "Bug reporting": "Bug reporting", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", - "Submit debug logs": "Submit debug logs", - "Clear cache and reload": "Clear cache and reload", - "FAQ": "FAQ", - "Versions": "Versions", - "matrix-react-sdk version:": "matrix-react-sdk version:", - "riot-web version:": "riot-web version:", - "olm version:": "olm version:", - "Homeserver is": "Homeserver is", - "Identity Server is": "Identity Server is", - "Access Token:": "Access Token:", - "click to reveal": "click to reveal", - "Labs": "Labs", - "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", - "Ignored/Blocked": "Ignored/Blocked", - "Error adding ignored user/server": "Error adding ignored user/server", - "Something went wrong. Please try again or view your console for hints.": "Something went wrong. Please try again or view your console for hints.", - "Error subscribing to list": "Error subscribing to list", - "Please verify the room ID or alias and try again.": "Please verify the room ID or alias and try again.", - "Error removing ignored user/server": "Error removing ignored user/server", - "Error unsubscribing from list": "Error unsubscribing from list", - "Please try again or view your console for hints.": "Please try again or view your console for hints.", - "None": "None", - "Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s", - "Server rules": "Server rules", - "User rules": "User rules", - "Close": "Close", - "You have not ignored anyone.": "You have not ignored anyone.", - "You are currently ignoring:": "You are currently ignoring:", - "You are not subscribed to any lists": "You are not subscribed to any lists", - "Unsubscribe": "Unsubscribe", - "View rules": "View rules", - "You are currently subscribed to:": "You are currently subscribed to:", - "Ignored users": "Ignored users", - "⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.", - "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.", - "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.": "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.", - "Personal ban list": "Personal ban list", - "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.": "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.", - "Server or user ID to ignore": "Server or user ID to ignore", - "eg: @bot:* or example.org": "eg: @bot:* or example.org", - "Ignore": "Ignore", - "Subscribed lists": "Subscribed lists", - "Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!", - "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", - "Room ID or alias of ban list": "Room ID or alias of ban list", - "Subscribe": "Subscribe", - "Notifications": "Notifications", - "Start automatically after system login": "Start automatically after system login", - "Always show the window menu bar": "Always show the window menu bar", - "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", - "Preferences": "Preferences", - "Composer": "Composer", - "Timeline": "Timeline", - "Room list": "Room list", - "Autocomplete delay (ms)": "Autocomplete delay (ms)", - "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", - "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", - "Unignore": "Unignore", - "": "", - "Import E2E room keys": "Import E2E room keys", - "Cryptography": "Cryptography", - "Device ID:": "Device ID:", - "Device key:": "Device key:", - "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", - "Cross-signing": "Cross-signing", - "Security & Privacy": "Security & Privacy", - "Devices": "Devices", - "A device's public name is visible to people you communicate with": "A device's public name is visible to people you communicate with", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot 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.", - "No media permissions": "No media permissions", - "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", - "Request media permissions": "Request media permissions", - "No Audio Outputs detected": "No Audio Outputs detected", - "No Microphones detected": "No Microphones detected", - "No Webcams detected": "No Webcams detected", - "Default Device": "Default Device", - "Audio Output": "Audio Output", - "Microphone": "Microphone", - "Camera": "Camera", - "Voice & Video": "Voice & Video", - "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", - "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", - "this room": "this room", - "View older messages in %(roomName)s.": "View older messages in %(roomName)s.", - "Room information": "Room information", - "Internal room ID:": "Internal room ID:", - "Room version": "Room version", - "Room version:": "Room version:", - "Developer options": "Developer options", - "Open Devtools": "Open Devtools", - "This bridge was provisioned by ": "This bridge was provisioned by ", - "This bridge is managed by .": "This bridge is managed by .", - "Bridged into , on ": "Bridged into , on ", - "Connected to on ": "Connected to on ", - "Connected via %(protocolName)s": "Connected via %(protocolName)s", - "Bridge Info": "Bridge Info", - "Below is a list of bridges connected to this room.": "Below is a list of bridges connected to this room.", - "Room Addresses": "Room Addresses", - "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", - "URL Previews": "URL Previews", - "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", - "Change room name": "Change room name", - "Change main address for the room": "Change main address for the room", - "Change history visibility": "Change history visibility", - "Change permissions": "Change permissions", - "Change topic": "Change topic", - "Upgrade the room": "Upgrade the room", - "Enable room encryption": "Enable room encryption", - "Modify widgets": "Modify widgets", - "Failed to unban": "Failed to unban", - "Unban": "Unban", - "Banned by %(displayName)s": "Banned by %(displayName)s", - "Error changing power level requirement": "Error changing power level requirement", - "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.", - "Error changing power level": "Error changing power level", - "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.", - "Default role": "Default role", - "Send messages": "Send messages", - "Invite users": "Invite users", - "Change settings": "Change settings", - "Kick users": "Kick users", - "Ban users": "Ban users", - "Remove messages": "Remove messages", - "Notify everyone": "Notify everyone", - "No users have specific privileges in this room": "No users have specific privileges in this room", - "Privileged Users": "Privileged Users", - "Muted Users": "Muted Users", - "Banned users": "Banned users", - "Send %(eventType)s events": "Send %(eventType)s events", - "Roles & Permissions": "Roles & Permissions", - "Permissions": "Permissions", - "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", - "Enable encryption?": "Enable encryption?", - "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.": "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.", - "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", - "Click here to fix": "Click here to fix", - "To link to this room, please add an alias.": "To link to this room, please add an alias.", - "Only people who have been invited": "Only people who have been invited", - "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", - "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", - "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.", - "Anyone": "Anyone", - "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", - "Members only (since they were invited)": "Members only (since they were invited)", - "Members only (since they joined)": "Members only (since they joined)", - "Encryption": "Encryption", - "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", - "Encrypted": "Encrypted", - "Who can access this room?": "Who can access this room?", - "Who can read history?": "Who can read history?", - "Unable to revoke sharing for email address": "Unable to revoke sharing for email address", - "Unable to share email address": "Unable to share email address", - "Your email address hasn't been verified yet": "Your email address hasn't been verified yet", - "Click the link in the email you received to verify and then click continue again.": "Click the link in the email you received to verify and then click continue again.", - "Unable to verify email address.": "Unable to verify email address.", - "Verify the link in your inbox": "Verify the link in your inbox", - "Complete": "Complete", - "Revoke": "Revoke", - "Share": "Share", - "Discovery options will appear once you have added an email above.": "Discovery options will appear once you have added an email above.", - "Unable to revoke sharing for phone number": "Unable to revoke sharing for phone number", - "Unable to share phone number": "Unable to share phone number", - "Unable to verify phone number.": "Unable to verify phone number.", - "Incorrect verification code": "Incorrect verification code", - "Please enter verification code sent via text.": "Please enter verification code sent via text.", - "Verification code": "Verification code", - "Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.", - "Unable to remove contact information": "Unable to remove contact information", - "Remove %(email)s?": "Remove %(email)s?", - "Invalid Email Address": "Invalid Email Address", - "This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address", - "Unable to add email address": "Unable to add email address", - "Add": "Add", - "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.", - "Email Address": "Email Address", - "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", - " (unsupported)": " (unsupported)", - "Join as voice or video.": "Join as voice or video.", - "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", - "This user has not verified all of their devices.": "This user has not verified all of their devices.", - "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", - "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", - "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", - "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", - "Some devices for this user are not trusted": "Some devices for this user are not trusted", - "All devices for this user are trusted": "All devices for this user are trusted", - "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", - "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", - "Edit message": "Edit message", - "This event could not be displayed": "This event could not be displayed", - "%(senderName)s sent an image": "%(senderName)s sent an image", - "%(senderName)s sent a video": "%(senderName)s sent a video", - "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", - "Your key share request has been sent - please check your other devices for key share requests.": "Your key share request has been sent - please check your other devices for key share requests.", - "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.", - "If your other devices do not have the key for this message you will not be able to decrypt them.": "If your other devices do not have the key for this message you will not be able to decrypt them.", - "Key request sent.": "Key request sent.", - "Re-request encryption keys from your other devices.": "Re-request encryption keys from your other devices.", - "This message cannot be decrypted": "This message cannot be decrypted", - "Encrypted by an unverified device": "Encrypted by an unverified device", - "Unencrypted": "Unencrypted", - "Please select the destination room for this message": "Please select the destination room for this message", - "Scroll to bottom of page": "Scroll to bottom of page", - "Close preview": "Close preview", - "device id: ": "device id: ", - "Disinvite": "Disinvite", - "Kick": "Kick", - "Disinvite this user?": "Disinvite this user?", - "Kick this user?": "Kick this user?", - "Failed to kick": "Failed to kick", - "Ban": "Ban", - "Unban this user?": "Unban this user?", - "Ban this user?": "Ban this user?", - "Failed to ban user": "Failed to ban user", - "No recent messages by %(user)s found": "No recent messages by %(user)s found", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", - "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", - "Remove %(count)s messages|other": "Remove %(count)s messages", - "Remove %(count)s messages|one": "Remove 1 message", - "Demote yourself?": "Demote yourself?", - "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.": "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.", - "Demote": "Demote", - "Failed to mute user": "Failed to mute user", - "Failed to toggle moderator status": "Failed to toggle moderator status", - "Deactivate user?": "Deactivate user?", - "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", - "Deactivate user": "Deactivate user", - "Failed to deactivate user": "Failed to deactivate user", - "Failed to change power level": "Failed to change power level", - "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.", - "Are you sure?": "Are you sure?", - "No devices with registered encryption keys": "No devices with registered encryption keys", - "Jump to read receipt": "Jump to read receipt", - "Mention": "Mention", - "Invite": "Invite", - "Share Link to User": "Share Link to User", - "User Options": "User Options", - "Direct chats": "Direct chats", - "Remove recent messages": "Remove recent messages", - "Unmute": "Unmute", - "Mute": "Mute", - "Revoke Moderator": "Revoke Moderator", - "Make Moderator": "Make Moderator", - "Admin Tools": "Admin Tools", - "and %(count)s others...|other": "and %(count)s others...", - "and %(count)s others...|one": "and one other...", - "Invite to this room": "Invite to this room", - "Invited": "Invited", - "Filter room members": "Filter room members", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", - "Voice call": "Voice call", - "Video call": "Video call", - "Hangup": "Hangup", - "Upload file": "Upload file", - "Send an encrypted reply…": "Send an encrypted reply…", - "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", - "Send an encrypted message…": "Send an encrypted message…", - "Send a message (unencrypted)…": "Send a message (unencrypted)…", - "The conversation continues here.": "The conversation continues here.", - "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", - "You do not have permission to post to this room": "You do not have permission to post to this room", - "Bold": "Bold", - "Italics": "Italics", - "Strikethrough": "Strikethrough", - "Code block": "Code block", - "Quote": "Quote", - "No pinned messages.": "No pinned messages.", - "Loading...": "Loading...", - "Pinned Messages": "Pinned Messages", - "Unpin Message": "Unpin Message", - "Jump to message": "Jump to message", - "%(duration)ss": "%(duration)ss", - "%(duration)sm": "%(duration)sm", - "%(duration)sh": "%(duration)sh", - "%(duration)sd": "%(duration)sd", - "Online for %(duration)s": "Online for %(duration)s", - "Idle for %(duration)s": "Idle for %(duration)s", - "Offline for %(duration)s": "Offline for %(duration)s", - "Unknown for %(duration)s": "Unknown for %(duration)s", - "Online": "Online", - "Idle": "Idle", - "Offline": "Offline", - "Unknown": "Unknown", - "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", - "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", - "Replying": "Replying", - "Direct Chat": "Direct Chat", - "Room %(name)s": "Room %(name)s", - "Recent rooms": "Recent rooms", - "No rooms to show": "No rooms to show", - "Unnamed room": "Unnamed room", - "World readable": "World readable", - "Guests can join": "Guests can join", - "(~%(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", - "Community Invites": "Community Invites", - "Invites": "Invites", - "Favourites": "Favourites", - "People": "People", - "Start chat": "Start chat", - "Rooms": "Rooms", - "Low priority": "Low priority", - "Historical": "Historical", - "System Alerts": "System Alerts", - "This room": "This room", - "Joining room …": "Joining room …", - "Loading …": "Loading …", - "Rejecting invite …": "Rejecting invite …", - "Join the conversation with an account": "Join the conversation with an account", - "Sign Up": "Sign Up", - "Sign In": "Sign In", - "Loading room preview": "Loading room preview", - "You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s", - "Reason: %(reason)s": "Reason: %(reason)s", - "Forget this room": "Forget this room", - "Re-join": "Re-join", - "You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s", - "Something went wrong with your invite to %(roomName)s": "Something went wrong with your invite to %(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.": "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.", - "unknown error code": "unknown error code", - "You can only join it with a working invite.": "You can only join it with a working invite.", - "Try to join anyway": "Try to join anyway", - "You can still join it because this is a public room.": "You can still join it because this is a public room.", - "Join the discussion": "Join the discussion", - "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "This invite to %(roomName)s was sent to %(email)s which is not associated with your account", - "Link this email with your account in Settings to receive invites directly in Riot.": "Link this email with your account in Settings to receive invites directly in Riot.", - "This invite to %(roomName)s was sent to %(email)s": "This invite to %(roomName)s was sent to %(email)s", - "Use an identity server in Settings to receive invites directly in Riot.": "Use an identity server in Settings to receive invites directly in Riot.", - "Share this email in Settings to receive invites directly in Riot.": "Share this email in Settings to receive invites directly in Riot.", - "Do you want to chat with %(user)s?": "Do you want to chat with %(user)s?", - " wants to chat": " wants to chat", - "Start chatting": "Start chatting", - "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", - " invited you": " invited you", - "Reject": "Reject", - "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", - "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", - "%(roomName)s does not exist.": "%(roomName)s does not exist.", - "This room doesn't exist. Are you sure you're at the right place?": "This room doesn't exist. Are you sure you're at the right place?", - "%(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.", - "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", - "Options": "Options", - "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", - "%(count)s unread messages including mentions.|one": "1 unread mention.", - "%(count)s unread messages.|other": "%(count)s unread messages.", - "%(count)s unread messages.|one": "1 unread message.", - "Unread mentions.": "Unread mentions.", - "Unread messages.": "Unread messages.", - "Add a topic": "Add a topic", - "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", - "This room has already been upgraded.": "This room has already been upgraded.", - "This room is running room version , which this homeserver has marked as unstable.": "This room is running room version , which this homeserver has marked as unstable.", - "Only room administrators will see this warning": "Only room administrators will see this warning", - "This Room": "This Room", - "All Rooms": "All Rooms", - "Search…": "Search…", - "Server error": "Server error", - "Command error": "Command error", - "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", - "Failed to connect to integration manager": "Failed to connect to integration manager", - "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", - "Add some now": "Add some now", - "Stickerpack": "Stickerpack", - "Hide Stickers": "Hide Stickers", - "Show Stickers": "Show Stickers", - "Failed to revoke invite": "Failed to revoke invite", - "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.", - "Revoke invite": "Revoke invite", - "Invited by %(sender)s": "Invited by %(sender)s", - "Jump to first unread message.": "Jump to first unread message.", - "Error updating main address": "Error updating main address", - "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", - "Error creating alias": "Error creating alias", - "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", - "Error removing alias": "Error removing alias", - "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", - "Main address": "Main address", - "not specified": "not specified", - "Remote addresses for this room:": "Remote addresses for this room:", - "Local addresses for this room:": "Local addresses for this room:", - "This room has no local addresses": "This room has no local addresses", - "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", - "Error updating flair": "Error updating flair", - "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", - "Invalid community ID": "Invalid community ID", - "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", - "Showing flair for these communities:": "Showing flair for these communities:", - "This room is not showing flair for any communities": "This room is not showing flair for any communities", - "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", - "Room Name": "Room Name", - "Room Topic": "Room Topic", - "Room avatar": "Room avatar", - "You have enabled URL previews by default.": "You have enabled URL previews by default.", - "You have disabled URL previews by default.": "You have disabled URL previews by default.", - "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", - "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.", - "Verify User": "Verify User", - "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.", - "For maximum security, do this in person.": "For maximum security, do this in person.", - "Start Verification": "Start Verification", - "Members": "Members", - "Files": "Files", - "Trusted": "Trusted", - "Not trusted": "Not trusted", - "Hide verified sessions": "Hide verified sessions", - "%(count)s verified sessions|other": "%(count)s verified sessions", - "%(count)s verified sessions|one": "1 verified session", - "Direct message": "Direct message", - "Remove from community": "Remove from community", - "Disinvite this user from community?": "Disinvite this user from community?", - "Remove this user from community?": "Remove this user from community?", - "Failed to withdraw invitation": "Failed to withdraw invitation", - "Failed to remove user from community": "Failed to remove user from community", - "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", - "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", - "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", - "Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.", - "Security": "Security", - "Sunday": "Sunday", - "Monday": "Monday", - "Tuesday": "Tuesday", - "Wednesday": "Wednesday", - "Thursday": "Thursday", - "Friday": "Friday", - "Saturday": "Saturday", - "Today": "Today", - "Yesterday": "Yesterday", - "View Source": "View Source", - "Error decrypting audio": "Error decrypting audio", - "React": "React", - "Reply": "Reply", - "Edit": "Edit", - "Message Actions": "Message Actions", - "Attachment": "Attachment", - "Error decrypting attachment": "Error decrypting attachment", - "Decrypt %(text)s": "Decrypt %(text)s", - "Download %(text)s": "Download %(text)s", - "Invalid file%(extra)s": "Invalid file%(extra)s", - "Error decrypting image": "Error decrypting image", - "Show image": "Show image", - "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", - "You verified %(name)s": "You verified %(name)s", - "You cancelled verifying %(name)s": "You cancelled verifying %(name)s", - "%(name)s cancelled verifying": "%(name)s cancelled verifying", - "You accepted": "You accepted", - "%(name)s accepted": "%(name)s accepted", - "You cancelled": "You cancelled", - "%(name)s cancelled": "%(name)s cancelled", - "%(name)s wants to verify": "%(name)s wants to verify", - "You sent a verification request": "You sent a verification request", - "Error decrypting video": "Error decrypting video", - "Show all": "Show all", - "Reactions": "Reactions", - " reacted with %(content)s": " reacted with %(content)s", - "reacted with %(shortName)s": "reacted with %(shortName)s", - "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", - "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", - "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", - "This room is a continuation of another conversation.": "This room is a continuation of another conversation.", - "Click here to see older messages.": "Click here to see older messages.", - "Copied!": "Copied!", - "Failed to copy": "Failed to copy", - "Add an Integration": "Add an 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?": "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?", - "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", - "edited": "edited", - "Removed or unknown message type": "Removed or unknown message type", - "Message removed by %(userId)s": "Message removed by %(userId)s", - "Message removed": "Message removed", - "Failed to load group members": "Failed to load group members", - "Filter community members": "Filter community members", - "Invite to this community": "Invite to this community", - "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", - "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.", - "Failed to remove room from community": "Failed to remove room from community", - "Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s", - "Something went wrong!": "Something went wrong!", - "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.", - "Visibility in Room List": "Visibility in Room List", - "Visible to everyone": "Visible to everyone", - "Only visible to community members": "Only visible to community members", - "Add rooms to this community": "Add rooms to this community", - "Filter community rooms": "Filter community rooms", - "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", - "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", - "You're not currently a member of any communities.": "You're not currently a member of any communities.", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.", - "Yes, I want to help!": "Yes, I want to help!", - "You are not receiving desktop notifications": "You are not receiving desktop notifications", - "Enable them now": "Enable them now", - "What's New": "What's New", - "Update": "Update", - "What's new?": "What's new?", - "A new version of Riot is available.": "A new version of Riot is available.", - "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", - "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.", - "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.", - "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.", - "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", - "Checking for an update...": "Checking for an update...", - "No update available.": "No update available.", - "Downloading update...": "Downloading update...", - "Frequently Used": "Frequently Used", - "Smileys & People": "Smileys & People", - "Animals & Nature": "Animals & Nature", - "Food & Drink": "Food & Drink", - "Activities": "Activities", - "Travel & Places": "Travel & Places", - "Objects": "Objects", - "Symbols": "Symbols", - "Flags": "Flags", - "Quick Reactions": "Quick Reactions", - "Cancel search": "Cancel search", - "Unknown Address": "Unknown Address", - "Any of the following data may be shared:": "Any of the following data may be shared:", - "Your display name": "Your display name", - "Your avatar URL": "Your avatar URL", - "Your user ID": "Your user ID", - "Your theme": "Your theme", - "Riot URL": "Riot URL", - "Room ID": "Room ID", - "Widget ID": "Widget ID", - "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", - "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", - "Widgets do not use message encryption.": "Widgets do not use message encryption.", - "Widget added by": "Widget added by", - "This widget may use cookies.": "This widget may use cookies.", - "Delete Widget": "Delete Widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", - "Delete widget": "Delete widget", - "Failed to remove widget": "Failed to remove widget", - "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", - "Minimize apps": "Minimize apps", - "Maximize apps": "Maximize apps", - "Popout widget": "Popout widget", - "More options": "More options", - "Create new room": "Create new room", - "Unblacklist": "Unblacklist", - "Blacklist": "Blacklist", - "Unverify": "Unverify", - "Verify...": "Verify...", - "Join": "Join", - "No results": "No results", - "Yes": "Yes", - "No": "No", - "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", - "collapse": "collapse", - "expand": "expand", - "Communities": "Communities", - "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", - "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", - "Rotate Left": "Rotate Left", - "Rotate counter-clockwise": "Rotate counter-clockwise", - "Rotate Right": "Rotate Right", - "Rotate clockwise": "Rotate clockwise", - "Download this file": "Download this file", - "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", - "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", - "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", - "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", - "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", - "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", - "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", - "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", - "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", - "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", - "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", - "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", - "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", - "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", - "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", - "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", - "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", - "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", - "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", - "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", - "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", - "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", - "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", - "were invited %(count)s times|other": "were invited %(count)s times", - "were invited %(count)s times|one": "were invited", - "was invited %(count)s times|other": "was invited %(count)s times", - "was invited %(count)s times|one": "was invited", - "were banned %(count)s times|other": "were banned %(count)s times", - "were banned %(count)s times|one": "were banned", - "was banned %(count)s times|other": "was banned %(count)s times", - "was banned %(count)s times|one": "was banned", - "were unbanned %(count)s times|other": "were unbanned %(count)s times", - "were unbanned %(count)s times|one": "were unbanned", - "was unbanned %(count)s times|other": "was unbanned %(count)s times", - "was unbanned %(count)s times|one": "was unbanned", - "were kicked %(count)s times|other": "were kicked %(count)s times", - "were kicked %(count)s times|one": "were kicked", - "was kicked %(count)s times|other": "was kicked %(count)s times", - "was kicked %(count)s times|one": "was kicked", - "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", - "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", - "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", - "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", - "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)smade no changes %(count)s times", - "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes", - "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times", - "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", - "Power level": "Power level", - "Custom level": "Custom level", - "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", - "In reply to ": "In reply to ", - "Room alias": "Room alias", - "e.g. my-room": "e.g. my-room", - "Some characters not allowed": "Some characters not allowed", - "Please provide a room alias": "Please provide a room alias", - "This alias is available to use": "This alias is available to use", - "This alias is already in use": "This alias is already in use", - "Room directory": "Room directory", - "And %(count)s more...|other": "And %(count)s more...", - "ex. @bob:example.com": "ex. @bob:example.com", - "Add User": "Add User", - "Matrix ID": "Matrix ID", - "Matrix Room ID": "Matrix Room ID", - "email address": "email address", - "That doesn't look like a valid email address": "That doesn't look like a valid email address", - "You have entered an invalid address.": "You have entered an invalid address.", - "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.", - "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.", - "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", - "The following users may not exist": "The following users may not exist", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", - "Invite anyway and never warn me again": "Invite anyway and never warn me again", - "Invite anyway": "Invite anyway", - "Close dialog": "Close dialog", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", - "Preparing to send logs": "Preparing to send logs", - "Logs sent": "Logs sent", - "Thank you!": "Thank you!", - "Failed to send logs: ": "Failed to send logs: ", - "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.": "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.", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.", - "GitHub issue": "GitHub issue", - "Notes": "Notes", - "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.": "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.", - "Send logs": "Send logs", - "Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s", - "Unavailable": "Unavailable", - "Changelog": "Changelog", - "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", - "Removing…": "Removing…", - "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 on this device?": "Clear all data on this device?", - "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this device is permanent. Encrypted messages will be lost unless their keys have been backed up.", - "Clear all data": "Clear all data", - "Community IDs cannot be empty.": "Community IDs cannot be empty.", - "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'", - "Something went wrong whilst creating your community": "Something went wrong whilst creating your community", - "Create Community": "Create Community", - "Community Name": "Community Name", - "Example": "Example", - "Community ID": "Community ID", - "example": "example", - "Create": "Create", - "Please enter a name for the room": "Please enter a name for the room", - "Set a room alias to easily share your room with other people.": "Set a room alias to easily share your room with other people.", - "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", - "Create a public room": "Create a public room", - "Create a private room": "Create a private room", - "Name": "Name", - "Topic (optional)": "Topic (optional)", - "Make this room public": "Make this room public", - "Hide advanced": "Hide advanced", - "Show advanced": "Show advanced", - "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", - "Create Room": "Create Room", - "Sign out": "Sign out", - "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 Riot to do this": "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 Riot to do this", - "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", - "Incompatible Database": "Incompatible Database", - "Continue With Encryption Disabled": "Continue With Encryption Disabled", - "Unknown error": "Unknown error", - "Incorrect password": "Incorrect password", - "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.", - "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)", - "To continue, please enter your password:": "To continue, please enter your password:", - "Verify device": "Verify device", - "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", - "Verify by comparing a short text string.": "Verify by comparing a short text string.", - "Begin Verifying": "Begin Verifying", - "Waiting for partner to accept...": "Waiting for partner to accept...", - "Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .", - "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", - "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:", - "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:", - "Use two-way text verification": "Use two-way text verification", - "Device name": "Device name", - "Device ID": "Device ID", - "Device key": "Device key", - "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.", - "I verify that the keys match": "I verify that the keys match", - "Back": "Back", - "Send Custom Event": "Send Custom Event", - "You must specify an event type!": "You must specify an event type!", - "Event sent!": "Event sent!", - "Failed to send custom event.": "Failed to send custom event.", - "Event Type": "Event Type", - "State Key": "State Key", - "Event Content": "Event Content", - "Send Account Data": "Send Account Data", - "Filter results": "Filter results", - "Explore Room State": "Explore Room State", - "Explore Account Data": "Explore Account Data", - "View Servers in Room": "View Servers in Room", - "Toolbox": "Toolbox", - "Developer Tools": "Developer Tools", - "An error has occurred.": "An error has occurred.", - "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", - "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", - "Waiting for partner to confirm...": "Waiting for partner to confirm...", - "Incoming Verification Request": "Incoming Verification Request", - "Integrations are disabled": "Integrations are disabled", - "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", - "Integrations not allowed": "Integrations not allowed", - "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", - "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s", - "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.", - "Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.", - "We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.", - "Failed to find the following users": "Failed to find the following users", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s", - "Recent Conversations": "Recent Conversations", - "Suggestions": "Suggestions", - "Recently Direct Messaged": "Recently Direct Messaged", - "Show more": "Show more", - "Direct Messages": "Direct Messages", - "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", - "Go": "Go", - "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", - "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", - "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", - "Start verification": "Start verification", - "Share without verifying": "Share without verifying", - "Ignore request": "Ignore request", - "Loading device info...": "Loading device info...", - "Encryption key request": "Encryption key request", - "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.", - "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.", - "Incompatible local cache": "Incompatible local cache", - "Clear cache and resync": "Clear cache and resync", - "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", - "Updating Riot": "Updating Riot", - "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", - "Are you sure you want to sign out?": "Are you sure you want to sign out?", - "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", - "Message edits": "Message edits", - "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.", - "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.", - "Report bugs & give feedback": "Report bugs & give feedback", - "Please fill why you're reporting.": "Please fill why you're reporting.", - "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", - "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.", - "Send report": "Send report", - "Room Settings - %(roomName)s": "Room Settings - %(roomName)s", - "Failed to upgrade room": "Failed to upgrade room", - "The room upgrade could not be completed": "The room upgrade could not be completed", - "Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s", - "Upgrade Room Version": "Upgrade Room Version", - "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:", - "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar", - "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", - "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", - "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages", - "Automatically invite users": "Automatically invite users", - "Upgrade private room": "Upgrade private room", - "Upgrade public room": "Upgrade public room", - "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", - "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.", - "You'll upgrade this room from to .": "You'll upgrade this room from to .", - "Upgrade": "Upgrade", - "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", - "Clear Storage and Sign Out": "Clear Storage and Sign Out", - "Send Logs": "Send Logs", - "Refresh": "Refresh", - "Unable to restore session": "Unable to restore session", - "We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.", - "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.", - "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.", - "Verification Pending": "Verification Pending", - "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", - "Email address": "Email address", - "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", - "Skip": "Skip", - "A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'", - "Username not available": "Username not available", - "Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s", - "An error occurred: %(error_string)s": "An error occurred: %(error_string)s", - "Checking...": "Checking...", - "Username available": "Username available", - "To get started, please pick a username!": "To get started, please pick a username!", - "This will be your account name on the homeserver, or you can pick a different server.": "This will be your account name on the homeserver, or you can pick a different server.", - "If you already have a Matrix account you can log in instead.": "If you already have a Matrix account you can log in instead.", - "You have successfully set a password!": "You have successfully set a password!", - "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", - "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.", - "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", - "(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)", - "Please set a password!": "Please set a password!", - "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", - "Share Room": "Share Room", - "Link to most recent message": "Link to most recent message", - "Share User": "Share User", - "Share Community": "Share Community", - "Share Room Message": "Share Room Message", - "Link to selected message": "Link to selected message", - "COPY": "COPY", - "Command Help": "Command Help", - "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", - "Missing session data": "Missing session data", - "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", - "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", - "Integration Manager": "Integration Manager", - "Find others by phone or email": "Find others by phone or email", - "Be found by phone or email": "Be found by phone or email", - "Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs", - "Terms of Service": "Terms of Service", - "To continue you need to accept the terms of this service.": "To continue you need to accept the terms of this service.", - "Service": "Service", - "Summary": "Summary", - "Document": "Document", - "Next": "Next", - "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", - "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", - "Room contains unknown devices": "Room contains unknown devices", - "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", - "Unknown devices": "Unknown devices", - "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", - "Upload files": "Upload files", - "Upload all": "Upload all", - "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", - "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", - "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", - "Upload %(count)s other files|other": "Upload %(count)s other files", - "Upload %(count)s other files|one": "Upload %(count)s other file", - "Cancel All": "Cancel All", - "Upload Error": "Upload Error", - "A widget would like to verify your identity": "A widget would like to verify your identity", - "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.": "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.", - "Remember my selection for this widget": "Remember my selection for this widget", - "Allow": "Allow", - "Deny": "Deny", - "Enter secret storage passphrase": "Enter secret storage passphrase", - "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.", - "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", - "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.", - "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.", - "Enter secret storage recovery key": "Enter secret storage recovery key", - "This looks like a valid recovery key!": "This looks like a valid recovery key!", - "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", - "Not a valid recovery key": "Not a valid recovery key", - "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.", - "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", - "Unable to load backup status": "Unable to load backup status", - "Recovery Key Mismatch": "Recovery Key Mismatch", - "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", - "Incorrect Recovery Passphrase": "Incorrect Recovery Passphrase", - "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", - "Unable to restore backup": "Unable to restore backup", - "No backup found!": "No backup found!", - "Backup Restored": "Backup Restored", - "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", - "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", - "Enter Recovery Passphrase": "Enter Recovery Passphrase", - "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", - "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", - "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", - "Enter Recovery Key": "Enter Recovery Key", - "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", - "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", - "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", - "Private Chat": "Private Chat", - "Public Chat": "Public Chat", - "Custom": "Custom", - "Alias (optional)": "Alias (optional)", - "Reject invitation": "Reject invitation", - "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", - "Unable to reject invite": "Unable to reject invite", - "Resend": "Resend", - "Resend edit": "Resend edit", - "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", - "Resend removal": "Resend removal", - "Cancel Sending": "Cancel Sending", - "Forward Message": "Forward Message", - "Pin Message": "Pin Message", - "View Decrypted Source": "View Decrypted Source", - "Unhide Preview": "Unhide Preview", - "Share Permalink": "Share Permalink", - "Share Message": "Share Message", - "Source URL": "Source URL", - "Collapse Reply Thread": "Collapse Reply Thread", - "End-to-end encryption information": "End-to-end encryption information", - "Report Content": "Report Content", - "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", - "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", - "Notification settings": "Notification settings", - "All messages (noisy)": "All messages (noisy)", - "All messages": "All messages", - "Mentions only": "Mentions only", - "Leave": "Leave", - "Forget": "Forget", - "Favourite": "Favourite", - "Low Priority": "Low Priority", - "Clear status": "Clear status", - "Update status": "Update status", - "Set status": "Set status", - "Set a new status...": "Set a new status...", - "View Community": "View Community", - "Hide": "Hide", - "Home": "Home", - "Sign in": "Sign in", - "Help": "Help", - "Reload": "Reload", - "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", - "User Status": "User Status", - "powered by Matrix": "powered by Matrix", - "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", - "Country Dropdown": "Country Dropdown", - "Custom Server Options": "Custom Server Options", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", - "To continue, please enter your password.": "To continue, please enter your password.", - "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", - "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", - "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", - "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", - "Please check your email to continue registration.": "Please check your email to continue registration.", - "Token incorrect": "Token incorrect", - "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", - "Please enter the code it contains:": "Please enter the code it contains:", - "Code": "Code", - "Submit": "Submit", - "Start authentication": "Start authentication", - "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", - "Your Modular server": "Your Modular server", - "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.", - "Server Name": "Server Name", - "The email field must not be blank.": "The email field must not be blank.", - "The username field must not be blank.": "The username field must not be blank.", - "The phone number field must not be blank.": "The phone number field must not be blank.", - "The password field must not be blank.": "The password field must not be blank.", - "Email": "Email", - "Username": "Username", - "Phone": "Phone", - "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", - "Sign in with": "Sign in with", - "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", - "Use an email address to recover your account": "Use an email address to recover your account", - "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", - "Doesn't look like a valid email address": "Doesn't look like a valid email address", - "Enter password": "Enter password", - "Password is allowed, but unsafe": "Password is allowed, but unsafe", - "Nice, strong password!": "Nice, strong password!", - "Keep going...": "Keep going...", - "Passwords don't match": "Passwords don't match", - "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", - "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", - "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", - "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", - "Enter username": "Enter username", - "Email (optional)": "Email (optional)", - "Confirm": "Confirm", - "Phone (optional)": "Phone (optional)", - "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Create your Matrix account on ": "Create your Matrix account on ", - "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", - "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", - "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", - "Homeserver URL": "Homeserver URL", - "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", - "Identity Server URL": "Identity Server URL", - "Other servers": "Other servers", - "Free": "Free", - "Join millions for free on the largest public server": "Join millions for free on the largest public server", - "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", - "Find other public servers or use a custom server": "Find other public servers or use a custom server", - "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", - "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", - "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", - "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", - "Please install Chrome, Firefox, or Safari for the best experience.": "Please install Chrome, Firefox, or Safari for the best experience.", - "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!": "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!", - "I understand the risks and wish to continue": "I understand the risks and wish to continue", - "Couldn't load page": "Couldn't load page", - "You must register to use this functionality": "You must register to use this functionality", - "You must join the room to see its files": "You must join the room to see its files", - "There are no visible files in this room": "There are no visible files in this room", - "

    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 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", - "Add rooms to the community summary": "Add rooms to the community summary", - "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", - "Add to summary": "Add to summary", - "Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:", - "Add a Room": "Add a Room", - "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", - "The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.", - "Add users to the community summary": "Add users to the community summary", - "Who would you like to add to this summary?": "Who would you like to add to this summary?", - "Failed to add the following users to the summary of %(groupId)s:": "Failed to add the following users to the summary of %(groupId)s:", - "Add a User": "Add a User", - "Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s", - "The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.", - "Failed to upload image": "Failed to upload image", - "Failed to update community": "Failed to update community", - "Unable to accept invite": "Unable to accept invite", - "Unable to join community": "Unable to join community", - "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.", - "Leave Community": "Leave Community", - "Leave %(groupName)s?": "Leave %(groupName)s?", - "Unable to leave community": "Unable to leave community", - "Community Settings": "Community Settings", - "Want more than a community? Get your own server": "Want more than a community? Get your own server", - "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", - "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", - "Featured Rooms:": "Featured Rooms:", - "Featured Users:": "Featured Users:", - "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", - "Join this community": "Join this community", - "Leave this community": "Leave this community", - "You are an administrator of this community": "You are an administrator of this community", - "You are a member of this community": "You are a member of this community", - "Who can join this community?": "Who can join this community?", - "Everyone": "Everyone", - "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!": "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!", - "Long Description (HTML)": "Long Description (HTML)", - "Upload avatar": "Upload avatar", - "Description": "Description", - "Community %(groupId)s not found": "Community %(groupId)s not found", - "This homeserver does not support communities": "This homeserver does not support communities", - "Failed to load %(groupId)s": "Failed to load %(groupId)s", - "Explore": "Explore", - "Filter": "Filter", - "Filter rooms…": "Filter rooms…", - "Failed to reject invitation": "Failed to reject invitation", - "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", - "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", - "Failed to leave room": "Failed to leave room", - "Can't leave Server Notices room": "Can't leave Server Notices room", - "This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.", - "Signed Out": "Signed Out", - "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", - "Terms and Conditions": "Terms and Conditions", - "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.", - "Review terms and conditions": "Review terms and conditions", - "Old cryptography data detected": "Old cryptography data detected", - "Data from an older version of Riot 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 from an older version of Riot 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.", - "Verification Request": "Verification Request", - "Logout": "Logout", - "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", - "Your Communities": "Your Communities", - "Did you know: you can use communities to filter your Riot.im experience!": "Did you know: you can use communities to filter your Riot.im experience!", - "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.": "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.", - "Error whilst fetching joined communities": "Error whilst fetching joined communities", - "Create a new community": "Create a new community", - "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", - "You have no visible notifications": "You have no visible notifications", - "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", - "Riot failed to get the public room list.": "Riot failed to get the public room list.", - "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", - "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?", - "Remove %(name)s from the directory?": "Remove %(name)s from the directory?", - "Remove from Directory": "Remove from Directory", - "remove %(name)s from the directory.": "remove %(name)s from the directory.", - "delete the alias.": "delete the alias.", - "The server may be unavailable or overloaded": "The server may be unavailable or overloaded", - "Unable to join network": "Unable to join network", - "Riot does not know how to join a room on this network": "Riot does not know how to join a room on this network", - "Room not found": "Room not found", - "Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room", - "Fetching third party location failed": "Fetching third party location failed", - "Unable to look up room ID from server": "Unable to look up room ID from server", - "Preview": "Preview", - "View": "View", - "Find a room…": "Find a room…", - "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", - "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", - "Explore rooms": "Explore rooms", - "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", - "Show devices, send anyway or cancel.": "Show devices, send anyway or cancel.", - "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", - "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", - "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", - "%(count)s of your messages have not been sent.|one": "Your message was not sent.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", - "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", - "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", - "Active call": "Active call", - "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", - "Jump to first unread room.": "Jump to first unread room.", - "Jump to first invite.": "Jump to first invite.", - "Add room": "Add room", - "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", - "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", - "Search failed": "Search failed", - "Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(", - "No more results": "No more results", - "Unknown room %(roomId)s": "Unknown room %(roomId)s", - "Room": "Room", - "Failed to reject invite": "Failed to reject invite", - "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", - "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", - "Fill screen": "Fill screen", - "Click to unmute video": "Click to unmute video", - "Click to mute video": "Click to mute video", - "Click to unmute audio": "Click to unmute audio", - "Click to mute audio": "Click to mute audio", - "Clear filter": "Clear filter", - "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", - "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", - "Failed to load timeline position": "Failed to load timeline position", - " (1/%(totalCount)s)": " (1/%(totalCount)s)", - "Guest": "Guest", - "Your profile": "Your profile", - "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", - "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", - "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", - "Could not load user profile": "Could not load user profile", - "Complete security": "Complete security", - "Verify this session to grant it access to encrypted messages.": "Verify this session to grant it access to encrypted messages.", - "Start": "Start", - "Session verified": "Session verified", - "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", - "Done": "Done", - "Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.", - "Go Back": "Go Back", - "Failed to send email": "Failed to send email", - "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", - "A new password must be entered.": "A new password must be entered.", - "New passwords must match each other.": "New passwords must match each other.", - "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.", - "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", - "Your Matrix account on ": "Your Matrix account on ", - "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", - "Sign in instead": "Sign in instead", - "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", - "Send Reset Email": "Send Reset Email", - "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", - "I have verified my email address": "I have verified my email address", - "Your password has been reset.": "Your password has been reset.", - "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.", - "Return to login screen": "Return to login screen", - "Set a new password": "Set a new password", - "Invalid homeserver discovery response": "Invalid homeserver discovery response", - "Failed to get autodiscovery configuration from server": "Failed to get autodiscovery configuration from server", - "Invalid base_url for m.homeserver": "Invalid base_url for m.homeserver", - "Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver URL does not appear to be a valid Matrix homeserver", - "Invalid identity server discovery response": "Invalid identity server discovery response", - "Invalid base_url for m.identity_server": "Invalid base_url for m.identity_server", - "Identity server URL does not appear to be a valid identity server": "Identity server URL does not appear to be a valid identity server", - "General failure": "General failure", - "This homeserver does not support login using email address.": "This homeserver does not support login using email address.", - "Please contact your service administrator to continue using this service.": "Please contact your service administrator to continue using this service.", - "This account has been deactivated.": "This account has been deactivated.", - "Incorrect username and/or password.": "Incorrect username and/or password.", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", - "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", - "The phone number entered looks invalid": "The phone number entered looks invalid", - "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", - "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", - "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.": "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.", - "Sign in with single sign-on": "Sign in with single sign-on", - "Create account": "Create account", - "Failed to fetch avatar URL": "Failed to fetch avatar URL", - "Set a display name:": "Set a display name:", - "Upload an avatar:": "Upload an avatar:", - "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", - "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", - "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", - "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", - "Continue with previous account": "Continue with previous account", - "Log in to your new account.": "Log in to your new account.", - "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", - "Registration Successful": "Registration Successful", - "Create your account": "Create your account", - "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", - "Failed to re-authenticate": "Failed to re-authenticate", - "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.", - "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.", - "Forgotten your password?": "Forgotten your password?", - "Sign in and regain access to your account.": "Sign in and regain access to your account.", - "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", - "You're signed out": "You're signed out", - "Clear personal data": "Clear personal data", - "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.", - "Commands": "Commands", - "Command Autocomplete": "Command Autocomplete", - "Community Autocomplete": "Community Autocomplete", - "Results from DuckDuckGo": "Results from DuckDuckGo", - "DuckDuckGo Results": "DuckDuckGo Results", - "Emoji": "Emoji", - "Emoji Autocomplete": "Emoji Autocomplete", - "Notify the whole room": "Notify the whole room", - "Room Notification": "Room Notification", - "Notification Autocomplete": "Notification Autocomplete", - "Room Autocomplete": "Room Autocomplete", - "Users": "Users", - "User Autocomplete": "User Autocomplete", - "unknown device": "unknown device", - "NOT verified": "NOT verified", - "Blacklisted": "Blacklisted", - "verified": "verified", - "Verification": "Verification", - "Ed25519 fingerprint": "Ed25519 fingerprint", - "User ID": "User ID", - "Curve25519 identity key": "Curve25519 identity key", - "none": "none", - "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", - "Algorithm": "Algorithm", - "unencrypted": "unencrypted", - "Decryption error": "Decryption error", - "Session ID": "Session ID", - "Event information": "Event information", - "Sender device information": "Sender device information", - "Passphrases must match": "Passphrases must match", - "Passphrase must not be empty": "Passphrase must not be empty", - "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", - "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.", - "Restore": "Restore", - "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.", - "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", - "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", - "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", - "Enter a passphrase...": "Enter a passphrase...", - "Set up with a recovery key": "Set up with a recovery key", - "That matches!": "That matches!", - "That doesn't match.": "That doesn't match.", - "Go back to set it again.": "Go back to set it again.", - "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", - "Repeat your passphrase...": "Repeat your passphrase...", - "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.": "As a safety net, you can use it to restore your access to encrypted messages if you forget your passphrase.", - "As a safety net, you can use it to restore your access to encrypted messages.": "As a safety net, you can use it to restore your access to encrypted messages.", - "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", - "Keep your recovery key somewhere very secure, like a password manager (or a safe).": "Keep your recovery key somewhere very secure, like a password manager (or a safe).", - "Your Recovery Key": "Your Recovery Key", - "Copy to clipboard": "Copy to clipboard", - "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 access to encrypted messages is now protected.": "Your access to encrypted messages is now protected.", - "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.", - "Set up secret storage": "Set up secret storage", - "Restore your Key Backup": "Restore your Key Backup", - "Migrate from Key Backup": "Migrate from Key Backup", - "Secure your encrypted messages with a passphrase": "Secure your encrypted messages with a passphrase", - "Confirm your passphrase": "Confirm your passphrase", - "Recovery key": "Recovery key", - "Keep it safe": "Keep it safe", - "Storing secrets...": "Storing secrets...", - "Success!": "Success!", - "Unable to set up secret storage": "Unable to set up secret storage", - "Retry": "Retry", - "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", - "Set up with a Recovery Key": "Set up with a Recovery Key", - "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.", - "As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.", - "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 device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.", - "Set up Secure Message Recovery": "Set up Secure Message Recovery", - "Secure your backup with a passphrase": "Secure your backup with a passphrase", - "Starting backup...": "Starting backup...", - "Create Key Backup": "Create Key Backup", - "Unable to create key backup": "Unable to create key backup", - "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.", - "Set up": "Set up", - "Don't ask again": "Don't ask again", - "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.", - "This device is encrypting history using the new recovery method.": "This device is encrypting history using the new recovery method.", - "Go to Settings": "Go to Settings", - "Set up Secure Messages": "Set up Secure Messages", - "Recovery Method Removed": "Recovery Method Removed", - "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.", - "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.", - "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.": "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.", - "Failed to set direct chat tag": "Failed to set direct chat tag", - "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" -} From 590ff29e60bac1283c15c512a7afc5089e60de94 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 13:15:57 +0000 Subject: [PATCH 219/282] Unused import --- src/AsyncWrapper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js index 63b856a882..b7b81688e1 100644 --- a/src/AsyncWrapper.js +++ b/src/AsyncWrapper.js @@ -16,7 +16,6 @@ limitations under the License. */ import createReactClass from 'create-react-class'; -import Analytics from './Analytics'; import * as sdk from './index'; import PropTypes from 'prop-types'; import { _t } from './languageHandler'; From 66768416a628c10c0c80ecc7e0099c36e9b8a89b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 24 Jan 2020 14:20:48 +0100 Subject: [PATCH 220/282] dont assume the room exist ... not sure why though --- src/utils/KeyVerificationStateObserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 7da532109c..a29d2ea1aa 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -20,7 +20,7 @@ import { _t } from '../languageHandler'; export function getNameForEventRoom(userId, roomId) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); - const member = room.getMember(userId); + const member = room && room.getMember(userId); return member ? member.name : userId; } From 3534cd42023882785cc83c6222a1e478302f8131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:23:43 +0100 Subject: [PATCH 221/282] FilePanel: Add comments to explain what's going on with the event index. --- src/components/structures/FilePanel.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index e03c587e61..4c02f925fc 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,8 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + // This is used to track if a decrypted event was a live event and should be + // added to the timeline. decryptingEvents: new Set(), propTypes: { @@ -84,6 +86,14 @@ const FilePanel = createReactClass({ if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + // The timelineSets filter makes sure that encrypted events that contain + // URLs never get added to the timeline, even if they are live events. + // These methods are here to manually listen for such events and add + // them despite the filter's best efforts. + // + // We do this only for encrypted rooms and if an event index exists, + // this could be made more general in the future or the filter logic + // could be fixed. if (EventIndexPeg.get() !== null) { client.on('Room.timeline', this.onRoomTimeline.bind(this)); client.on('Event.decrypted', this.onEventDecrypted.bind(this)); @@ -133,6 +143,10 @@ const FilePanel = createReactClass({ const room = client.getRoom(roomId); + // We override the pagination request for encrypted rooms so that we ask + // the event index to fulfill the pagination request. Asking the server + // to paginate won't ever work since the server can't correctly filter + // out events containing URLs if (client.isRoomEncrypted(roomId) && eventIndex !== null) { return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); } else { @@ -153,6 +167,15 @@ const FilePanel = createReactClass({ try { timelineSet = await this.fetchFileEventsServer(room); + // If this room is encrypted the file panel won't be populated + // correctly since the defined filter doesn't support encrypted + // events and the server can't check if encrypted events contain + // URLs. + // + // This is where our event index comes into place, we ask the + // event index to populate the timelineSet for us. This call + // will add 10 events to the live timeline of the set. More can + // be requested using pagination. if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); From 37f289b120ff23752204fd6a98b47b79b648df67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:24:25 +0100 Subject: [PATCH 222/282] EventIndex: Add docstrings for the FilePanel methods. --- src/indexing/EventIndex.js | 72 +++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 2b432ab1a1..b6e29c455d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -407,6 +407,27 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } + /** + * Load events that contain URLs from the event index. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to an array of events that + * contain URLs. + */ async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); @@ -472,6 +493,33 @@ export default class EventIndex { return matrixEvents; } + /** + * Fill a timeline with events that contain URLs. + * + * @param {TimelineSet} timelineSet The TimelineSet the Timeline belongs to, + * used to check if we're adding duplicate events. + * + * @param {Timeline} timeline The Timeline which should be filed with + * events. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to true if events were added to the + * timeline, false otherwise. + */ async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); @@ -486,7 +534,7 @@ export default class EventIndex { direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; } - // Add the events to the live timeline of the file panel. + // Add the events to the timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); @@ -503,6 +551,28 @@ export default class EventIndex { } } + /** + * Emulate a TimelineWindow pagination() request with the event index as the event source + * + * Might not fetch events from the index if the timeline already contains + * events that the window isn't showing. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {TimelineWindow} timelineWindow The timeline window that should be + * populated with new events. + * + * @param {string} direction The direction in which we should paginate. + * EventTimeline.BACKWARDS to paginate back, EventTimeline.FORWARDS to + * paginate forwards. + * + * @param {number} limit The maximum number of events to fetch while + * paginating. + * + * @returns {Promise} Resolves to a boolean which is true if more + * events were successfully retrieved. + */ paginateTimelineWindow(room, timelineWindow, direction, limit) { const tl = timelineWindow.getTimelineIndex(direction); From 6ebeb0a376f9fab634f48c731b82740e5921af11 Mon Sep 17 00:00:00 2001 From: random Date: Fri, 24 Jan 2020 13:30:03 +0000 Subject: [PATCH 223/282] Translated using Weblate (Italian) Currently translated at 98.7% (2031 of 2057 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index d6ebc985fb..6a021e8ab7 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2079,5 +2079,7 @@ "Verify User": "Verifica utente", "For extra security, verify this user by checking a one-time code on both of your devices.": "Per maggiore sicurezza, verifica questo utente controllando un codice univoco sui vostri dispositivi.", "For maximum security, do this in person.": "Per massima sicurezza, fatelo di persona.", - "Start Verification": "Inizia la verifica" + "Start Verification": "Inizia la verifica", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s ha aggiunto %(addedAddresses)s e %(count)s altri indirizzi a questa stanza", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s ha rimosso %(removedAddresses)s e %(count)s altri indirizzi da questa stanza" } From be98f24051f212f5b9d7cda96aba00f697b019ff Mon Sep 17 00:00:00 2001 From: random Date: Fri, 24 Jan 2020 13:31:06 +0000 Subject: [PATCH 224/282] Translated using Weblate (Italian) Currently translated at 98.8% (2032 of 2057 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 6a021e8ab7..c9a6c27221 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2081,5 +2081,6 @@ "For maximum security, do this in person.": "Per massima sicurezza, fatelo di persona.", "Start Verification": "Inizia la verifica", "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s ha aggiunto %(addedAddresses)s e %(count)s altri indirizzi a questa stanza", - "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s ha rimosso %(removedAddresses)s e %(count)s altri indirizzi da questa stanza" + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s ha rimosso %(removedAddresses)s e %(count)s altri indirizzi da questa stanza", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s ha rimosso %(countRemoved)s e aggiunto %(countAdded)s indirizzi a questa stanza" } From 75da5b7944ded503a50b099a2a467abec3c40858 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 24 Jan 2020 12:59:46 +0000 Subject: [PATCH 225/282] Design pass for room icons --- src/components/views/rooms/RoomList.js | 2 +- src/components/views/rooms/RoomTile.js | 6 +++--- src/i18n/strings/en_EN.json | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index ee3100b535..f41400ecfc 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -719,7 +719,7 @@ export default createReactClass({ }, { list: this.state.lists['im.vector.fake.direct'], - label: _t('People'), + label: _t('Direct Messages'), tagName: "im.vector.fake.direct", order: "recent", incomingCall: incomingCallIfTaggedAs('im.vector.fake.direct'), diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 0b50d85ff6..c546b7503e 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -478,7 +478,8 @@ export default createReactClass({ let dmIndicator; let dmOnline; - if (dmUserId) { + // If we can place a shield, do that instead + if (dmUserId && !this.state.e2eStatus) { dmIndicator = ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 099b64dd49..1d5f534a36 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1012,7 +1012,7 @@ "Community Invites": "Community Invites", "Invites": "Invites", "Favourites": "Favourites", - "People": "People", + "Direct Messages": "Direct Messages", "Start chat": "Start chat", "Rooms": "Rooms", "Low priority": "Low priority", @@ -1471,7 +1471,6 @@ "Suggestions": "Suggestions", "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", - "Direct Messages": "Direct Messages", "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", From a46646105f29cb68714e0c0794136b452b37833d Mon Sep 17 00:00:00 2001 From: random Date: Fri, 24 Jan 2020 13:31:48 +0000 Subject: [PATCH 226/282] Translated using Weblate (Italian) Currently translated at 100.0% (2057 of 2057 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index c9a6c27221..c8844fb2bb 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2082,5 +2082,30 @@ "Start Verification": "Inizia la verifica", "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s ha aggiunto %(addedAddresses)s e %(count)s altri indirizzi a questa stanza", "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s ha rimosso %(removedAddresses)s e %(count)s altri indirizzi da questa stanza", - "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s ha rimosso %(countRemoved)s e aggiunto %(countAdded)s indirizzi a questa stanza" + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s ha rimosso %(countRemoved)s e aggiunto %(countAdded)s indirizzi a questa stanza", + "Someone is using an unknown device": "Qualcuno sta usando un dispositivo sconosciuto", + "This room is end-to-end encrypted": "Questa stanza è cifrata end-to-end", + "Everyone in this room is verified": "Tutti in questa stanza sono verificati", + "Invite only": "Solo a invito", + "Send a reply…": "Invia risposta…", + "Send a message…": "Invia un messaggio…", + "Reject & Ignore user": "Rifiuta e ignora l'utente", + "Unknown Command": "Comando sconosciuto", + "Unrecognised command: %(commandText)s": "Comando non riconosciuto: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Puoi usare /help per elencare i comandi disponibili. Volevo forse inviarlo come messaggio?", + "Hint: Begin your message with // to start it with a slash.": "Suggerimento: anteponi al tuo messaggio // per farlo iniziare con uno slash.", + "Send as message": "Invia come messaggio", + "Enter your account password to confirm the upgrade:": "Inserisci la password del tuo account per confermare l'aggiornamento:", + "You'll need to authenticate with the server to confirm the upgrade.": "Dovrai autenticarti con il server per confermare l'aggiornamento.", + "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Aggiorna il dispositivo per consentirgli di verificare altri dispositivi, dando loro accesso ai messaggi cifrati e contrassegnandoli come fidati per gli altri utenti.", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Imposta la cifratura sul dispositivo per consentirgli di verificare altri dispositivi, dando loro accesso ai messaggi cifrati e contrassegnandoli come fidati per gli altri utenti.", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Proteggi le chiavi di cifratura con una password. Per massima sicurezza questa dovrebbe essere diversa da quella del tuo account:", + "Enter a passphrase": "Inserisci una password", + "Enter your passphrase a second time to confirm it.": "Inserisci di nuovo la tua password per confermarla.", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Questo dispositivo ora può verificare altri dispositivi, dando loro accesso ai messaggi cifrati e contrassegnandoli come fidati per gli altri utenti.", + "Verify other users in their profile.": "Verifica gli altri utenti nel loro profilo.", + "Upgrade your encryption": "Aggiorna la tua cifratura", + "Set up encryption": "Imposta la cifratura", + "Encryption upgraded": "Cifratura aggiornata", + "Encryption setup complete": "Impostazione cifratura completata" } From 098d09792215d4a1922a906e05e0ca23dc26d685 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:03:31 +0000 Subject: [PATCH 227/282] Re-enable stylelint on CI --- .buildkite/pipeline.yaml | 8 ++++++++ res/css/views/rooms/_RoomHeader.scss | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index f5f63b647a..1e08d2cd3b 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -25,6 +25,14 @@ steps: plugins: - docker#v3.0.1: image: "node:12" + - label: ":stylelint: Style Lint" + command: + - "echo '--- Install'" + - "yarn install --ignore-scripts" + - "yarn lint:style" + plugins: + - docker#v3.0.1: + image: "node:12" - label: ":jest: Tests" agents: diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 0ac2e99b97..6f0377b29c 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -24,7 +24,7 @@ limitations under the License. bottom: -1px; right: -2px; height: 10px; - width: 10px + width: 10px; } } From 5536384866eb72aa61500fcbd844b61c0b7a00d3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:21:28 +0000 Subject: [PATCH 228/282] Do less unnecessary work on CI We were checking out & installing the develop js-sdk explicitly in cases where we didn't need it at all. We were babeling the src folder many, many times over (in some cases twice in the same job) and never using the output at all. --- .buildkite/pipeline.yaml | 43 ++++++++++++++------------------------ scripts/ci/install-deps.sh | 4 ++-- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 1e08d2cd3b..39bba6637a 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -2,7 +2,8 @@ steps: - label: ":eslint: JS Lint" command: - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "./scripts/ci/install-deps.sh --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:js" plugins: - docker#v3.0.1: @@ -10,8 +11,9 @@ steps: - label: ":eslint: TS Lint" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "echo '--- Install" + - "yarn install --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:ts" plugins: - docker#v3.0.1: @@ -19,8 +21,9 @@ steps: - label: ":eslint: Types Lint" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" + - "echo '--- Install" + - "yarn install --ignore-scripts" + - "echo '+++ Lint" - "yarn lint:types" plugins: - docker#v3.0.1: @@ -41,13 +44,10 @@ steps: queue: "medium" command: - "echo '--- Install js-sdk'" - # TODO: Remove hacky chmod for BuildKite - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - - "echo '--- Installing Dependencies'" - - "./scripts/ci/install-deps.sh" - - "echo '--- Running initial build steps'" - - "yarn build" + # We don't use the babel-ed output for anything so we can --ignore-scripts + # to save transpiling the files. We run the transpile step explicitly in + # the 'build' job. + - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Running Tests'" - "yarn test" plugins: @@ -56,10 +56,8 @@ steps: - label: "🛠 Build" command: - - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" - - "echo '+++ Building Project'" - - "yarn build" + - "echo '+++ Install & Build'" + - "yarn install" plugins: - docker#v3.0.1: image: "node:12" @@ -70,14 +68,8 @@ steps: # e2e tests otherwise take +-8min queue: "xlarge" command: - # TODO: Remove hacky chmod for BuildKite - - "echo '--- Setup'" - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - "echo '--- Install js-sdk'" - - "./scripts/ci/install-deps.sh" - - "echo '--- Running initial build steps'" - - "yarn build" + - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Running Tests'" - "./scripts/ci/end-to-end-tests.sh" plugins: @@ -96,9 +88,6 @@ steps: # webpack loves to gorge itself on resources. queue: "medium" command: - # TODO: Remove hacky chmod for BuildKite - - "chmod +x ./scripts/ci/*.sh" - - "chmod +x ./scripts/*" - "echo '+++ Running Tests'" - "./scripts/ci/riot-unit-tests.sh" plugins: @@ -110,7 +99,7 @@ steps: - label: "🌐 i18n" command: - "echo '--- Fetching Dependencies'" - - "yarn install" + - "yarn install --ignore-scripts" - "echo '+++ Testing i18n output'" - "yarn diff-i18n" plugins: diff --git a/scripts/ci/install-deps.sh b/scripts/ci/install-deps.sh index a2e2e59a45..14b5fc5393 100755 --- a/scripts/ci/install-deps.sh +++ b/scripts/ci/install-deps.sh @@ -6,9 +6,9 @@ scripts/fetchdep.sh matrix-org matrix-js-sdk pushd matrix-js-sdk yarn link -yarn install +yarn install $@ yarn build popd yarn link matrix-js-sdk -yarn install +yarn install $@ From 97d55f63a362194ce5d9cc6d9bb92f78e1c63157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 15:25:27 +0100 Subject: [PATCH 229/282] DisableEventIndexDialog: Remove the incorrect class on the dialog. --- .../views/dialogs/eventindex/DisableEventIndexDialog.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index c2b7e2933e..81920eab7a 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -56,10 +56,7 @@ export default class DisableEventIndexDialog extends React.Component { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return ( - + {_t("If disabled, messages from encrypted rooms won't appear in search results.")} {this.state.disabling ? :
    } Date: Fri, 24 Jan 2020 15:26:24 +0100 Subject: [PATCH 230/282] DisableEventIndexDialog: Use a self-closing tag for the buttons. --- .../views/dialogs/eventindex/DisableEventIndexDialog.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 81920eab7a..13278217de 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -65,8 +65,7 @@ export default class DisableEventIndexDialog extends React.Component { primaryButtonClass="danger" onCancel={this.props.onFinished} disabled={this.state.disabling} - > - + /> ); } From 47999c2e468ea98ec5ea99c527b48b7ac54c3fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 15:26:54 +0100 Subject: [PATCH 231/282] EventIndexPanel: Add a separate message for the case where Seshat is missing. --- src/components/views/settings/EventIndexPanel.js | 16 ++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 2 files changed, 17 insertions(+) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 5edc25bbab..321f45699b 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -140,6 +140,22 @@ export default class EventIndexPanel extends React.Component {
    ); + } else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) { + eventIndexingSettings = ( +
    + { + _t( "Riot is missing some components required for securely " + + "caching encrypted messages locally. If you'd like to " + + "experiment with this feature, build a custom Riot Desktop " + + "with search components added.", + {}, + { + 'nativeLink': (sub) => {sub}, + }, + ) + } +
    + ); } else { eventIndexingSettings = (
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fceb299131..419aecd528 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -560,6 +560,7 @@ "Manage": "Manage", "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", "Enable": "Enable", + "Riot is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom Riot Desktop with search components added.": "Riot is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom Riot Desktop with search components added.", "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locally while running in a web browser. Use Riot Desktop for encrypted messages to appear in search results.", "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", From 57ee99d6f797115d051914d8513b262ca50441e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:30:41 +0000 Subject: [PATCH 232/282] chmod --- scripts/ci/layered-riot-web.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/ci/layered-riot-web.sh diff --git a/scripts/ci/layered-riot-web.sh b/scripts/ci/layered-riot-web.sh old mode 100644 new mode 100755 From 19c49eedec75ce21e7ecc982ec209b6b4203b2bc Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:36:12 +0000 Subject: [PATCH 233/282] reskindex for tests --- .buildkite/pipeline.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 39bba6637a..7dbb959dea 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -48,6 +48,7 @@ steps: # to save transpiling the files. We run the transpile step explicitly in # the 'build' job. - "./scripts/ci/install-deps.sh --ignore-scripts" + - "yarn run reskindex" - "echo '+++ Running Tests'" - "yarn test" plugins: From de5c47960c8a34f7412dcf0c836349348378e65a Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:51:11 +0000 Subject: [PATCH 234/282] quotes --- .buildkite/pipeline.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 7dbb959dea..698ce43c93 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -3,7 +3,7 @@ steps: command: - "echo '--- Install js-sdk'" - "./scripts/ci/install-deps.sh --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:js" plugins: - docker#v3.0.1: @@ -13,7 +13,7 @@ steps: command: - "echo '--- Install" - "yarn install --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:ts" plugins: - docker#v3.0.1: @@ -21,9 +21,9 @@ steps: - label: ":eslint: Types Lint" command: - - "echo '--- Install" + - "echo '--- Install'" - "yarn install --ignore-scripts" - - "echo '+++ Lint" + - "echo '+++ Lint'" - "yarn lint:types" plugins: - docker#v3.0.1: From b17f38856ca5f49efe593aa92755583cdc5058c9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:56:00 +0000 Subject: [PATCH 235/282] More quote failing --- .buildkite/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 698ce43c93..842ea091fb 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -11,7 +11,7 @@ steps: - label: ":eslint: TS Lint" command: - - "echo '--- Install" + - "echo '--- Install'" - "yarn install --ignore-scripts" - "echo '+++ Lint'" - "yarn lint:ts" From 26aaa58e3cc2c05ceeb239a753747cde6f720d34 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 14:58:46 +0000 Subject: [PATCH 236/282] Comments are great --- .buildkite/pipeline.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 842ea091fb..4bc69a76bd 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -1,6 +1,7 @@ steps: - label: ":eslint: JS Lint" command: + # We fetch the develop js-sdk to get our latest eslint rules - "echo '--- Install js-sdk'" - "./scripts/ci/install-deps.sh --ignore-scripts" - "echo '+++ Lint'" From 2d8477aaa69ac35c451c8a47691a0372cb635175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:13:55 +0100 Subject: [PATCH 237/282] FormattingUtils: Add a formatCountLong method. --- src/utils/FormattingUtils.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/FormattingUtils.js b/src/utils/FormattingUtils.js index 9016d62cfb..b932214530 100644 --- a/src/utils/FormattingUtils.js +++ b/src/utils/FormattingUtils.js @@ -30,6 +30,15 @@ export function formatCount(count) { return (count / 1000000000).toFixed(1) + "B"; // 10B is enough for anyone, right? :S } +/** + * Format a count showing the whole number but making it a bit more readable. + * e.g: 1000 => 1,000 + */ +export function formatCountLong(count) { + const formatter = new Intl.NumberFormat(); + return formatter.format(count) +} + /** * format a size in bytes into a human readable form * e.g: 1024 -> 1.00 KB From ddea7415c7bd954343efc98322a00326b5c1d2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:15:06 +0100 Subject: [PATCH 238/282] EventIndexPanel: Use formatCountLong to format the event and room counts. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 6 +++--- src/components/views/settings/EventIndexPanel.js | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index a8fc7dce3a..66cd89f4ab 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -20,7 +20,7 @@ import PropTypes from 'prop-types'; import { _t } from '../../../../languageHandler'; import Modal from '../../../../Modal'; -import {formatBytes, formatCount} from "../../../../utils/FormattingUtils"; +import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; @@ -125,8 +125,8 @@ export default class ManageEventIndexDialog extends React.Component { }
    {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
    - {_t("Indexed messages:")} {formatCount(this.state.eventCount)}
    - {_t("Number of rooms:")} {formatCount(this.state.roomCount)}
    + {_t("Indexed messages:")} {formatCountLong(this.state.eventCount)}
    + {_t("Number of rooms:")} {formatCountLong(this.state.roomCount)}
    {crawlerState}
    diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 321f45699b..851f86f3d4 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -21,7 +21,7 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import AccessibleButton from "../elements/AccessibleButton"; -import {formatBytes, formatCount} from "../../../utils/FormattingUtils"; +import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; export default class EventIndexPanel extends React.Component { @@ -115,7 +115,8 @@ export default class EventIndexPanel extends React.Component { {_t( "Securely cache encrypted messages locally for them " + "to appear in search results, using ") } {formatBytes(this.state.eventIndexSize, 0)} - {_t( " to store messages from ")} {formatCount(this.state.roomCount)} {_t("rooms.")} + {_t( " to store messages from ")} + {formatCountLong(this.state.roomCount)} {_t("rooms.")}
    From cd225943ea7a504d8e9ff09eefa6aa098514ee2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:22:09 +0100 Subject: [PATCH 239/282] EventIndexPanel: Shorten a overly long line. --- src/components/views/settings/EventIndexPanel.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 851f86f3d4..479a995bc8 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -142,6 +142,12 @@ export default class EventIndexPanel extends React.Component {
    ); } else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) { + const nativeLink = ( + "https://github.com/vector-im/riot-web/blob/develop/" + + "docs/native-node-modules.md#" + + "adding-seshat-for-search-in-e2e-encrypted-rooms" + ); + eventIndexingSettings = (
    { @@ -151,7 +157,7 @@ export default class EventIndexPanel extends React.Component { "with search components added.", {}, { - 'nativeLink': (sub) => {sub}, + 'nativeLink': (sub) => {sub}, }, ) } From 5d3b916a89bb0c04b38fa24a5e360c93ff178d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:46:46 +0100 Subject: [PATCH 240/282] DialogButtons: Allow setting the cancel button class with a prop. --- src/components/views/elements/DialogButtons.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js index 4e47e73052..2cb25d9c9c 100644 --- a/src/components/views/elements/DialogButtons.js +++ b/src/components/views/elements/DialogButtons.js @@ -40,6 +40,10 @@ export default createReactClass({ // should there be a cancel button? default: true hasCancel: PropTypes.bool, + // The class of the cancel button, only used if a cancel button is + // enabled + cancelButtonClass: PropTypes.node, + // onClick handler for the cancel button. onCancel: PropTypes.func, @@ -69,8 +73,10 @@ export default createReactClass({ primaryButtonClassName += " " + this.props.primaryButtonClass; } let cancelButton; + if (this.props.cancelButton || this.props.hasCancel) { - cancelButton = ; } From 3208ac60c7b6ea453448ada43e08441a371d6758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:47:29 +0100 Subject: [PATCH 241/282] ManageEventIndexDialog: Override the Disable button class to be danger. --- .../eventindex/ManageEventIndexDialog.js | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index 66cd89f4ab..5f6f4985da 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -132,18 +132,8 @@ export default class ManageEventIndexDialog extends React.Component {
    ); - const buttons = ( -
    - - {_t("Disable")} - - - {_t("Done")} - -
    - ); - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return ( {eventIndexingSettings} - {buttons} + ); } From 9f3e5ab1db15b0aa4cf6d45528e9a0f2323f85a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 16:52:26 +0100 Subject: [PATCH 242/282] ManageEventIndexDialog: Remove an unused import. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index 5f6f4985da..5c82cc0391 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -22,7 +22,6 @@ import { _t } from '../../../../languageHandler'; import Modal from '../../../../Modal'; import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils"; import EventIndexPeg from "../../../../indexing/EventIndexPeg"; -import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; /* * Allows the user to introspect the event index state and disable it. From abc2808b62d1cacb94d7a4e2403c5ee5f74faab8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 24 Jan 2020 08:57:03 -0700 Subject: [PATCH 243/282] Fix i18n post-merge --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3c758ecbfb..eb06518c7a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1475,7 +1475,6 @@ "Suggestions": "Suggestions", "Recently Direct Messaged": "Recently Direct Messaged", "Show more": "Show more", - "Direct Messages": "Direct Messages", "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.", "Go": "Go", "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", From f8e0dcf26437c03c384ace261b53f06cb5a2293d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 24 Jan 2020 09:33:55 -0700 Subject: [PATCH 244/282] Add null check for cross-signing info in verification panel --- src/components/views/right_panel/VerificationPanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index a29546c8f7..70ef8486bd 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -49,12 +49,12 @@ export default class VerificationPanel extends React.PureComponent { Verify by emoji ; - if (request.requestEvent && request.requestEvent.getId()) { + const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + if (request.requestEvent && request.requestEvent.getId() && crossSigningInfo) { const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningId()], ]; - const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); const qrCode = Date: Fri, 24 Jan 2020 09:37:12 -0700 Subject: [PATCH 245/282] Also check for our own key ID --- src/components/views/right_panel/VerificationPanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 70ef8486bd..403cff818c 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -50,10 +50,11 @@ export default class VerificationPanel extends React.PureComponent { ; const crossSigningInfo = MatrixClientPeg.get().getStoredCrossSigningForUser(request.otherUserId); + const myKeyId = MatrixClientPeg.get().getCrossSigningId(); if (request.requestEvent && request.requestEvent.getId() && crossSigningInfo) { const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [MatrixClientPeg.get().getCrossSigningId(), MatrixClientPeg.get().getCrossSigningId()], + [myKeyId, MatrixClientPeg.get().getCrossSigningId()], ]; const qrCode = Date: Fri, 24 Jan 2020 09:41:47 -0700 Subject: [PATCH 246/282] less duplication --- src/components/views/right_panel/VerificationPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 403cff818c..a75afd154a 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -54,7 +54,7 @@ export default class VerificationPanel extends React.PureComponent { if (request.requestEvent && request.requestEvent.getId() && crossSigningInfo) { const qrCodeKeys = [ [MatrixClientPeg.get().getDeviceId(), MatrixClientPeg.get().getDeviceEd25519Key()], - [myKeyId, MatrixClientPeg.get().getCrossSigningId()], + [myKeyId, myKeyId], ]; const qrCode = Date: Fri, 24 Jan 2020 17:31:00 +0000 Subject: [PATCH 247/282] Update profile avatar letter size Fixes https://github.com/vector-im/riot-web/issues/12042 --- res/css/views/right_panel/_UserInfo.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index d2d9d12c6d..ad6254f57c 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -103,7 +103,7 @@ limitations under the License. justify-content: center; // override the calculated sizes so that the letter isn't HUGE - font-size: 26px !important; + font-size: 56px !important; width: 100% !important; } From bf0e1efce8441d8b8bd06243255617da76a11ffc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 24 Jan 2020 17:53:30 +0000 Subject: [PATCH 248/282] Hide default encryption algorithm When the default encryption algorithm is used, we hide it to remove some jargon from the app. For any other algorithm, we continue showing it and add "unrecognised". Fixes https://github.com/vector-im/riot-web/issues/8829 --- src/TextForEvent.js | 17 +++++++++++++---- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 6a2744109b..cdfea45ad7 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -444,10 +444,19 @@ function textForHistoryVisibilityEvent(event) { function textForEncryptionEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); - return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', { - senderName, - algorithm: event.getContent().algorithm, - }); + if (event.getContent().algorithm === "m.megolm.v1.aes-sha2") { + return _t('%(senderName)s turned on end-to-end encryption.', { + senderName, + }); + } + return _t( + '%(senderName)s turned on end-to-end encryption ' + + '(unrecognised algorithm %(algorithm)s).', + { + senderName, + algorithm: event.getContent().algorithm, + }, + ); } // Currently will only display a change if a user's power level is changed diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3ad3d1901..79f5731aed 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -256,7 +256,8 @@ "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", - "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s turned on end-to-end encryption.", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", From 3d7137d4adeb4fa69742605e1828c53dabb5a2e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jan 2020 19:11:57 +0000 Subject: [PATCH 249/282] Setup flow for cross-signing on login / registration Still outstanding: * Keep password from login / registration * Confirmation on skip button Fixes https://github.com/vector-im/riot-web/issues/11902 --- res/css/_common.scss | 18 ++++--- res/css/views/auth/_AuthBody.scss | 9 ++-- .../_CreateSecretStorageDialog.scss | 4 ++ .../CreateSecretStorageDialog.js | 39 +++++++++++---- src/components/structures/MatrixChat.js | 35 +++++++++++--- src/components/structures/RoomView.js | 2 +- src/components/structures/auth/E2eSetup.js | 48 +++++++++++++++++++ src/components/views/auth/AuthBody.js | 4 ++ .../keybackup/RestoreKeyBackupDialog.js | 25 ++++++++++ 9 files changed, 159 insertions(+), 25 deletions(-) create mode 100644 src/components/structures/auth/E2eSetup.js diff --git a/res/css/_common.scss b/res/css/_common.scss index abc57a95ed..b92a618504 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -386,7 +386,13 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { text-align: right; } -.mx_Dialog button, .mx_Dialog input[type="submit"] { +/* XXX: Our button style are a mess: buttons that happen to appear in dialogs get special styles applied + * to them that no button anywhere else in the app gets by default. In practice, buttons in other places + * in the app look the same by being AccessibleButtons, or possibly by having explict button classes. + * We should go through and have one consistent set of styles for buttons throughout the app. + * For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons. + */ +.mx_Dialog button, .mx_Dialog input[type="submit"], .mx_Dialog_buttons button, .mx_Dialog_buttons input[type="submit"] { @mixin mx_DialogButton; margin-left: 0px; margin-right: 8px; @@ -402,27 +408,27 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { margin-right: 0px; } -.mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover { +.mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover, .mx_Dialog_buttons button:hover, .mx_Dialog_buttons input[type="submit"]:hover { @mixin mx_DialogButton_hover; } -.mx_Dialog button:focus, .mx_Dialog input[type="submit"]:focus { +.mx_Dialog button:focus, .mx_Dialog input[type="submit"]:focus, .mx_Dialog_buttons button:focus, .mx_Dialog_buttons input[type="submit"]:focus { filter: brightness($focus-brightness); } -.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary { +.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary, .mx_Dialog_buttons button.mx_Dialog_primary, .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { color: $accent-fg-color; background-color: $accent-color; min-width: 156px; } -.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger { +.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger, .mx_Dialog_buttons button.danger, .mx_Dialog_buttons input[type="submit"].danger { background-color: $warning-color; border: solid 1px $warning-color; color: $accent-fg-color; } -.mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled { +.mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled, .mx_Dialog_buttons button:disabled, .mx_Dialog_buttons input[type="submit"]:disabled { background-color: $light-fg-color; border: solid 1px $light-fg-color; opacity: 0.7; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index d342de6d75..51b9775811 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -15,13 +15,10 @@ limitations under the License. */ .mx_AuthBody { - width: 500px; background-color: $authpage-body-bg-color; border-radius: 0 4px 4px 0; padding: 25px 60px; box-sizing: border-box; - font-size: 12px; - color: $authpage-secondary-color; h2 { font-size: 24px; @@ -99,6 +96,12 @@ limitations under the License. border-radius: 4px; } +.mx_AuthBody_loginRegister { + width: 500px; + font-size: 12px; + color: $authpage-secondary-color; +} + .mx_AuthBody_editServerDetails { padding-left: 1em; font-size: 12px; diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index ed5aaa05a3..53e82670e1 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -78,6 +78,10 @@ limitations under the License. align-items: center; } +.mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton { + margin-right: 10px; +} + .mx_CreateSecretStorageDialog_recoveryKeyButtons button { flex: 1; white-space: nowrap; diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 8fd881fc32..92ede334d0 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { scorePassword } from '../../../../utils/PasswordScorer'; @@ -52,6 +53,14 @@ function selectText(target) { * Secret Storage in account data. */ export default class CreateSecretStorageDialog extends React.PureComponent { + static propTypes = { + hasCancel: PropTypes.bool, + }; + + defaultProps = { + hasCancel: true, + }; + constructor(props) { super(props); @@ -82,9 +91,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._fetchBackupInfo(); this._queryKeyUploadAuth(); + + MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange); } componentWillUnmount() { + MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange); if (this._setZxcvbnResultTimeout !== null) { clearTimeout(this._setZxcvbnResultTimeout); } @@ -92,7 +104,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { async _fetchBackupInfo() { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); + const backupSigStatus = ( + // we may not have started crypto yet, in which case we definitely don't trust the backup + MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) + ); const phase = backupInfo ? (backupSigStatus.usable ? PHASE_MIGRATE : PHASE_RESTORE_KEY_BACKUP) : @@ -127,6 +142,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } + _onKeyBackupStatusChange = () => { + this._fetchBackupInfo(); + } + _collectRecoveryKeyNode = (n) => { this._recoveryKeyNode = n; } @@ -229,7 +248,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _onRestoreKeyBackupClick = () => { const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( - 'Restore Backup', '', RestoreKeyBackupDialog, null, null, + 'Restore Backup', '', RestoreKeyBackupDialog, {showSummary: false}, null, /* priority = */ false, /* static = */ true, ); } @@ -411,6 +430,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _renderPhasePassPhrase() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let strengthMeter; let helpText; @@ -472,9 +492,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
    {_t("Advanced")} -

    +

    ; } @@ -554,6 +574,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { ); } + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return

    {_t( "Your recovery key is a safety net - you can use it to restore " + @@ -572,12 +593,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { {this._encodedRecoveryKey}

    - - +
    @@ -740,7 +761,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onFinished={this.props.onFinished} title={this._titleForPhase(this.state.phase)} headerImage={headerImage} - hasCancel={[PHASE_PASSPHRASE].includes(this.state.phase)} + hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} >
    {content} diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 0486ce764c..b4b38d7617 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -89,12 +89,15 @@ export const VIEWS = { // showing flow to trust this new device with cross-signing COMPLETE_SECURITY: 6, + // flow to setup SSSS / cross-signing on this account + E2E_SETUP: 7, + // we are logged in with an active matrix client. - LOGGED_IN: 7, + LOGGED_IN: 8, // We are logged out (invalid token) but have our local state again. The user // should log back in to rehydrate the client. - SOFT_LOGOUT: 8, + SOFT_LOGOUT: 9, }; // Actions that are redirected through the onboarding process prior to being @@ -657,7 +660,9 @@ export default createReactClass({ if ( !Lifecycle.isSoftLogout() && this.state.view !== VIEWS.LOGIN && - this.state.view !== VIEWS.COMPLETE_SECURITY + this.state.view !== VIEWS.REGISTER && + this.state.view !== VIEWS.COMPLETE_SECURITY && + this.state.view !== VIEWS.E2E_SETUP ) { this._onLoggedIn(); } @@ -1724,6 +1729,11 @@ export default createReactClass({ this.showScreen("forgot_password"); }, + onRegisterFlowComplete: function(credentials) { + this.onUserCompletedLoginFlow(); + return this.onRegistered(credentials); + }, + // returns a promise which resolves to the new MatrixClient onRegistered: function(credentials) { return Lifecycle.setLoggedIn(credentials); @@ -1847,12 +1857,18 @@ export default createReactClass({ if (masterKeyInStorage) { this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY }); + } else if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + // This will only work if the feature is set to 'enable' in the config, + // since it's too early in the lifecycle for users to have turned the + // labs flag on. + this.setStateForNewView({ view: VIEWS.E2E_SETUP }); } else { this._onLoggedIn(); } }, - onCompleteSecurityFinished() { + // complete security / e2e setup has finished + onCompleteSecurityE2eSetupFinished() { this._onLoggedIn(); }, @@ -1872,7 +1888,14 @@ export default createReactClass({ const CompleteSecurity = sdk.getComponent('structures.auth.CompleteSecurity'); view = ( + ); + } else if (this.state.view === VIEWS.E2E_SETUP) { + const E2eSetup = sdk.getComponent('structures.auth.E2eSetup'); + view = ( + ); } else if (this.state.view === VIEWS.POST_REGISTRATION) { @@ -1939,7 +1962,7 @@ export default createReactClass({ email={this.props.startingFragmentQueryParams.email} brand={this.props.config.brand} makeRegistrationUrl={this._makeRegistrationUrl} - onLoggedIn={this.onRegistered} + onLoggedIn={this.onRegisterFlowComplete} onLoginClick={this.onLoginClick} onServerConfigChange={this.onServerConfigChange} {...this.getServerProperties()} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 5c243f04bc..60fff5f1e3 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -766,7 +766,7 @@ export default createReactClass({ onUserVerificationChanged: function(userId, _trustStatus) { const room = this.state.room; - if (!room.currentState.getMember(userId)) { + if (!room || !room.currentState.getMember(userId)) { return; } this._updateE2EStatus(room); diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js new file mode 100644 index 0000000000..a5f4ff933b --- /dev/null +++ b/src/components/structures/auth/E2eSetup.js @@ -0,0 +1,48 @@ +/* +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 AsyncWrapper from '../../../AsyncWrapper'; +import * as sdk from '../../../index'; + +export default class E2eSetup extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + constructor() { + super(); + // awkwardly indented because https://github.com/eslint/eslint/issues/11310 + this._createStorageDialogPromise = + import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"); + } + + render() { + const AuthPage = sdk.getComponent("auth.AuthPage"); + const AuthBody = sdk.getComponent("auth.AuthBody"); + return ( + + + + + + ); + } +} diff --git a/src/components/views/auth/AuthBody.js b/src/components/views/auth/AuthBody.js index fe20d76afb..b74b7d866a 100644 --- a/src/components/views/auth/AuthBody.js +++ b/src/components/views/auth/AuthBody.js @@ -33,6 +33,10 @@ export default class AuthBody extends React.PureComponent { const classes = { 'mx_AuthBody': true, 'mx_AuthBody_noHeader': !this.props.header, + // XXX The login pages all use a smaller fonts size but we don't want this + // for subsequent auth screens like the e2e setup. Doing this a terrible way + // for now. + 'mx_AuthBody_loginRegister': this.props.header, }; return
    diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 77fdee5e8a..0c432ba542 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { MatrixClient } from 'matrix-js-sdk'; @@ -32,6 +33,16 @@ const RESTORE_TYPE_SECRET_STORAGE = 2; * Dialog for restoring e2e keys from a backup and the user's recovery key */ export default class RestoreKeyBackupDialog extends React.PureComponent { + static propTypes = { + // if false, will close the dialog as soon as the restore completes succesfully + // default: true + showSummary: PropTypes.bool, + }; + + defaultProps = { + showSummary: true, + }; + constructor(props) { super(props); this.state = { @@ -96,6 +107,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword( this.state.passPhrase, undefined, undefined, this.state.backupInfo, ); + if (!this.props.showSummary) { + this.props.onFinished(true); + return; + } this.setState({ loading: false, recoverInfo, @@ -119,6 +134,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( this.state.recoveryKey, undefined, undefined, this.state.backupInfo, ); + if (!this.props.showSummary) { + this.props.onFinished(true); + return; + } this.setState({ loading: false, recoverInfo, @@ -253,6 +272,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { title = _t("Error"); content = _t("No backup found!"); } else if (this.state.recoverInfo) { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); title = _t("Backup Restored"); let failedToDecrypt; if (this.state.recoverInfo.total > this.state.recoverInfo.imported) { @@ -264,6 +284,11 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { content =

    {_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}

    {failedToDecrypt} +
    ; } else if (backupHasPassphrase && !this.state.forceRecoveryKey) { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); From 9991a544aa2b413d00640bb6cb9262d831c80358 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 24 Jan 2020 22:26:28 +0000 Subject: [PATCH 250/282] Resolve default export warnings from Webpack Webpack was printing several warnings about missing default exports, particularly from components, where we auto-generate a default import via the component index. As part of this, the emoji picker recent handling (which is not a component) is moved elsewhere to avoid the generated import. --- src/components/structures/InteractiveAuth.js | 2 +- src/components/structures/TopLeftMenuButton.js | 2 +- src/components/views/auth/InteractiveAuthEntryComponents.js | 2 +- src/components/views/context_menus/TopLeftMenu.js | 2 +- src/components/views/emojipicker/EmojiPicker.js | 2 +- src/{components/views => }/emojipicker/recent.js | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename src/{components/views => }/emojipicker/recent.js (100%) diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js index 53bb990e26..3d63029b06 100644 --- a/src/components/structures/InteractiveAuth.js +++ b/src/components/structures/InteractiveAuth.js @@ -20,7 +20,7 @@ import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import {getEntryComponentForLoginType} from '../views/auth/InteractiveAuthEntryComponents'; +import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents'; import * as sdk from '../../index'; diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js index 967805d099..ebd7aaae89 100644 --- a/src/components/structures/TopLeftMenuButton.js +++ b/src/components/structures/TopLeftMenuButton.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {TopLeftMenu} from '../views/context_menus/TopLeftMenu'; +import TopLeftMenu from '../views/context_menus/TopLeftMenu'; import BaseAvatar from '../views/avatars/BaseAvatar'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import * as Avatar from '../../Avatar'; diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 869e81c1f7..801420da95 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -641,7 +641,7 @@ const AuthEntryComponents = [ TermsAuthEntry, ]; -export function getEntryComponentForLoginType(loginType) { +export default function getEntryComponentForLoginType(loginType) { for (const c of AuthEntryComponents) { if (c.LOGIN_TYPE == loginType) { return c; diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index 528e4790c2..51ec202b90 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -27,7 +27,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MenuItem} from "../../structures/ContextMenu"; import * as sdk from "../../../index"; -export class TopLeftMenu extends React.Component { +export default class TopLeftMenu extends React.Component { static propTypes = { displayName: PropTypes.string.isRequired, userId: PropTypes.string.isRequired, diff --git a/src/components/views/emojipicker/EmojiPicker.js b/src/components/views/emojipicker/EmojiPicker.js index 1f508ceedc..ca8f0c0565 100644 --- a/src/components/views/emojipicker/EmojiPicker.js +++ b/src/components/views/emojipicker/EmojiPicker.js @@ -20,7 +20,7 @@ import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -import * as recent from './recent'; +import * as recent from '../../../emojipicker/recent'; import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji"; export const CATEGORY_HEADER_HEIGHT = 22; diff --git a/src/components/views/emojipicker/recent.js b/src/emojipicker/recent.js similarity index 100% rename from src/components/views/emojipicker/recent.js rename to src/emojipicker/recent.js From 95df8b9bb7d4a2b40e65d079fb31bfca6767c57c Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 24 Jan 2020 18:34:27 +0000 Subject: [PATCH 251/282] Translated using Weblate (Hungarian) Currently translated at 99.8% (2052 of 2056 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 52 +++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index da2c015335..d2b877a8de 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -981,7 +981,7 @@ "Please contact your service administrator to get this limit increased.": "A korlát emelése érdekében kérlek vedd fel a kapcsolatot a szolgáltatás adminisztrátorával.", "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "Ez a Matrix szerver elérte a havi aktív felhasználói korlátját néhány felhasználó nem fog tudni bejelentkezni.", "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "Ez a Matrix szerver túllépte valamelyik erőforrás korlátját így néhány felhasználó nem tud majd bejelentkezni.", - "Upgrade Room Version": "Szoba verziójának frissítése", + "Upgrade Room Version": "Szoba verziójának fejlesztése", "Create a new room with the same name, description and avatar": "Készíts egy új szobát ugyanazzal a névvel, leírással és profilképpel", "Update any local room aliases to point to the new room": "Állíts át minden helyi alternatív nevet erre a szobára", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "A felhasználóknak tiltsd meg, hogy a régi szobában beszélgessenek. Küldj egy üzenetet amiben megkéred a felhasználókat, hogy menjenek át az új szobába", @@ -996,9 +996,9 @@ "The conversation continues here.": "A beszélgetés itt folytatódik.", "This room is a continuation of another conversation.": "Ebben a szobában folytatódik egy másik beszélgetés.", "Click here to see older messages.": "Ide kattintva megnézheted a régi üzeneteket.", - "Failed to upgrade room": "A szoba frissítése sikertelen", - "The room upgrade could not be completed": "A szoba frissítését nem sikerült befejezni", - "Upgrade this room to version %(version)s": "A szoba frissítése %(version)s verzióra", + "Failed to upgrade room": "A szoba fejlesztése sikertelen", + "The room upgrade could not be completed": "A szoba fejlesztését nem sikerült befejezni", + "Upgrade this room to version %(version)s": "A szoba fejlesztése %(version)s verzióra", "Forces the current outbound group session in an encrypted room to be discarded": "A jelenlegi csoport munkamenet törlését kikényszeríti a titkosított szobában", "Registration Required": "Regisztrációt igényel", "You need to register to do this. Would you like to register now?": "Hogy ezt megtedd regisztrálnod kell. Szeretnél regisztrálni?", @@ -1140,9 +1140,9 @@ "Invite anyway and never warn me again": "Mindenképpen meghív és ne figyelmeztess többet", "Invite anyway": "Mindenképpen meghív", "Whether or not you're logged in (we don't record your username)": "Se akkor ha bejelentkezel se akkor ha nem; mi nem tároljuk a felhasználói nevedet", - "Upgrades a room to a new version": "Szoba frissítése új verzióra", + "Upgrades a room to a new version": "Szoba fejlesztése új verzióra", "Sets the room name": "Szobanév beállítása", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s frissítette a szobát.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s fejlesztette a szobát.", "%(displayName)s is typing …": "%(displayName)s gépel …", "%(names)s and %(count)s others are typing …|other": "%(names)s és %(count)s másik gépelnek …", "%(names)s and %(count)s others are typing …|one": "%(names)s és még valaki gépelnek …", @@ -1460,9 +1460,9 @@ "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s visszavonta %(targetDisplayName)s a szobába való belépéséhez szükséges meghívóját.", "Enable desktop notifications for this device": "Asztali értesítések engedélyezése ehhez az eszközhöz", "Enable audible notifications for this device": "Hallható értesítések engedélyezése ehhez az eszközhöz", - "Upgrade this room to the recommended room version": "A szoba frissítése a javasolt verzióra", + "Upgrade this room to the recommended room version": "A szoba fejlesztése a javasolt verzióra", "This room is running room version , which this homeserver has marked as unstable.": "A szoba verziója: , amit a Matrix szerver instabilnak tekint.", - "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "A szoba frissítése bezárja ezt a szobát és új, frissített verzióval ugyanezen a néven létrehoz egy újat.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "A szoba fejlesztése bezárja ezt a szobát és új, frissített verzióval ugyanezen a néven létrehoz egy újat.", "Failed to revoke invite": "A meghívó visszavonása sikertelen", "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "A meghívót nem lehet visszavonni. Vagy a szervernek átmenetileg problémái vannak vagy nincs megfelelő jogosultságod a meghívó visszavonásához.", "Revoke invite": "Meghívó visszavonása", @@ -1507,7 +1507,7 @@ "Sends the given message coloured as a rainbow": "A megadott üzenetet szivárvány színben küldi el", "Sends the given emote coloured as a rainbow": "A megadott hangulatjelet szivárvány színben küldi el", "The user's homeserver does not support the version of the room.": "A felhasználó matrix szervere nem támogatja a megadott szoba verziót.", - "When rooms are upgraded": "Ha a szobák frissültek", + "When rooms are upgraded": "Ha a szobák fejlesztésre kerülnek", "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Ez az eszköz nem menti el a kulcsaidat, de létezik mentés amit visszaállíthatsz és folytathatod.", "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Csatlakozz ezzel az eszközzel a kulcs mentéshez kilépés előtt, hogy ne veszíts el kulcsot ami esetleg csak ezen az eszközön van meg.", "Connect this device to Key Backup": "Csatlakozz ezzel az eszközzel a Kulcs Mentéshez", @@ -1538,7 +1538,7 @@ "This room doesn't exist. Are you sure you're at the right place?": "Ez a szoba nem létezik. Biztos, hogy jó helyen vagy?", "Try again later, or ask a room admin to check if you have access.": "Próbálkozz később vagy kérd meg a szoba adminisztrátorát, hogy nézze meg van-e hozzáférésed.", "%(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.": "Amikor a szobát próbáltuk elérni ezt a hibaüzenetet kaptuk: %(errcode)s. Ha úgy gondolod, hogy ez egy hiba légy szívesnyiss egy hibajegyet.", - "This room has already been upgraded.": "Ez a szoba már frissült.", + "This room has already been upgraded.": "Ez a szoba már fejlesztve van.", "Rotate Left": "Balra forgat", "Rotate Right": "Jobbra forgat", "View Servers in Room": "Szerverek megjelenítése a szobában", @@ -1690,7 +1690,7 @@ "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Ha felkutatásra és, hogy más ismerősök megtalálhassanak, nem akarod használni ezt a szervert: , akkor adjál meg másik azonosítási szervert alább.", "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.": "Azonosítási szerver használata nem kötelező. Ha úgy döntesz, hogy az azonosítási szervert nem használod más felhasználók nem találnak rád és másokat sem tudsz e-mail cím vagy telefonszám alapján meghívni.", "Do not use an identity server": "Az azonosítási szerver mellőzése", - "Upgrade the room": "Szoba frissítése", + "Upgrade the room": "Szoba fejlesztése", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "E-mail cím beállítása a fiók visszaállításához. E-mail cím vagy telefonszám, hogy ismerősök megtalálhassanak.", "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "E-mail cím beállítása a fiók visszaállításához. E-mail cím, hogy ismerősök megtalálhassanak.", "Enter your custom homeserver URL What does this mean?": "Add meg a matrix szervered URL-jét Mit jelent ez?", @@ -1930,12 +1930,12 @@ "This message cannot be decrypted": "Ezt az üzenetet nem lehet visszafejteni", "Unencrypted": "Titkosítatlan", "Automatically invite users": "Felhasználók automatikus meghívása", - "Upgrade private room": "Privát szoba frissítése", - "Upgrade public room": "Nyilvános szoba frissítése", + "Upgrade private room": "Privát szoba fejlesztése", + "Upgrade public room": "Nyilvános szoba fejlesztése", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "A szoba frissítése nem egyszerű művelet, általában a szoba hibás működése, hiányzó funkció vagy biztonsági sérülékenység esetén javasolt.", "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "Ez általában a szoba szerver oldali kezelésében jelent változást. Ha a Riotban van problémád, kérlek küldj egy hibajelentést.", - "You'll upgrade this room from to .": " verzióról verzióra frissíted a szobát.", - "Upgrade": "Frissítés", + "You'll upgrade this room from to .": " verzióról verzióra fejleszted a szobát.", + "Upgrade": "Fejlesztés", "Notification settings": "Értesítések beállítása", "User Status": "Felhasználó állapota", "Reactions": "Reakciók", @@ -2086,5 +2086,25 @@ "Unrecognised command: %(commandText)s": "Ismeretlen parancs: %(commandText)s", "You can use /help to list available commands. Did you mean to send this as a message?": "Használhatod a /help-et az elérhető parancsok kilistázásához. Ezt üzenetként akartad küldeni?", "Hint: Begin your message with // to start it with a slash.": "Tipp: Ez üzenetedet kezd ezzel: //, ha perjellel szeretnéd kezdeni.", - "Send as message": "Üzenet küldése" + "Send as message": "Üzenet küldése", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s szoba címnek, %(count)s másikkal együtt, hozzáadta: %(addedAddresses)s", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s %(count)s másikkal együtt törölte a szoba címek közül: %(removedAddresses)s", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s %(countRemoved)s darabot törölt és %(countAdded)s darabot hozzáadott a szoba címekhez", + "Someone is using an unknown device": "Valaki ismeretlen eszközt használ", + "This room is end-to-end encrypted": "Ez a szoba végpontok közötti titkosítást használ", + "Everyone in this room is verified": "A szobába mindenki ellenőrizve van", + "Invite only": "Csak meghívóval", + "Send a reply…": "Válasz küldése…", + "Send a message…": "Üzenet küldése…", + "Reject & Ignore user": "Felhasználó elutasítása és figyelmen kívül hagyása", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "Ha nem találsz valakit, akkor kérdezd meg a felhasználói nevét, áruld el neki a felhasználói nevedet (%(userId)s) vagy a profil hivatkozásodat.", + "Enter your account password to confirm the upgrade:": "A fejlesztés megerősítéséhez add meg a fiók jelszavadat:", + "You'll need to authenticate with the server to confirm the upgrade.": "Azonosítanod kell magad a szerveren a fejlesztés megerősítéséhez.", + "Enter a passphrase": "Jelmondat bevitele", + "Enter your passphrase a second time to confirm it.": "Add meg a jelmondatot másodszor is a biztonság kedvéért.", + "Verify other users in their profile.": "Más felhasználók ellenőrzése a profiljukban.", + "Upgrade your encryption": "Titkosításod fejlesztése", + "Set up encryption": "Titkosítás beállítása", + "Encryption upgraded": "Titkosítás fejlesztve", + "Encryption setup complete": "Titkosítás beállítása kész" } From 662c723bf3401157e59601188d53ff008ae4d230 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 24 Jan 2020 18:48:00 +0000 Subject: [PATCH 252/282] Translated using Weblate (Turkish) Currently translated at 73.7% (1515 of 2056 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 2656b00593..db43e96cea 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1510,5 +1510,27 @@ "%(senderName)s placed a voice call.": "%(senderName)s bir çağrı yaptı.", "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s bir çağrı başlattı. (Bu tarayıcı tarafından desteklenmiyor)", "%(senderName)s placed a video call.": "%(senderName)s bir görüntülü çağrı yaptı.", - "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s bir görüntülü çağrı yaptı. (bu tarayıcı tarafından desteklenmiyor)" + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s bir görüntülü çağrı yaptı. (bu tarayıcı tarafından desteklenmiyor)", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Riot yöneticinize yapılandırmanızın hatalı ve mükerrer girdilerini kontrol etmesi için talepte bulunun.", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Kayıt olabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Parolanızı sıfırlayabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Oturum açabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.", + "a few seconds from now": "şu andan itibaren bir kaç saniye", + "about a minute from now": "şu andan itibaren yaklaşık bir dakika", + "%(num)s minutes from now": "şu andan itibaren %(num)s dakika", + "about an hour from now": "şu andan itibaren yaklaşık bir saat", + "%(num)s hours from now": "şu andan itibaren %(num)s saat", + "about a day from now": "şu andan itibaren yaklaşık bir gün", + "%(num)s days from now": "şu andan itibaren %(num)s gün", + "The user must be unbanned before they can be invited.": "Kullanıcının davet edilebilmesi için öncesinde yasağının kaldırılması gereklidir.", + "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "“abcabcabc” gibi tekrarlar “abc” yi tahmin etmekten çok az daha zor olur", + "Sequences like abc or 6543 are easy to guess": "abc veya 6543 gibi diziler tahmin için oldukça kolaydır", + "Common names and surnames are easy to guess": "Yaygın isimleri ve soyisimleri tahmin etmek oldukça kolay", + "Enable cross-signing to verify per-user instead of per-device (in development)": "Her cihaz yerine her kullanıcıyı doğrulamak için çarpraz-imzalamayı aç (geliştiriliyor)", + "Show info about bridges in room settings": "Oda ayarlarındaki köprülerin bilgilerini göster", + "Show a placeholder for removed messages": "Silinen mesajlar için bir yer tutucu göster", + "Show display name changes": "Ekran isim değişikliklerini göster", + "Enable URL previews for this room (only affects you)": "Bu oda için URL önizlemeyi aç (sadece sizi etkiler)", + "Enable URL previews by default for participants in this room": "Bu odadaki katılımcılar için URL önizlemeyi varsayılan olarak açık hale getir", + "Enable widget screenshots on supported widgets": "Desteklenen görsel bileşenlerde anlık görüntüleri aç" } From 440b3fd1fdefe69993ff1b73d1a7b0c6de7ff317 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 25 Jan 2020 11:39:29 +0000 Subject: [PATCH 253/282] Translated using Weblate (Basque) Currently translated at 100.0% (2050 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eu/ --- src/i18n/strings/eu.json | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 97cef0c70d..1ab4bd15b1 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -1617,7 +1617,7 @@ "Resend edit": "Birbidali edizioa", "Resend %(unsentCount)s reaction(s)": "Birbidali %(unsentCount)s erreakzio", "Resend removal": "Birbidali kentzeko agindua", - "Forgotten your password?": "Pasahitza ahaztu duzu?", + "Forgotten your password?": "Pasahitza ahaztuta?", "Sign in and regain access to your account.": "Hasi saioa eta berreskuratu zure kontua.", "You're signed out": "Saioa amaitu duzu", "Clear personal data": "Garbitu datu pertsonalak", @@ -2074,5 +2074,41 @@ "Done": "Egina", "Without completing security on this device, it won’t have access to encrypted messages.": "Gailu honetan segurtasuna osatu ezean, ez du zifratutako mezuetara sarbiderik izango.", "Go Back": "Joan atzera", - "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Biltegi sekretua oraingo gakoen babeskopiaren xehetasunak erabiliz ezarriko da. Zure biltegi sekretuaren pasa-esaldia eta berreskuratze gakoa zure gakoen babes-kopiarako zenerabiltzanak izango dira." + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Biltegi sekretua oraingo gakoen babeskopiaren xehetasunak erabiliz ezarriko da. Zure biltegi sekretuaren pasa-esaldia eta berreskuratze gakoa zure gakoen babes-kopiarako zenerabiltzanak izango dira.", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s erabiltzaileak %(addedAddresses)s helbideak eta beste %(count)s gehitu dizkio gela honi", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s erabiltzaileak %(removedAddresses)s helbideak eta beste %(count)s kendu dizkio gela honi", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s erabiltzaileak %(countRemoved)s helbide kendu eta %(countAdded)s gehitu dizkio gela honi", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s erabiltzaileak muturretik muturrera zifratzea aktibatu du.", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s erabiltzaileak muturretik muturrera zifratzea gaitu du (%(algorithm)s algoritmo ezezaguna).", + "Someone is using an unknown device": "Baten bat gailu ezezagun bat erabiltzen ari da", + "This room is end-to-end encrypted": "Gela hau muturretik muturrera zifratuta dago", + "Everyone in this room is verified": "Gelako guztiak egiaztatuta daude", + "Encrypted by a deleted device": "Ezabatutako gailu batek zifratua", + "Invite only": "Gonbidapenez besterik ez", + "Send a reply…": "Bidali erantzuna…", + "Send a message…": "Bidali mezua…", + "Reject & Ignore user": "Ukatu eta ezikusi erabiltzailea", + "Unknown Command": "Agindu ezezaguna", + "Unrecognised command: %(commandText)s": "Agindu ezezaguna: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "/help erabili dezakezu agindu erabilgarrien zerrenda ikusteko. Ala mezu gisa bidali nahi zenuen hau?", + "Hint: Begin your message with // to start it with a slash.": "Aholkua: Hasi zure mezua // idatziz barra batekin hasi nahi baduzu.", + "Send as message": "Bidali mezu gisa", + "Verify User": "Egiaztatu erabiltzailea", + "For extra security, verify this user by checking a one-time code on both of your devices.": "Segurtasun gehiagorako, egiaztatu erabiltzaile hau aldi-bakarrerako kode bat bi gailuetan egiaztatuz.", + "For maximum security, do this in person.": "Segurtasun gorenerako, egin hau aurrez aurre.", + "Start Verification": "Hasi egiaztaketa", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "Ez baduzu baten bat aurkitzen, eskatu bere erabiltzaile-izena, partekatu zurea (%(userId)s) edo partekatu profilaren esteka.", + "Enter your account password to confirm the upgrade:": "Sartu zure kontuaren pasa-hitza eguneraketa baieztatzeko:", + "You'll need to authenticate with the server to confirm the upgrade.": "Zerbitzariarekin autentifikatu beharko duzu eguneraketa baieztatzeko.", + "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Eguneratu gailu hau honek beste gailuak egiaztatu ahal izateko, horrela zifratutako mezuetara sarbidea emanez eta beste erabiltzaileentzat fidagarri gisa markatzeko.", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Ezarri zifratzea gailu honetan honek beste gailuak egiaztatu ahal izateko, horrela zifratutako mezuetara sarbidea emanez eta beste erabiltzaileentzat fidagarri gisa markatzeko.", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Babestu zure zifratze gakoak pasa-esaldi batekin. Segurtasun gorenerako hau eta zure kontuaren pasahitza desberdinak izan beharko lukete:", + "Enter a passphrase": "Sartu pasa-esaldia", + "Enter your passphrase a second time to confirm it.": "Sartu zure pasa-esaldia berriro hau baieztatzeko.", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Gailu honek beste gailuak egiaztatu ditzake, horrela zifratutako mezuetara sarbidea emanez eta beste erabiltzaileentzat fidagarri gisa markatuz.", + "Verify other users in their profile.": "Egiaztatu beste erabiltzaileak bere profiletan.", + "Upgrade your encryption": "Eguneratu zure zifratzea", + "Set up encryption": "Ezarri zifratzea", + "Encryption upgraded": "Zifratzea eguneratuta", + "Encryption setup complete": "Zifratzearen ezarpena egina" } From e710607a7244452aeb66f2c3204ccea62351bcd8 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 25 Jan 2020 08:22:40 +0000 Subject: [PATCH 254/282] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2050 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 03fbe6f61b..4a70441317 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2085,5 +2085,31 @@ "Unrecognised command: %(commandText)s": "無法識別的指令:%(commandText)s", "You can use /help to list available commands. Did you mean to send this as a message?": "您可以使用 /help 來列出可用的指令。您是要傳送此訊息嗎?", "Hint: Begin your message with // to start it with a slash.": "提示:以 // 開頭讓您的訊息傳送時可以用斜線開頭。", - "Send as message": "以訊息傳送" + "Send as message": "以訊息傳送", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s 向此聊天室新增了 %(addedAddresses)s 與其他 %(count)s 個地址", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s 從此聊天室移除了 %(removedAddresses)s 與其他 %(count)s 個地址", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s 對此聊天室移除了 %(countRemoved)s 個並新增了 %(countAdded)s 地址到此聊天室", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s 開啟了端到端加密。", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s 開啟了端到端加密(無法識別的演算法 %(algorithm)s)。", + "Someone is using an unknown device": "某人正在使用未知的裝置", + "This room is end-to-end encrypted": "此聊天室已端到端加密", + "Everyone in this room is verified": "此聊天室中每個人都已驗證", + "Invite only": "僅邀請", + "Send a reply…": "傳送回覆……", + "Send a message…": "傳送訊息……", + "Reject & Ignore user": "回絕並忽略使用者", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "如果您找不到某人,請詢問他們以取得他們的使用者名稱,分享您的使用者名稱 (%(userId)s) 或簡介連結。", + "Enter your account password to confirm the upgrade:": "輸入您的帳號密碼以確認升級:", + "You'll need to authenticate with the server to confirm the upgrade.": "您必須透過伺服器驗證以確認升級。", + "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "升級此裝置以允許其驗證其他裝置,並允許存取加密訊息以及將它們標記為受其他使用者信任。", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "在此裝置上設定加密以允許其驗證其他裝置,並允許存取加密訊息以及將它們標記為受其他使用者信任。", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "使用通關密語保護您的加密金鑰。為了取得最強的安全性,此通關密語應與您的帳號密碼不同:", + "Enter a passphrase": "輸入通關密語", + "Enter your passphrase a second time to confirm it.": "輸入您的通關密語兩次以確認。", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "此裝置現在可以驗證其他裝置,並允許存取加密訊息以及將它們標記為受其他使用者信任。", + "Verify other users in their profile.": "透過他們的簡介驗證其他使用者。", + "Upgrade your encryption": "升級您的加密", + "Set up encryption": "設定加密", + "Encryption upgraded": "加密已升級", + "Encryption setup complete": "加密設定完成" } From c96f82cd8b742b9f863099a2d7a6c2de7275768e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 25 Jan 2020 08:09:22 +0000 Subject: [PATCH 255/282] Translated using Weblate (French) Currently translated at 100.0% (2050 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 5d02717b0c..1ddcc55544 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2102,5 +2102,14 @@ "Upgrade your encryption": "Mettre à niveau votre chiffrement", "Set up encryption": "Configurer le chiffrement", "Encryption upgraded": "Chiffrement mis à niveau", - "Encryption setup complete": "Configuration du chiffrement terminé" + "Encryption setup complete": "Configuration du chiffrement terminé", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s a activé le chiffrement de bout en bout.", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s a activé le chiffrement de bout en bout (algorithme %(algorithm)s non reconnu).", + "Someone is using an unknown device": "Quelqu'un utilise un appareil inconnu", + "This room is end-to-end encrypted": "Ce salon est chiffré de bout en bout", + "Everyone in this room is verified": "Tout le monde dans ce salon est vérifié", + "Invite only": "Uniquement sur invitation", + "Send a reply…": "Envoyer une réponse…", + "Send a message…": "Envoyer un message…", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "Si vous n’arrivez pas à trouver quelqu’un, demandez-lui son nom d’utilisateur, partagez votre nom d’utilisateur (%(userId)s) ou votre lien de profil." } From 437b45f8a65decdf6d83f5e61e484f345505f314 Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 15:28:06 +0000 Subject: [PATCH 256/282] Remember password for e2e bootstrapping Fixes https://github.com/vector-im/riot-web/issues/12046 --- .../CreateSecretStorageDialog.js | 3 ++- src/components/structures/MatrixChat.js | 24 +++++++++++++++---- src/components/structures/auth/E2eSetup.js | 2 ++ src/components/structures/auth/Login.js | 7 +++++- .../structures/auth/Registration.js | 8 ++++++- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 92ede334d0..0867cae6f4 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -55,6 +55,7 @@ function selectText(target) { export default class CreateSecretStorageDialog extends React.PureComponent { static propTypes = { hasCancel: PropTypes.bool, + accountPassword: PropTypes.string, }; defaultProps = { @@ -82,7 +83,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // does the server offer a UI auth flow with just m.login.password // for /keys/device_signing/upload? canUploadKeysWithPasswordOnly: null, - accountPassword: '', + accountPassword: props.accountPassword, accountPasswordCorrect: null, // set if we are 'upgrading' encryption (making an SSSS store from // an existing key backup secret). diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b4b38d7617..133d74db45 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -256,6 +256,9 @@ export default createReactClass({ // logout page. Lifecycle.loadSession({}); } + + this._accountPassword = null; + this._accountPasswordTimer = null; }, componentDidMount: function() { @@ -352,6 +355,8 @@ export default createReactClass({ window.removeEventListener("focus", this.onFocus); window.removeEventListener('resize', this.handleResize); this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize); + + if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer); }, componentWillUpdate: function(props, state) { @@ -1729,9 +1734,8 @@ export default createReactClass({ this.showScreen("forgot_password"); }, - onRegisterFlowComplete: function(credentials) { - this.onUserCompletedLoginFlow(); - return this.onRegistered(credentials); + onRegisterFlowComplete: function(credentials, password) { + return this.onUserCompletedLoginFlow(credentials, password); }, // returns a promise which resolves to the new MatrixClient @@ -1822,7 +1826,14 @@ export default createReactClass({ this._loggedInView = ref; }, - async onUserCompletedLoginFlow(credentials) { + async onUserCompletedLoginFlow(credentials, password) { + this._accountPassword = password; + // self-destruct the password after 5mins + if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer); + this._accountPasswordTimer = setTimeout(() => { + this._accountPassword = null; + this._accountPasswordTimer = null; + }, 60 * 5 * 1000); // Wait for the client to be logged in (but not started) // which is enough to ask the server about account data. const loggedIn = new Promise(resolve => { @@ -1836,7 +1847,7 @@ export default createReactClass({ }); // Create and start the client in the background - Lifecycle.setLoggedIn(credentials); + const setLoggedInPromise = Lifecycle.setLoggedIn(credentials); await loggedIn; const cli = MatrixClientPeg.get(); @@ -1865,6 +1876,8 @@ export default createReactClass({ } else { this._onLoggedIn(); } + + return setLoggedInPromise; }, // complete security / e2e setup has finished @@ -1896,6 +1909,7 @@ export default createReactClass({ view = ( ); } else if (this.state.view === VIEWS.POST_REGISTRATION) { diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index a5f4ff933b..29b4345761 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -22,6 +22,7 @@ import * as sdk from '../../../index'; export default class E2eSetup extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, + accountPassword: PropTypes.string, }; constructor() { @@ -40,6 +41,7 @@ export default class E2eSetup extends React.Component { diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 7bc2dbcbae..c8b2a1ea9c 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -58,6 +58,11 @@ export default createReactClass({ displayName: 'Login', propTypes: { + // Called when the user has logged in. Params: + // - The object returned by the login API + // - The user's password, if applicable, (may be cached in memory for a + // short time so the user is not required to re-enter their password + // for operations like uploading cross-signing keys). onLoggedIn: PropTypes.func.isRequired, // If true, the component will consider itself busy. @@ -181,7 +186,7 @@ export default createReactClass({ username, phoneCountry, phoneNumber, password, ).then((data) => { this.setState({serverIsAlive: true}); // it must be, we logged in. - this.props.onLoggedIn(data); + this.props.onLoggedIn(data, password); }, (error) => { if (this._unmounted) { return; diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index fdf2f51e00..171d3ada26 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -45,7 +45,13 @@ export default createReactClass({ displayName: 'Registration', propTypes: { + // Called when the user has logged in. Params: + // - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken + // - The user's password, if available and applicable (may be cached in memory + // for a short time so the user is not required to re-enter their password + // for operations like uploading cross-signing keys). onLoggedIn: PropTypes.func.isRequired, + clientSecret: PropTypes.string, sessionId: PropTypes.string, makeRegistrationUrl: PropTypes.func.isRequired, @@ -348,7 +354,7 @@ export default createReactClass({ homeserverUrl: this.state.matrixClient.getHomeserverUrl(), identityServerUrl: this.state.matrixClient.getIdentityServerUrl(), accessToken: response.access_token, - }); + }, this.state.formVals.password); this._setupPushers(cli); // we're still busy until we get unmounted: don't show the registration form again From 8c5fd5c77e7110abba89412d41004149b6633918 Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 16:52:12 +0000 Subject: [PATCH 257/282] Verification nag toasts Implement the three differenty cases for this session, and also fix ones for other sessions which had the wrong copy. Fixes https://github.com/vector-im/riot-web/issues/11220 --- src/DeviceListener.js | 87 +++++++++++++++++-- .../views/toasts/NewSessionToast.js | 6 +- .../views/toasts/SetupEncryptionToast.js | 68 +++++++++++++++ src/i18n/strings/en_EN.json | 11 ++- 4 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 src/components/views/toasts/SetupEncryptionToast.js diff --git a/src/DeviceListener.js b/src/DeviceListener.js index a4c5785db4..dc066eb7cb 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -24,6 +24,9 @@ function toastKey(device) { return 'newsession_' + device.deviceId; } +const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; +const THIS_DEVICE_TOAST_KEY = 'setupencryption'; + export default class DeviceListener { static sharedInstance() { if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); @@ -33,42 +36,114 @@ export default class DeviceListener { constructor() { // device IDs for which the user has dismissed the verify toast ('Later') this._dismissed = new Set(); + // has the user dismissed any of the various nag toasts to setup encryption on this device? + this._dismissedThisDeviceToast = false; + + // cache of the key backup info + this._keyBackupInfo = null; + this._keyBackupFetchedAt = null; } start() { MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); - this.recheck(); + MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + this._recheck(); } stop() { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); + MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); } this._dismissed.clear(); } dismissVerification(deviceId) { this._dismissed.add(deviceId); - this.recheck(); + this._recheck(); + } + + dismissEncryptionSetup() { + this._dismissedThisDeviceToast = true; + this._recheck(); } _onDevicesUpdated = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - this.recheck(); + this._recheck(); } _onDeviceVerificationChanged = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - this.recheck(); + this._recheck(); } - async recheck() { + _onUserTrustStatusChanged = (userId, trustLevel) => { + if (userId !== MatrixClientPeg.get().getUserId()) return; + this._recheck(); + } + + // The server doesn't tell us when key backup is set up, so we poll + // & cache the result + async _getKeyBackupInfo() { + const now = (new Date()).getTime(); + if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { + this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + this._keyBackupFetchedAt = now; + } + return this._keyBackupInfo; + } + + async _recheck() { if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; const cli = MatrixClientPeg.get(); - if (!cli.isCryptoEnabled()) return false; + if (!cli.isCryptoEnabled()) return; + if (!cli.getCrossSigningId()) { + if (this._dismissedThisDeviceToast) { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + return; + } + + // cross signing isn't enabled - nag to enable it + // There 3 different toasts for: + if (cli.getStoredCrossSigningForUser(cli.getUserId())) { + // Cross-signing on account but this device doesn't trust the master key (verify this session) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Verify this Session"), + icon: "verification_warning", + props: {kind: 'verify_this_session'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } else { + const backupInfo = await this._getKeyBackupInfo(); + if (backupInfo) { + // No cross-signing on account but key backup available (upgrade encryption) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Encryption upgrade available"), + icon: "verification_warning", + props: {kind: 'upgrade_encryption'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } else { + // No cross-signing or key backup on account (set up encryption) + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Set up encryption"), + icon: "verification_warning", + props: {kind: 'set_up_encryption'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); + } + } + return; + } else { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + } const devices = await cli.getStoredDevicesForUser(cli.getUserId()); for (const device of devices) { diff --git a/src/components/views/toasts/NewSessionToast.js b/src/components/views/toasts/NewSessionToast.js index f83326121b..3b60f59131 100644 --- a/src/components/views/toasts/NewSessionToast.js +++ b/src/components/views/toasts/NewSessionToast.js @@ -32,7 +32,7 @@ export default class VerifySessionToast extends React.PureComponent { DeviceListener.sharedInstance().dismissVerification(this.props.deviceId); }; - _onVerifyClick = async () => { + _onReviewClick = async () => { const cli = MatrixClientPeg.get(); const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); @@ -47,10 +47,10 @@ export default class VerifySessionToast extends React.PureComponent { render() { const FormButton = sdk.getComponent("elements.FormButton"); return (
    -
    {_t("Other users may not trust it")}
    +
    {_t("Review & verify your new session")}
    - +
    ); } diff --git a/src/components/views/toasts/SetupEncryptionToast.js b/src/components/views/toasts/SetupEncryptionToast.js new file mode 100644 index 0000000000..841ee66ac7 --- /dev/null +++ b/src/components/views/toasts/SetupEncryptionToast.js @@ -0,0 +1,68 @@ +/* +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 DeviceListener from '../../../DeviceListener'; +import { accessSecretStorage } from '../../../CrossSigningManager'; + +export default class SetupEncryptionToast extends React.PureComponent { + static propTypes = { + toastKey: PropTypes.string.isRequired, + kind: PropTypes.oneOf(['set_up_encryption', 'verify_this_session', 'upgrade_encryption']).isRequired, + }; + + _onLaterClick = () => { + DeviceListener.sharedInstance().dismissEncryptionSetup(); + }; + + _onSetupClick = async () => { + accessSecretStorage(); + }; + + getDescription() { + switch (this.props.kind) { + case 'set_up_encryption': + case 'upgrade_encryption': + return _t('Verify your other devices easier'); + case 'verify_this_session': + return _t('Other users may not trust it'); + } + } + + getSetupCaption() { + switch (this.props.kind) { + case 'set_up_encryption': + case 'upgrade_encryption': + return _t('Upgrade'); + case 'verify_this_session': + return _t('Verify'); + } + } + + render() { + const FormButton = sdk.getComponent("elements.FormButton"); + return (
    +
    {this.getDescription()}
    +
    + + +
    +
    ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 79f5731aed..0d033dff4d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -88,6 +88,9 @@ "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "Verify this Session": "Verify this Session", + "Encryption upgrade available": "Encryption upgrade available", + "Set up encryption": "Set up encryption", "New Session": "New Session", "Who would you like to add to this community?": "Who would you like to add to this community?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", @@ -509,8 +512,12 @@ "Headphones": "Headphones", "Folder": "Folder", "Pin": "Pin", - "Other users may not trust it": "Other users may not trust it", + "Review & verify your new session": "Review & verify your new session", "Later": "Later", + "Review": "Review", + "Verify your other devices easier": "Verify your other devices easier", + "Other users may not trust it": "Other users may not trust it", + "Upgrade": "Upgrade", "Verify": "Verify", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", @@ -1514,7 +1521,6 @@ "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.", "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.", "You'll upgrade this room from to .": "You'll upgrade this room from to .", - "Upgrade": "Upgrade", "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", "Clear Storage and Sign Out": "Clear Storage and Sign Out", "Send Logs": "Send Logs", @@ -2008,7 +2014,6 @@ "Set up secret storage": "Set up secret storage", "Restore your Key Backup": "Restore your Key Backup", "Upgrade your encryption": "Upgrade your encryption", - "Set up encryption": "Set up encryption", "Recovery key": "Recovery key", "Keep it safe": "Keep it safe", "Storing secrets...": "Storing secrets...", From 7169a8444959fe384504e99c5a0736408e9120ef Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 17:08:31 +0000 Subject: [PATCH 258/282] Dismiss toasts for logged out devices --- src/DeviceListener.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index dc066eb7cb..3135e3d9a5 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -20,8 +20,8 @@ import * as sdk from './index'; import { _t } from './languageHandler'; import ToastStore from './stores/ToastStore'; -function toastKey(device) { - return 'newsession_' + device.deviceId; +function toastKey(deviceId) { + return 'newsession_' + deviceId; } const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -34,6 +34,8 @@ export default class DeviceListener { } constructor() { + // set of device ID's we're currently showing toasts for + this._activeNagToasts = new Set(); // device IDs for which the user has dismissed the verify toast ('Later') this._dismissed = new Set(); // has the user dismissed any of the various nag toasts to setup encryption on this device? @@ -145,22 +147,32 @@ export default class DeviceListener { ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); } + const newActiveToasts = new Set(); + const devices = await cli.getStoredDevicesForUser(cli.getUserId()); for (const device of devices) { if (device.deviceId == cli.deviceId) continue; const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId); if (deviceTrust.isCrossSigningVerified() || this._dismissed.has(device.deviceId)) { - ToastStore.sharedInstance().dismissToast(toastKey(device)); + ToastStore.sharedInstance().dismissToast(toastKey(device.deviceId)); } else { + this._activeNagToasts.add(device.deviceId); ToastStore.sharedInstance().addOrReplaceToast({ - key: toastKey(device), + key: toastKey(device.deviceId), title: _t("New Session"), icon: "verification_warning", props: {deviceId: device.deviceId}, component: sdk.getComponent("toasts.NewSessionToast"), }); + newActiveToasts.add(device.deviceId); } } + + // clear any other outstanding toasts (eg. logged out devices) + for (const deviceId of this._activeNagToasts) { + if (!newActiveToasts.has(deviceId)) ToastStore.sharedInstance().dismissToast(toastKey(deviceId)); + } + this._activeNagToasts = newActiveToasts; } } From 9e38c627913444f358939533c43fce2dc7754ebf Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 20:42:45 +0000 Subject: [PATCH 259/282] Show incoming verification requests in the 'complete security' phase If you click to verify your new sign in on another device, actually show the verification request on the 'complete security' screen. --- .../structures/auth/CompleteSecurity.js | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 89711fcb1d..ce938bd2f8 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -35,7 +35,18 @@ export default class CompleteSecurity extends React.Component { this.state = { phase: PHASE_INTRO, + verificationRequest: null, }; + MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); + } + + componentWillUnmount() { + if (this.state.verificationRequest) { + this.state.verificationRequest.off("change", this.onVerificationRequestChange); + } + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest); + } } onStartClick = async () => { @@ -55,6 +66,27 @@ export default class CompleteSecurity extends React.Component { } } + onVerificationRequest = (request) => { + if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return; + + if (this.state.verificationRequest) { + this.state.verificationRequest.off("change", this.onVerificationRequestChange); + } + request.on("change", this.onVerificationRequestChange); + this.setState({ + verificationRequest: request, + }); + } + + onVerificationRequestChange = () => { + if (this.state.verificationRequest.cancelled) { + this.state.verificationRequest.off("change", this.onVerificationRequestChange); + this.setState({ + verificationRequest: null, + }); + } + } + onSkipClick = () => { this.setState({ phase: PHASE_CONFIRM_SKIP, @@ -87,7 +119,13 @@ export default class CompleteSecurity extends React.Component { let icon; let title; let body; - if (phase === PHASE_INTRO) { + + if (this.state.verificationRequest) { + const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); + body = ; + } else if (phase === PHASE_INTRO) { icon = ; title = _t("Complete security"); body = ( From f851f976e7e82017cc02955c3adf758664d85ccc Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 21:26:43 +0000 Subject: [PATCH 260/282] Stop rogue verification toast if you verify during login Fixes https://github.com/vector-im/riot-web/issues/12057 --- src/components/structures/ToastContainer.js | 2 -- src/components/views/toasts/VerificationRequestToast.js | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index 8a05f62e61..298c40e825 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -23,9 +23,7 @@ export default class ToastContainer extends React.Component { constructor() { super(); this.state = {toasts: ToastStore.sharedInstance().getToasts()}; - } - componentDidMount() { ToastStore.sharedInstance().on('update', this._onToastStoreUpdate); } diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index f912984486..b2d1aaf59d 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -39,6 +39,13 @@ export default class VerificationRequestToast extends React.PureComponent { this.setState({counter}); }, 1000); request.on("change", this._checkRequestIsPending); + // We should probably have a separate class managing the active verification toasts, + // rather than monitoring this in the toast component itself, since we'll get problems + // like the toasdt not going away when the verification is cancelled unless it's the + // one on the top (ie. the one that's mounted). + // As a quick & dirty fix, check the toast is still relevant when it mounts (this prevents + // a toast hanging around after logging in if you did a verification as part of login). + this._checkRequestIsPending(); } componentWillUnmount() { From 086e43e58469e7dba0fb2443b241ca1f68f2fe17 Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 25 Jan 2020 21:30:42 +0000 Subject: [PATCH 261/282] comments ftw --- src/components/structures/ToastContainer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index 298c40e825..283fbdd96a 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -24,6 +24,10 @@ export default class ToastContainer extends React.Component { super(); this.state = {toasts: ToastStore.sharedInstance().getToasts()}; + // Start listening here rather than in componentDidMount because + // toasts may dismiss themselves in their didMount if they find + // they're already irrelevant by the time they're mounted, and + // our own componentDidMount is too late. ToastStore.sharedInstance().on('update', this._onToastStoreUpdate); } From 49cdbc9ebe68da74e0099c4e598d851800ba7852 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 25 Jan 2020 16:25:51 +0000 Subject: [PATCH 262/282] Translated using Weblate (Albanian) Currently translated at 99.7% (2043 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 91 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index e833009d59..3c04ff85c0 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -1917,11 +1917,11 @@ "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s bëri një thirrje zanore. (e pambuluar nga ky shfletues)", "%(senderName)s placed a video call.": "%(senderName)s bëri një thirrje video.", "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s bëri një thirrje video. (e pambuluar nga ky shfletues)", - "Enable cross-signing to verify per-user instead of per-device (in development)": "", + "Enable cross-signing to verify per-user instead of per-device (in development)": "Aktivizoni cross-signing për të verifikuar me bazë përdorues në vend se me bazë pajisje (në zhvillim)", "Enable local event indexing and E2EE search (requires restart)": "Aktivizoni indeksim aktesh vendore dhe kërkim E2EE (lyp rinisje)", "Match system theme": "Përputhe me temën e sistemit", - "Send cross-signing keys to homeserver": "", - "Cross-signing public keys:": "", + "Send cross-signing keys to homeserver": "Dërgo te shërbyesi Home kyçe cross-signing", + "Cross-signing public keys:": "Kyçe publikë për cross-signing:", "on device": "në pajisje", "not found": "s’u gjet", "in secret storage": "në depozitë të fshehtë", @@ -2016,5 +2016,88 @@ "Connected to on ": "Lidhur me ", "Connected via %(protocolName)s": "Lidhur përmes %(protocolName)s", "Bridge Info": "Të dhëna Ure", - "Below is a list of bridges connected to this room.": "Më poshtë keni një listë urash të lidhura në këtë dhomë." + "Below is a list of bridges connected to this room.": "Më poshtë keni një listë urash të lidhura në këtë dhomë.", + "New Session": "Sesion i Ri", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s shtoi %(addedAddresses)s dhe dhe %(count)s adresa të tjera te kjo dhomë", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s hoqi %(removedAddresses)s dhe %(count)s adresa të tjera nga kjo dhomë", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s hoqi %(countRemoved)s dhe shtoi %(countAdded)s adresa te kjo dhomë", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s aktivizoi fshehtëzimin skaj-më-skaj.", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s aktivizoi fshehtëzimin skaj-më-skaj (algoritëm jo i pranuar %(algorithm)s).", + "a few seconds ago": "pak sekonda më parë", + "about a minute ago": "rreth një minutë më parë", + "%(num)s minutes ago": "%(num)s minuta më parë", + "about an hour ago": "rreth një orë më parë", + "%(num)s hours ago": "%(num)s orë më parë", + "about a day ago": "rreth një ditë më parë", + "%(num)s days ago": "%(num)s ditë më parë", + "a few seconds from now": "pak sekonda nga tani", + "about a minute from now": "rreth një minutë nga tani", + "%(num)s minutes from now": "%(num)s minuta nga tani", + "about an hour from now": "rreth një orë nga tani", + "%(num)s hours from now": "%(num)s orë nga tani", + "about a day from now": "rreth një ditë nga tani", + "%(num)s days from now": "%(num)s ditë nga tani", + "Show a presence dot next to DMs in the room list": "Shfaqni një pikë pranie në krah DM-sh te lista e dhomave", + "Other users may not trust it": "Përdorues të tjerë mund të mos e besojnë", + "Later": "Më vonë", + "Cross-signing and secret storage are enabled.": "Cross-signing dhe depozitimi i fshehtë janë aktivizuar.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this device.": "Llogaria juaj ka një identitet cross-signing në depozitë të fshehtë, por s’është ende i besuar në këtë pajisje.", + "Cross-signing and secret storage are not yet set up.": "Cross-signing dhe depozitimi i fshehtë s’janë ujdisur ende.", + "Cross-signing private keys:": "Kyçe privatë për cross-signing:", + "Backup key stored in secret storage, but this feature is not enabled on this device. Please enable cross-signing in Labs to modify key backup state.": "Kyçi i kopjeruajtjeve u depozitua në depozitë të fshehtë, po kjo veçori s’është e aktivizuar në këtë pajisje. Ju lutemi, aktivizoni në Labs cross-signing që të modifikoni gjendje kopjeruatjeje kyçesh.", + "Labs": "Labs", + "Complete": "E plotë", + "Someone is using an unknown device": "Dikush po përdor një pajisje të panjohur", + "This room is end-to-end encrypted": "Kjo dhomë është e fshehtëzuar skaj-më-skaj", + "Everyone in this room is verified": "Gjithkush në këtë dhomë është verifikuar", + "Encrypted by a deleted device": "Fshehtëzuar nga një pajisje e fshirë", + "Invite only": "Vetëm me ftesa", + "Send a reply…": "Dërgoni një përgjigje…", + "Send a message…": "Dërgoni një mesazh…", + "Reject & Ignore user": "Hidhe poshtë & Shpërfille përdoruesin", + "Unknown Command": "Urdhër i Panjohur", + "Unrecognised command: %(commandText)s": "Urdhër Jo i Pranuar: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Mund të përdorni /help që të shfaqen urdhrat e gatshëm. Donit vërtet ta dërgoni këtë si një mesazh?", + "Hint: Begin your message with // to start it with a slash.": "Ndihmëz: Fillojeni mesazhin tuaj me // që të nisë me një pjerrake.", + "Send as message": "Dërgoni një mesazh", + "Verify User": "Verifikoni Përdoruesin", + "For extra security, verify this user by checking a one-time code on both of your devices.": "Për siguri ekstra, verifikojeni këtë përdorues duke kontrolluar në të dyja pajisjet tuaja një kod njëpërdorimsh.", + "For maximum security, do this in person.": "Për siguri maksimum, bëjeni këtë ju vetë.", + "Start Verification": "Fillo Verifikimin", + "Failed to invite the following users to chat: %(csvUsers)s": "S’u arrit të ftoheshin për bisedë përdoruesit vijues: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "S’e krijuam dot DM-në tuaj. Ju lutemi, kontrolloni përdoruesit që doni të ftoni dhe riprovoni.", + "Something went wrong trying to invite the users.": "Diç shkoi ters teksa provohej të ftoheshin përdoruesit.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "S’i ftuam dot këta përdorues. Ju lutemi, kontrolloni përdoruesit që doni të ftoni dhe riprovoni.", + "Failed to find the following users": "S’u arrit të gjendeshin përdoruesit vijues", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Përdoruesit vijues mund të mos ekzistojnë ose janë të pavlefshëm, dhe s’mund të ftohen: %(csvNames)s", + "Suggestions": "Sugjerime", + "If you can't find someone, ask them for their username, share your username (%(userId)s) or profile link.": "Nëse s’gjeni dot dikë, kërkojini emrin e tij të përdoruesit, tregojuni emrin tuaj të përdoruesit (%(userId)s) ose lidhjen e profilit.", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "Nëse s’gjeni dot dikë, kërkojini emrin e tij të përdoruesit (p.sh., @përdorues:shërbyes.com) ose tregojuni këtë dhomë.", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your passphrase.": "Për verifikim pajisjesh të tjera përmes dhënies së frazëkalimit tuaj, hyni te historiku i mesazheve tuaj të sigurt dhe identiteti juaj për cross-signing.", + "Access your secure message history and your cross-signing identity for verifying other devices by entering your recovery key.": "Për verifikim pajisjesh të tjera përmes dhënies së kyçit tuaj të rimarrjes, hyni te historiku i mesazheve tuaj të sigurt dhe identiteti juaj për cross-signing.", + "Complete security": "Siguri të plotë", + "Verify this session to grant it access to encrypted messages.": "Verifikojeni këtë sesion që t’i akordohet hyrje te mesazhe të fshehtëzuar.", + "Start": "Nise", + "Session verified": "Sesion i verifikuar", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Sesioni juaj i ri tani është i verifikuar. Ka hyrje te mesazhet tuaj të fshehtëzuar dhe përdoruesit e tjerë do ta shohin si të besuar.", + "Done": "U bë", + "Without completing security on this device, it won’t have access to encrypted messages.": "Pa plotësuar sigurinë në këtë pajisje, s’do të ketë hyrje te mesazhe të fshehtëzuar.", + "Go Back": "Shko Mbrapsht", + "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Kopjeruajtja e Kyçeve është e aktivizuar në llogarinë tuaj, por nuk është ujdisur që nga ky sesion. Që të ujdisni depozitim të fshehtë, riktheni kopjeruajtjen tuaj të kyçeve.", + "Restore": "Riktheje", + "Enter your account password to confirm the upgrade:": "Që të ripohohet përmirësimi, jepni fjalëkalimin e llogarisë tuaj:", + "You'll need to authenticate with the server to confirm the upgrade.": "Do t’ju duhet të bëni mirëfilltësimin me shërbyesin që të ripohohet përmirësimi.", + "Upgrade this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Përmirësojeni këtë pajisje për ta lejuar të verifikojë pajisje të tjera, duke u akorduar hyrje te mesazhe të fshehtëzuar dhe duke u vënë shenjë si të besuara për përdorues të tjerë.", + "Set up encryption on this device to allow it to verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Ujdisni fshehtëzim në këtë pajisje që ta lejoni të verifikojë pajisje të tjera, duke u akorduar atyre hyrje te mesazhe të fshehtëzuar dhe duke u vënë shenjë atyre si të besuara për përdorues të tjerë.", + "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Sigurojini kyçet tuaj të fshehtëzimit me një frazëkalim. Për siguri maksimale, ky do të duhej të ishte i ndryshëm nga fjalëkalimi për llogarinë tuaj:", + "Enter a passphrase": "Jepni një frazëkalim", + "Enter your passphrase a second time to confirm it.": "Që të ripohohet, jepeni edhe një herë frazëkalimin tuaj.", + "This device can now verify other devices, granting them access to encrypted messages and marking them as trusted for other users.": "Kjo pajisje mund të verifikojë pajisje të tjera, duke u akorduar hyrje te mesazhe të fshehtëzuar dhe duke u vënë shenjë si të besuara për përdorues të tjerë.", + "Verify other users in their profile.": "Verifikoni përdorues të tjerë në profilin e tyre.", + "Without setting up secret storage, you won't be able to restore your access to encrypted messages or your cross-signing identity for verifying other devices if you log out or use another device.": "Pa ujdisur depozitim të fshehtë, s’do të jeni në gjendje të rimerrni hyrje te mesazhe të fshehtëzuar apo te identiteti juaj cross-signing për verifikim pajisjesh të tjera, nëse dilni nga llogaria juaj ose përdorni një pajisje tjetër.", + "Restore your Key Backup": "Riktheni Kopjeruajtjen tuaj të Kyçeve", + "Upgrade your encryption": "Përmirësoni fshehtëzimin tuaj", + "Set up encryption": "Ujdisni fshehtëzim", + "Encryption upgraded": "U përmirësua fshehtëzimi", + "Encryption setup complete": "Ujdisje fshehtëzimit e plotësuar" } From a4bbe2ba645d6f411313b4b87b38a6783feec4c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 26 Jan 2020 22:15:44 +0000 Subject: [PATCH 263/282] fix compound emoji --- src/emoji.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emoji.js b/src/emoji.js index 125864e381..20b05531ca 100644 --- a/src/emoji.js +++ b/src/emoji.js @@ -79,13 +79,13 @@ EMOJIBASE.forEach(emoji => { }); /** - * Strips variation selectors from a string - * NB. Skin tone modifers are not variation selectors: + * Strips variation selectors from the end of given string + * NB. Skin tone modifiers are not variation selectors: * this function does not touch them. (Should it?) * * @param {string} str string to strip * @returns {string} stripped string */ function stripVariation(str) { - return str.replace(/[\uFE00-\uFE0F]/, ""); + return str.replace(/[\uFE00-\uFE0F]$/, ""); } From 13ba90a05469b3b6f35133d90c2233b50d63c57b Mon Sep 17 00:00:00 2001 From: Tirifto Date: Mon, 27 Jan 2020 09:33:34 +0000 Subject: [PATCH 264/282] Translated using Weblate (Esperanto) Currently translated at 90.1% (1848 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eo/ --- src/i18n/strings/eo.json | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index 4ac519d16a..243aebb52d 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -1851,7 +1851,7 @@ "Error unsubscribing from list": "Eraris malabono de la listo", "Please try again or view your console for hints.": "Bonvolu reprovi aŭ serĉi helpilojn en via konzolo.", "You have not ignored anyone.": "Vi neniun malatentis.", - "You are not subscribed to any lists": "Vi neniun liston abonis.", + "You are not subscribed to any lists": "Vi neniun liston abonis", "Unsubscribe": "Malaboni", "View rules": "Montri regulojn", "You are currently subscribed to:": "Vi nun abonas:", @@ -1867,5 +1867,28 @@ "Widget ID": "Identigilo de fenestraĵo", "Widgets do not use message encryption.": "Fenestraĵoj ne uzas ĉifradon de mesaĝoj.", "Widget added by": "Fenestraĵon aldonis", - "This widget may use cookies.": "Ĉi tiu fenestraĵo povas uzi kuketojn." + "This widget may use cookies.": "Ĉi tiu fenestraĵo povas uzi kuketojn.", + "%(senderName)s added %(addedAddresses)s and %(count)s other addresses to this room|other": "%(senderName)s aldonis %(addedAddresses)s kaj %(count)s aliajn adresojn al ĉi tiu ĉambro", + "%(senderName)s removed %(removedAddresses)s and %(count)s other addresses from this room|other": "%(senderName)s forigis %(removedAddresses)s kaj %(count)s aliajn adresojn el ĉi tiu ĉambro", + "%(senderName)s removed %(countRemoved)s and added %(countAdded)s addresses to this room": "%(senderName)s forigis %(countRemoved)s kaj aldonis %(countAdded)s adresojn al ĉi tiu ĉambro", + "%(senderName)s turned on end-to-end encryption.": "%(senderName)s ŝaltis tutvojan ĉifradon.", + "%(senderName)s turned on end-to-end encryption (unrecognised algorithm %(algorithm)s).": "%(senderName)s ŝaltis tutvojan ĉifradon (nerekonita algoritmo %(algorithm)s).", + "a few seconds ago": "antaŭ kelkaj sekundoj", + "about a minute ago": "antaŭ ĉirkaŭ minuto", + "%(num)s minutes ago": "antaŭ %(num)s minutoj", + "about an hour ago": "antaŭ ĉirkaŭ horo", + "%(num)s hours ago": "antaŭ %(num)s horoj", + "about a day ago": "antaŭ ĉirkaŭ tago", + "%(num)s days ago": "antaŭ %(num)s tagoj", + "a few seconds from now": "kelkajn sekundojn de nun", + "about a minute from now": "ĉirkaŭ minuton de nun", + "%(num)s minutes from now": "%(num)s minutojn de nun", + "about an hour from now": "ĉirkaŭ horon de nun", + "%(num)s hours from now": "%(num)s horojn de nun", + "about a day from now": "ĉirkaŭ tagon de nun", + "%(num)s days from now": "%(num)s tagojn de nun", + "Lock": "Seruro", + "Other users may not trust it": "Aliaj uzantoj eble ne kredas ĝin", + "Later": "Pli poste", + "Verify": "Kontroli" } From ca774f45fea0685b002f8bde37cae95cb7a51ece Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 26 Jan 2020 19:49:59 +0000 Subject: [PATCH 265/282] Translated using Weblate (Turkish) Currently translated at 73.8% (1513 of 2050 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/tr/ --- src/i18n/strings/tr.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index db43e96cea..257cb27b42 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1532,5 +1532,11 @@ "Show display name changes": "Ekran isim değişikliklerini göster", "Enable URL previews for this room (only affects you)": "Bu oda için URL önizlemeyi aç (sadece sizi etkiler)", "Enable URL previews by default for participants in this room": "Bu odadaki katılımcılar için URL önizlemeyi varsayılan olarak açık hale getir", - "Enable widget screenshots on supported widgets": "Desteklenen görsel bileşenlerde anlık görüntüleri aç" + "Enable widget screenshots on supported widgets": "Desteklenen görsel bileşenlerde anlık görüntüleri aç", + "Show recently visited rooms above the room list": "En son ziyaret edilen odaları oda listesinin en üstünde göster", + "Show hidden events in timeline": "Zaman çizelgesinde gizli olayları göster", + "Encrypted messages in one-to-one chats": "Birebir sohbetlerdeki şifrelenmiş mesajlar", + "Encrypted messages in group chats": "Grup sohbetlerdeki şifrelenmiş mesajlar", + "This is your list of users/servers you have blocked - don't leave the room!": "Bu sizin engellediğiniz kullanıcılar/sunucular listeniz - odadan ayrılmayın!", + "Got It": "Anlaşıldı" } From ebb70ca9fb943317bb2bcb8e214de04797a8ea51 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 27 Jan 2020 09:44:12 +0000 Subject: [PATCH 266/282] English Co-Authored-By: J. Ryan Stinnett --- src/DeviceListener.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index dc066eb7cb..f503353145 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -108,7 +108,7 @@ export default class DeviceListener { } // cross signing isn't enabled - nag to enable it - // There 3 different toasts for: + // There are 3 different toasts for: if (cli.getStoredCrossSigningForUser(cli.getUserId())) { // Cross-signing on account but this device doesn't trust the master key (verify this session) ToastStore.sharedInstance().addOrReplaceToast({ From 43173824d3bbecda233d896f43b44d4a53828862 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 27 Jan 2020 09:44:39 +0000 Subject: [PATCH 267/282] Capitalisation Co-Authored-By: J. Ryan Stinnett --- src/DeviceListener.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index f503353145..32024d1d87 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -113,7 +113,7 @@ export default class DeviceListener { // Cross-signing on account but this device doesn't trust the master key (verify this session) ToastStore.sharedInstance().addOrReplaceToast({ key: THIS_DEVICE_TOAST_KEY, - title: _t("Verify this Session"), + title: _t("Verify this session"), icon: "verification_warning", props: {kind: 'verify_this_session'}, component: sdk.getComponent("toasts.SetupEncryptionToast"), From 8c4c79adde070766a14ad49d6b736104ca9a9ea4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 27 Jan 2020 10:05:42 +0000 Subject: [PATCH 268/282] 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 0d033dff4d..5c2f427c6a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -88,7 +88,7 @@ "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", - "Verify this Session": "Verify this Session", + "Verify this session": "Verify this session", "Encryption upgrade available": "Encryption upgrade available", "Set up encryption": "Set up encryption", "New Session": "New Session", From e50ed95edff8e2d5b8bfebd1ec605b6dd196e23b Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 27 Jan 2020 10:17:16 +0000 Subject: [PATCH 269/282] English Co-Authored-By: J. Ryan Stinnett --- src/DeviceListener.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 3135e3d9a5..331b68398b 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -34,7 +34,7 @@ export default class DeviceListener { } constructor() { - // set of device ID's we're currently showing toasts for + // set of device IDs we're currently showing toasts for this._activeNagToasts = new Set(); // device IDs for which the user has dismissed the verify toast ('Later') this._dismissed = new Set(); From 11de92b9a90c6d847f6687d6c9808a24ca189fdd Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 27 Jan 2020 11:07:55 +0000 Subject: [PATCH 270/282] hopefully informative comment --- src/components/structures/auth/CompleteSecurity.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index ce938bd2f8..206cdb743e 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -35,6 +35,9 @@ export default class CompleteSecurity extends React.Component { this.state = { phase: PHASE_INTRO, + // this serves dual purpose as the object for the request logic and + // the presence of it insidicating that we're in 'verify mode'. + // Because of the latter, it lives in the state. verificationRequest: null, }; MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); From 692c993a34a5a4947b6ea25142e8533d11852c07 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Jan 2020 11:40:05 +0000 Subject: [PATCH 271/282] js-sdk v4.0.0 --- package.json | 2 +- yarn.lock | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dc619e50ef..901c0cec15 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "4.0.0-rc.1", + "matrix-js-sdk": "4.0.0", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", "prop-types": "^15.5.8", diff --git a/yarn.lock b/yarn.lock index 75c5af6a1d..232067a99f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5760,11 +5760,12 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw== -matrix-js-sdk@4.0.0-rc.1: - version "4.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-4.0.0-rc.1.tgz#160d445364aa8ff36ae4fffb0ebc944f680afa4e" - integrity sha512-1tP9fUCU5I4bXQQHuVlKHbpySh71aM6sugVC6L8ikG1ebZuKkAc2tXf1tiHizp3hZadHoTZ1PGH19ZFlKDZ8Qw== +matrix-js-sdk@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-4.0.0.tgz#c81bdc905af2ab1634527e5f542f2f15977d31cf" + integrity sha512-Xbe36xL443qtEBH4xk0k39JabolqZfloK7fwYGMb/PgWO26VOzvw94XWahnIr5w83oxBAF9nFmP+7EnPG6IHnA== dependencies: + "@babel/runtime" "^7.8.3" another-json "^0.2.0" browser-request "^0.3.3" bs58 "^4.0.1" From 976ac328e12414e4b9af631a7c6ce18770defda4 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Jan 2020 11:44:10 +0000 Subject: [PATCH 272/282] Prepare changelog for v2.0.0 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7630231615..881669a1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +Changes in [2.0.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0) (2020-01-27) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.0.0-rc.2...v2.0.0) + + * Ensure a plaintext version of the composer ends up on the clipboard + [\#3923](https://github.com/matrix-org/matrix-react-sdk/pull/3923) + * Move & upgrade babel runtime into dependencies (like it wants) + [\#3921](https://github.com/matrix-org/matrix-react-sdk/pull/3921) + * Don't list every single alias when there's many + [\#3919](https://github.com/matrix-org/matrix-react-sdk/pull/3919) + Changes in [2.0.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0-rc.2) (2020-01-20) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.0.0-rc.1...v2.0.0-rc.2) From 8c56c59922b2c0df387bd8fafb51272cb447392a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Jan 2020 11:44:10 +0000 Subject: [PATCH 273/282] v2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 901c0cec15..fa017b223d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.0.0-rc.2", + "version": "2.0.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From e224150190f2d2869951a03eebc76c2ebcc93b18 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 27 Jan 2020 12:17:12 +0000 Subject: [PATCH 274/282] detail pass on DMs --- src/components/views/rooms/RoomBreadcrumbs.js | 2 +- src/components/views/rooms/RoomHeader.js | 5 ++++- src/components/views/rooms/RoomTile.js | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 5a15a7518b..f9408d3259 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -363,7 +363,7 @@ export default class RoomBreadcrumbs extends React.Component { } let dmIndicator; - if (this._isDmRoom(r.room)) { + if (this._isDmRoom(r.room) && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { dmIndicator = : undefined; + const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId); const joinRules = this.props.room && this.props.room.currentState.getStateEvents("m.room.join_rules", ""); const joinRule = joinRules && joinRules.getContent().join_rule; let privateIcon; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + // Don't show an invite-only icon for DMs. Users know they're invite-only. + if (!dmUserId && SettingsStore.isFeatureEnabled("feature_cross_signing")) { if (joinRule == "invite") { privateIcon = ; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 9d2334de82..41975fe7b8 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -478,8 +478,9 @@ export default createReactClass({ let dmIndicator; let dmOnline; - // If we can place a shield, do that instead - if (dmUserId && !this.state.e2eStatus) { + /* Post-cross-signing we don't show DM indicators at all, instead relying on user + context to let them know when that is. */ + if (dmUserId && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { dmIndicator = Date: Mon, 27 Jan 2020 15:28:43 +0100 Subject: [PATCH 275/282] DisableEventIndexDialog: Turn the cancel button red. --- res/css/_common.scss | 5 +++++ .../views/dialogs/eventindex/DisableEventIndexDialog.js | 1 + 2 files changed, 6 insertions(+) diff --git a/res/css/_common.scss b/res/css/_common.scss index 51d985efb7..77284d0a14 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -414,6 +414,11 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { color: $accent-fg-color; } +.mx_Dialog button.warning, .mx_Dialog input[type="submit"].warning { + border: solid 1px $warning-color; + color: $warning-color; +} + .mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled { background-color: $light-fg-color; border: solid 1px $light-fg-color; diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js index 13278217de..120b086ef6 100644 --- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -63,6 +63,7 @@ export default class DisableEventIndexDialog extends React.Component { primaryButton={_t('Disable')} onPrimaryButtonClick={this._onDisable} primaryButtonClass="danger" + cancelButtonClass="warning" onCancel={this.props.onFinished} disabled={this.state.disabling} /> From e38f1191a52880ee5b80ee3c68feb02831a8bdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 27 Jan 2020 15:50:14 +0100 Subject: [PATCH 276/282] ManageEventIndex: Clarify that we're currently not downloading any messages. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index 5c82cc0391..b7ea87b1b2 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -108,7 +108,7 @@ export default class ManageEventIndexDialog extends React.Component { let crawlerState; if (this.state.currentRoom === null) { - crawlerState = _t("Not downloading messages for any room."); + crawlerState = _t("Not currently downloading messages for any room."); } else { crawlerState = ( _t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom }) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 419aecd528..e6e4ad3ed3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2040,7 +2040,7 @@ "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.": "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.", "If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.", "Disable": "Disable", - "Not downloading messages for any room.": "Not downloading messages for any room.", + "Not currently downloading messages for any room.": "Not currently downloading messages for any room.", "Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.", "Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot is securely caching encrypted messages locally for them to appear in search results:", "Space used:": "Space used:", From ab8ea5226647fcfdcaa7a352f3c711d2d5c536ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 27 Jan 2020 16:50:33 +0100 Subject: [PATCH 277/282] EventIndexPanel: Make sure links get opened in a new tab. --- src/components/views/settings/EventIndexPanel.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index 479a995bc8..68faa53e53 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -157,7 +157,8 @@ export default class EventIndexPanel extends React.Component { "with search components added.", {}, { - 'nativeLink': (sub) => {sub}, + 'nativeLink': (sub) => {sub}, }, ) } @@ -172,7 +173,8 @@ export default class EventIndexPanel extends React.Component { "for encrypted messages to appear in search results.", {}, { - 'riotLink': (sub) => {sub}, + 'riotLink': (sub) => {sub}, }, ) } From 805e9abb3981584cd941e59c5c10edc3595c38b9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 27 Jan 2020 16:00:25 +0000 Subject: [PATCH 278/282] Flip back to develop --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f0b7e04c73..78bbb5b4c6 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "4.0.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", "prop-types": "^15.5.8", diff --git a/yarn.lock b/yarn.lock index 232067a99f..b892ac44f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5760,10 +5760,9 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw== -matrix-js-sdk@4.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "4.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-4.0.0.tgz#c81bdc905af2ab1634527e5f542f2f15977d31cf" - integrity sha512-Xbe36xL443qtEBH4xk0k39JabolqZfloK7fwYGMb/PgWO26VOzvw94XWahnIr5w83oxBAF9nFmP+7EnPG6IHnA== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/21e4c597d9633aef606871cf9ffffaf039142be3" dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 1964e18315657f800257e66756f6f6e5505ba20b Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 27 Jan 2020 16:40:56 +0000 Subject: [PATCH 279/282] Fix issue where we don't notice if our own devices shouldn't be trusted --- src/components/structures/RoomView.js | 2 +- src/components/views/rooms/RoomTile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 60fff5f1e3..2d669f9243 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -811,7 +811,7 @@ export default createReactClass({ debuglog("e2e verified", verified, "unverified", unverified); /* Check all verified user devices. */ - for (const userId of verified) { + for (const userId of [...verified, cli.getUserId()]) { const devices = await cli.getStoredDevicesForUser(userId); const anyDeviceNotVerified = devices.some(({deviceId}) => { return !cli.checkDeviceTrust(userId, deviceId).isVerified(); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 41975fe7b8..41d43476ea 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -166,7 +166,7 @@ export default createReactClass({ }); /* Check all verified user devices. */ - for (const userId of verified) { + for (const userId of [...verified, cli.getUserId()]) { const devices = await cli.getStoredDevicesForUser(userId); const allDevicesVerified = devices.every(({deviceId}) => { return cli.checkDeviceTrust(userId, deviceId).isVerified(); From ff195381e9f17439fba3c7a2ff79e8587d41eaa6 Mon Sep 17 00:00:00 2001 From: stoically Date: Wed, 22 Jan 2020 16:44:47 +0100 Subject: [PATCH 280/282] Use https for recaptcha for all non-http protocols Signed-off-by: stoically --- src/components/views/auth/CaptchaForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index 2da837f029..efcc450067 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -62,7 +62,7 @@ export default createReactClass({ console.log("Loading recaptcha script..."); window.mx_on_recaptcha_loaded = () => {this._onCaptchaLoaded();}; let protocol = global.location.protocol; - if (protocol === "vector:") { + if (protocol !== "http:") { protocol = "https:"; } const scriptTag = document.createElement('script'); From 89f110f60a7756f0c27f4e248fc27011b55cd28e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 27 Jan 2020 22:27:11 +0000 Subject: [PATCH 281/282] Add separate component for post-auth security flows Instead of twisting `AuthBody`, this adds a new component for the different styling of post-auth security flows. This also makes them fixed width and adjusts padding to match designs. --- res/css/_components.scss | 3 +- .../structures/auth/_CompleteSecurity.scss | 2 +- res/css/views/auth/_AuthBody.scss | 14 ++----- res/css/views/auth/_CompleteSecurityBody.scss | 42 +++++++++++++++++++ .../structures/auth/CompleteSecurity.js | 6 +-- src/components/structures/auth/E2eSetup.js | 6 +-- src/components/views/auth/AuthBody.js | 21 +--------- .../views/auth/CompleteSecurityBody.js | 27 ++++++++++++ 8 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 res/css/views/auth/_CompleteSecurityBody.scss create mode 100644 src/components/views/auth/CompleteSecurityBody.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 07e92bdc7b..a2cfc94c79 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -36,6 +36,7 @@ @import "./views/auth/_AuthHeader.scss"; @import "./views/auth/_AuthHeaderLogo.scss"; @import "./views/auth/_AuthPage.scss"; +@import "./views/auth/_CompleteSecurityBody.scss"; @import "./views/auth/_CountryDropdown.scss"; @import "./views/auth/_InteractiveAuthEntryComponents.scss"; @import "./views/auth/_LanguageSelector.scss"; @@ -148,10 +149,10 @@ @import "./views/rooms/_AuxPanel.scss"; @import "./views/rooms/_BasicMessageComposer.scss"; @import "./views/rooms/_E2EIcon.scss"; -@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; +@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; @import "./views/rooms/_MemberDeviceInfo.scss"; diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index c258ce4ec7..2bf51d9574 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -22,7 +22,7 @@ limitations under the License. .mx_CompleteSecurity_headerIcon { width: 24px; height: 24px; - margin: 0 4px; + margin-right: 4px; position: relative; } diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 51b9775811..7c5b008535 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -1,5 +1,6 @@ /* Copyright 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. @@ -15,6 +16,9 @@ limitations under the License. */ .mx_AuthBody { + width: 500px; + font-size: 12px; + color: $authpage-secondary-color; background-color: $authpage-body-bg-color; border-radius: 0 4px 4px 0; padding: 25px 60px; @@ -92,16 +96,6 @@ limitations under the License. } } -.mx_AuthBody_noHeader { - border-radius: 4px; -} - -.mx_AuthBody_loginRegister { - width: 500px; - font-size: 12px; - color: $authpage-secondary-color; -} - .mx_AuthBody_editServerDetails { padding-left: 1em; font-size: 12px; diff --git a/res/css/views/auth/_CompleteSecurityBody.scss b/res/css/views/auth/_CompleteSecurityBody.scss new file mode 100644 index 0000000000..c7860fbe74 --- /dev/null +++ b/res/css/views/auth/_CompleteSecurityBody.scss @@ -0,0 +1,42 @@ +/* +Copyright 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. +*/ + +.mx_CompleteSecurityBody { + width: 600px; + color: $authpage-primary-color; + background-color: $authpage-body-bg-color; + border-radius: 4px; + padding: 20px; + box-sizing: border-box; + + h2 { + font-size: 24px; + font-weight: 600; + margin-top: 0; + } + + h3 { + font-size: 14px; + font-weight: 600; + } + + a:link, + a:hover, + a:visited { + @mixin mx_Dialog_link; + } +} diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 206cdb743e..29d8207d0a 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -112,7 +112,7 @@ export default class CompleteSecurity extends React.Component { render() { const AuthPage = sdk.getComponent("auth.AuthPage"); - const AuthBody = sdk.getComponent("auth.AuthBody"); + const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const { @@ -204,7 +204,7 @@ export default class CompleteSecurity extends React.Component { return ( - +

    {icon} {title} @@ -212,7 +212,7 @@ export default class CompleteSecurity extends React.Component {
    {body}
    - + ); } diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 29b4345761..9b390d24cc 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -34,16 +34,16 @@ export default class E2eSetup extends React.Component { render() { const AuthPage = sdk.getComponent("auth.AuthPage"); - const AuthBody = sdk.getComponent("auth.AuthBody"); + const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody"); return ( - + - + ); } diff --git a/src/components/views/auth/AuthBody.js b/src/components/views/auth/AuthBody.js index b74b7d866a..9a078efb52 100644 --- a/src/components/views/auth/AuthBody.js +++ b/src/components/views/auth/AuthBody.js @@ -17,29 +17,10 @@ limitations under the License. 'use strict'; import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; export default class AuthBody extends React.PureComponent { - static PropTypes = { - header: PropTypes.bool, - }; - - static defaultProps = { - header: true, - }; - render() { - const classes = { - 'mx_AuthBody': true, - 'mx_AuthBody_noHeader': !this.props.header, - // XXX The login pages all use a smaller fonts size but we don't want this - // for subsequent auth screens like the e2e setup. Doing this a terrible way - // for now. - 'mx_AuthBody_loginRegister': this.props.header, - }; - - return
    + return
    { this.props.children }
    ; } diff --git a/src/components/views/auth/CompleteSecurityBody.js b/src/components/views/auth/CompleteSecurityBody.js new file mode 100644 index 0000000000..d757de9fe0 --- /dev/null +++ b/src/components/views/auth/CompleteSecurityBody.js @@ -0,0 +1,27 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import React from 'react'; + +export default class CompleteSecurityBody extends React.PureComponent { + render() { + return
    + { this.props.children } +
    ; + } +} From 2a1407a531e74eaea8a729cbd66d8f33d9d5f661 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 27 Jan 2020 15:36:12 -0700 Subject: [PATCH 282/282] Add more logging to settings watchers To try and track leaks versus spam. Fixes https://github.com/vector-im/riot-web/issues/12094 (it's not leaking, probably) --- src/settings/SettingsStore.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index 9501bac205..b1d61197a0 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -145,7 +145,7 @@ export default class SettingsStore { callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue); }; - console.log(`Starting watcher for ${settingName}@${roomId || ''}`); + console.log(`Starting watcher for ${settingName}@${roomId || ''} as ID ${watcherId}`); SettingsStore._watchers[watcherId] = localizedCallback; defaultWatchManager.watchSetting(settingName, roomId, localizedCallback); @@ -159,8 +159,12 @@ export default class SettingsStore { * to cancel. */ static unwatchSetting(watcherReference) { - if (!SettingsStore._watchers[watcherReference]) return; + if (!SettingsStore._watchers[watcherReference]) { + console.warn(`Ending non-existent watcher ID ${watcherReference}`); + return; + } + console.log(`Ending watcher ID ${watcherReference}`); defaultWatchManager.unwatchSetting(SettingsStore._watchers[watcherReference]); delete SettingsStore._watchers[watcherReference]; }