From d8a38e6b74b185724ecbc7a13c0cb24341a46dc9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 10 Dec 2019 17:53:51 +0100 Subject: [PATCH 001/109] WIP --- src/components/structures/MatrixChat.js | 12 +-- src/components/structures/RightPanel.js | 4 + src/components/structures/ToastContainer.js | 1 + .../views/dialogs/DeviceVerifyDialog.js | 8 +- .../views/messages/MKeyVerificationRequest.js | 98 +++++++++++-------- .../views/right_panel/EncryptionPanel.js | 33 +++++++ .../views/right_panel/RoomHeaderButtons.js | 1 + src/stores/RightPanelStore.js | 6 +- src/stores/RightPanelStorePhases.js | 2 +- src/utils/KeyVerificationStateObserver.js | 1 + 10 files changed, 112 insertions(+), 54 deletions(-) create mode 100644 src/components/views/right_panel/EncryptionPanel.js diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index af3f4d2598..e093e9dff9 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1458,20 +1458,16 @@ export default createReactClass({ if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { cli.on("crypto.verification.request", request => { - let requestObserver; - if (request.event.getRoomId()) { - requestObserver = new KeyVerificationStateObserver( - request.event, MatrixClientPeg.get()); - } - - if (!requestObserver || requestObserver.pending) { + console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId()); + if (request.pending) { + console.log(`emitting toast for verification request with txnid ${request.channel.transactionId}`, request.event && request.event.getId()); dis.dispatch({ action: "show_toast", toast: { key: request.event.getId(), title: _t("Verification Request"), icon: "verification", - props: {request, requestObserver}, + props: {request}, component: sdk.getComponent("toasts.VerificationRequestToast"), }, }); diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index ff987a8f30..f5648a6d02 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -160,6 +160,7 @@ export default class RightPanel extends React.Component { groupId: payload.groupId, member: payload.member, event: payload.event, + verificationRequest: payload.verificationRequest, }); } } @@ -168,6 +169,7 @@ export default class RightPanel extends React.Component { const MemberList = sdk.getComponent('rooms.MemberList'); const MemberInfo = sdk.getComponent('rooms.MemberInfo'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); + const EncryptionPanel = sdk.getComponent('right_panel.EncryptionPanel'); const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo'); const NotificationPanel = sdk.getComponent('structures.NotificationPanel'); const FilePanel = sdk.getComponent('structures.FilePanel'); @@ -235,6 +237,8 @@ export default class RightPanel extends React.Component { panel = ; } else if (this.state.phase === RIGHT_PANEL_PHASES.FilePanel) { panel = ; + } else if (this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel) { + panel = ; } const classes = classNames("mx_RightPanel", "mx_fadable", { diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index a8dca35747..a9c8267d0d 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -26,6 +26,7 @@ export default class ToastContainer extends React.Component { } componentDidMount() { + console.log("ToastContainer mounted"); this._dispatcherRef = dis.register(this.onAction); } diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js index 6408245452..ca05d74964 100644 --- a/src/components/views/dialogs/DeviceVerifyDialog.js +++ b/src/components/views/dialogs/DeviceVerifyDialog.js @@ -100,9 +100,15 @@ export default class DeviceVerifyDialog extends React.Component { if (!verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) { const roomId = await ensureDMExistsAndOpen(this.props.userId); // throws upon cancellation before having started - this._verifier = await client.requestVerificationDM( + const request = await client.requestVerificationDM( this.props.userId, roomId, [verificationMethods.SAS], ); + await request.waitFor(r => r.ready || r.started); + if (request.ready) { + this._verifier = request.beginKeyVerification(verificationMethods.SAS); + } else { + this._verifier = request.verifier; + } } else { this._verifier = client.beginKeyVerification( verificationMethods.SAS, this.props.userId, this.props.device.deviceId, diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 4faa1b20aa..f31a823ac9 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -17,48 +17,62 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; import sdk from '../../../index'; -import Modal from "../../../Modal"; import { _t } from '../../../languageHandler'; -import KeyVerificationStateObserver, {getNameForEventRoom, userLabelForEventRoom} +import {getNameForEventRoom, userLabelForEventRoom} from '../../../utils/KeyVerificationStateObserver'; +import dis from "../../../dispatcher"; +import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; export default class MKeyVerificationRequest extends React.Component { constructor(props) { super(props); - this.keyVerificationState = new KeyVerificationStateObserver(this.props.mxEvent, MatrixClientPeg.get(), () => { - this.setState(this._copyState()); - }); - this.state = this._copyState(); - } - - _copyState() { - const {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId} = this.keyVerificationState; - return {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId}; } componentDidMount() { - this.keyVerificationState.attach(); + const request = this.props.mxEvent.verificationRequest; + if (request) { + request.on("change", this._onRequestChanged); + } } componentWillUnmount() { - this.keyVerificationState.detach(); + const request = this.props.mxEvent.verificationRequest; + if (request) { + request.off("change", this._onRequestChanged); + } } - _onAcceptClicked = () => { - const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); - // todo: validate event, for example if it has sas in the methods. - const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS); - Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { - verifier, - }, null, /* priority = */ false, /* static = */ true); + _onRequestChanged = () => { + this.forceUpdate(); }; - _onRejectClicked = () => { - // todo: validate event, for example if it has sas in the methods. - const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS); - verifier.cancel("User declined"); + _onAcceptClicked = async () => { + const request = this.props.mxEvent.verificationRequest; + if (request) { + try { + await request.accept(); + dis.dispatch({action: "show_right_panel"}); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + verificationRequest: request, + }); + } catch (err) { + console.error(err.message); + } + } + }; + + _onRejectClicked = async () => { + const request = this.props.mxEvent.verificationRequest; + if (request) { + try { + await request.cancel(); + } catch (err) { + console.error(err.message); + } + } }; _acceptedLabel(userId) { @@ -83,45 +97,43 @@ export default class MKeyVerificationRequest extends React.Component { render() { const {mxEvent} = this.props; - const fromUserId = mxEvent.getSender(); - const content = mxEvent.getContent(); - const toUserId = content.to; - const client = MatrixClientPeg.get(); - const myUserId = client.getUserId(); - const isOwn = fromUserId === myUserId; + const request = mxEvent.verificationRequest; let title; let subtitle; let stateNode; - if (this.state.accepted || this.state.cancelled) { + if (!request) { + return

This is an invalid request, ho ho ho!

; + } + + if (request.ready || request.started || request.cancelled) { let stateLabel; - if (this.state.accepted) { - stateLabel = this._acceptedLabel(toUserId); - } else if (this.state.cancelled) { - stateLabel = this._cancelledLabel(this.state.cancelPartyUserId); + if (request.ready || request.started) { + stateLabel = this._acceptedLabel(request.receivingUserId); + } else if (request.cancelled) { + stateLabel = this._cancelledLabel(request.cancellingUserId); } stateNode = (
{stateLabel}
); } - if (toUserId === myUserId) { // request sent to us + if (!request.initiatedByMe) { title = (
{ - _t("%(name)s wants to verify", {name: getNameForEventRoom(fromUserId, mxEvent)})}
); + _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}); subtitle = (
{ - userLabelForEventRoom(fromUserId, mxEvent)}
); - const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done); - if (isResolved) { + userLabelForEventRoom(request.requestingUserId, mxEvent)}); + if (request.requested) { const FormButton = sdk.getComponent("elements.FormButton"); stateNode = (
); } - } else if (isOwn) { // request sent by us + } else { // request sent by us title = (
{ _t("You sent a verification request")}
); subtitle = (
{ - userLabelForEventRoom(this.state.otherPartyUserId, mxEvent)}
); + userLabelForEventRoom(request.receivingUserId, mxEvent)}); } if (title) { diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js new file mode 100644 index 0000000000..2050ad8072 --- /dev/null +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -0,0 +1,33 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017, 2018 Vector Creations Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +export default class EncryptionPanel extends React.PureComponent { + render() { + const request = this.props.verificationRequest; + if (request) { + return

got a request, go straight to wizard

; + } else if (this.props.member) { + return

show encryption options for member {this.props.member.name}

; + } else { + return

nada

; + } + } +} diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js index f59159d1d9..2b5ea3aa27 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.js +++ b/src/components/views/right_panel/RoomHeaderButtons.js @@ -27,6 +27,7 @@ import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; const MEMBER_PHASES = [ RIGHT_PANEL_PHASES.RoomMemberList, RIGHT_PANEL_PHASES.RoomMemberInfo, + RIGHT_PANEL_PHASES.EncryptionPanel, RIGHT_PANEL_PHASES.Room3pidMemberInfo, ]; diff --git a/src/stores/RightPanelStore.js b/src/stores/RightPanelStore.js index 02775b847b..1b3cb3d64b 100644 --- a/src/stores/RightPanelStore.js +++ b/src/stores/RightPanelStore.js @@ -123,7 +123,11 @@ export default class RightPanelStore extends Store { if (payload.action === 'view_room' || payload.action === 'view_group') { // Reset to the member list if we're viewing member info - const memberInfoPhases = [RIGHT_PANEL_PHASES.RoomMemberInfo, RIGHT_PANEL_PHASES.Room3pidMemberInfo]; + const memberInfoPhases = [ + RIGHT_PANEL_PHASES.RoomMemberInfo, + RIGHT_PANEL_PHASES.Room3pidMemberInfo, + RIGHT_PANEL_PHASES.EncryptionPanel, + ]; if (memberInfoPhases.includes(this._state.lastRoomPhase)) { this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}}); } diff --git a/src/stores/RightPanelStorePhases.js b/src/stores/RightPanelStorePhases.js index 96807ebf5b..7783f960d6 100644 --- a/src/stores/RightPanelStorePhases.js +++ b/src/stores/RightPanelStorePhases.js @@ -22,7 +22,7 @@ export const RIGHT_PANEL_PHASES = Object.freeze({ NotificationPanel: 'NotificationPanel', RoomMemberInfo: 'RoomMemberInfo', Room3pidMemberInfo: 'Room3pidMemberInfo', - + EncryptionPanel: 'EncryptionPanel', // Group stuff GroupMemberList: 'GroupMemberList', GroupRoomList: 'GroupRoomList', diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 2f7c0367ad..7e33c29214 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -161,6 +161,7 @@ export default class KeyVerificationStateObserver { } this.otherPartyUserId = fromUserId === this._client.getUserId() ? toUserId : fromUserId; + console.log("KeyVerificationStateObserver update for txnId", this._requestEvent.getId(), {accepted: this.accepted, cancelled: this.cancelled, done: this.done}); } } From c02fc44d256a07c5c39802b05112194fc73ed9f0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 16 Dec 2019 18:16:22 +0100 Subject: [PATCH 002/109] WIP sas in right panel --- .../views/right_panel/EncryptionInfo.js | 30 +++++++ .../views/right_panel/EncryptionPanel.js | 26 ++++-- .../views/right_panel/VerificationPanel.js | 88 +++++++++++++++++++ 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 src/components/views/right_panel/EncryptionInfo.js create mode 100644 src/components/views/right_panel/VerificationPanel.js diff --git a/src/components/views/right_panel/EncryptionInfo.js b/src/components/views/right_panel/EncryptionInfo.js new file mode 100644 index 0000000000..88aa3f9a6c --- /dev/null +++ b/src/components/views/right_panel/EncryptionInfo.js @@ -0,0 +1,30 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from "../../.."; + +export default class EncryptionInfo extends React.PureComponent { + render() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + return (
+

End-to-end encryption is great!

+
+ Start verification +
+
); + } +} diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index 2050ad8072..c37d4f2b61 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -1,7 +1,4 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +15,27 @@ limitations under the License. */ import React from 'react'; +import EncryptionInfo from "./EncryptionInfo"; +import VerificationPanel from "./VerificationPanel"; +import MatrixClientPeg from "../../../MatrixClientPeg"; export default class EncryptionPanel extends React.PureComponent { render() { - const request = this.props.verificationRequest; + const request = this.props.verificationRequest || this.state.verificationRequest; + const {member} = this.props; if (request) { - return

got a request, go straight to wizard

; - } else if (this.props.member) { - return

show encryption options for member {this.props.member.name}

; - } else { - return

nada

; + return ; + } else if (member) { + return ; } } + + _onStartVerification = async () => { + const client = MatrixClientPeg.get(); + const {member} = this.props; + // TODO: get the room id of the DM here? + // will this panel be shown in non-DM rooms? + const verificationRequest = await client.requestVerificationDM(member.userId, member.roomId); + this.setState({verificationRequest}); + }; } diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js new file mode 100644 index 0000000000..3546e81bf3 --- /dev/null +++ b/src/components/views/right_panel/VerificationPanel.js @@ -0,0 +1,88 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from "../../.."; +import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; + +export default class VerificationPanel extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const Spinner = sdk.getComponent('elements.Spinner'); + const {request} = this.props; + + if (request.requested) { + return (

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

); + } else if (request.ready) { + return (

{request.otherUserId} is ready, start Verify by emoji

); + } else if (request.started) { + if (this.state.sasEvent) { + const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); + return (
+ +
); + } else { + return (

Setting up SAS verification...

); + } + } else if (request.done) { + return

verified {request.otherUserId}!!

; + } + } + + _startSAS = async () => { + const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); + verifier.on('show_sas', this._onVerifierShowSas); + try { + await this._verifier.verify(); + } finally { + this.setState({sasEvent: null}); + verifier.removeListener('show_sas', this._onVerifierShowSas); + } + }; + + _onSasMatchesClick = () => { + this.state.sasEvent.confirm(); + }; + + _onSasMismatchesClick = () => { + this.state.sasEvent.cancel(); + }; + + _onVerifierShowSas = (sasEvent) => { + this.setState({sasEvent}); + }; + + _onRequestChange = () => { + this.forceUpdate(); + }; + + componentDidMount() { + this.props.request.on("change", this._onRequestChange); + } + + componentWillUnmount() { + this.props.request.off("change", this._onRequestChange); + } +} From 0f415038beccd1d730ca6ecceb1275df0809fa2c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 18 Dec 2019 17:26:54 +0000 Subject: [PATCH 003/109] actually start verify process in verification panel temporary code, as ux is not complete --- .../views/right_panel/VerificationPanel.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 3546e81bf3..f73693a0ea 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -22,6 +22,7 @@ export default class VerificationPanel extends React.PureComponent { constructor(props) { super(props); this.state = {}; + this._hasVerifier = !!props.request.verifier; } render() { @@ -53,12 +54,10 @@ export default class VerificationPanel extends React.PureComponent { _startSAS = async () => { const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); - verifier.on('show_sas', this._onVerifierShowSas); try { - await this._verifier.verify(); + await verifier.verify(); } finally { this.setState({sasEvent: null}); - verifier.removeListener('show_sas', this._onVerifierShowSas); } }; @@ -75,6 +74,18 @@ export default class VerificationPanel extends React.PureComponent { }; _onRequestChange = () => { + const {request} = this.props; + if (!this._hasVerifier && !!request.verifier) { + request.verifier.on('show_sas', this._onVerifierShowSas); + try { + request.verifier.verify(); + } catch (err) { + console.error("error verify", err); + } + } else if (this._hasVerifier && !request.verifier) { + request.verifier.removeListener('show_sas', this._onVerifierShowSas); + } + this._hasVerifier = !!request.verifier; this.forceUpdate(); }; From 9e4b65de6a9f4ed0595a67e5bfa6d8166678c8a9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:08:11 +0000 Subject: [PATCH 004/109] fixup after rebase --- src/components/views/messages/MKeyVerificationRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index f31a823ac9..c851cd30b5 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -56,7 +56,7 @@ export default class MKeyVerificationRequest extends React.Component { dis.dispatch({ action: "set_right_panel_phase", phase: RIGHT_PANEL_PHASES.EncryptionPanel, - verificationRequest: request, + refireParams: {verificationRequest: request}, }); } catch (err) { console.error(err.message); From 2c28fa0568b196fd7e6147d12883a540761e3153 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:08:53 +0000 Subject: [PATCH 005/109] use verif in right panel from "verify" button --- .../views/right_panel/EncryptionInfo.js | 4 ++-- .../views/right_panel/EncryptionPanel.js | 7 +++++++ src/components/views/right_panel/UserInfo.js | 15 ++++++++++++--- .../views/right_panel/VerificationPanel.js | 4 +++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/components/views/right_panel/EncryptionInfo.js b/src/components/views/right_panel/EncryptionInfo.js index 88aa3f9a6c..f10d0b3499 100644 --- a/src/components/views/right_panel/EncryptionInfo.js +++ b/src/components/views/right_panel/EncryptionInfo.js @@ -21,9 +21,9 @@ export default class EncryptionInfo extends React.PureComponent { render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return (
-

End-to-end encryption is great!

+

End-to-end encryption is great! You should try it.

- Start verification + Start verification
); } diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index c37d4f2b61..9b32dba30e 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -20,6 +20,11 @@ import VerificationPanel from "./VerificationPanel"; import MatrixClientPeg from "../../../MatrixClientPeg"; export default class EncryptionPanel extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + render() { const request = this.props.verificationRequest || this.state.verificationRequest; const {member} = this.props; @@ -27,6 +32,8 @@ export default class EncryptionPanel extends React.PureComponent { return ; } else if (member) { return ; + } else { + return

Not a member nor request, not sure what to render

; } } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 208c5e8906..542e010edd 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -40,6 +40,7 @@ import E2EIcon from "../rooms/E2EIcon"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; import {textualPowerLevel} from '../../../Roles'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -128,6 +129,14 @@ function verifyDevice(userId, device) { }, null, /* priority = */ false, /* static = */ true); } +function verifyUser(user) { + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {member: user}, + }); +} + function DeviceItem({userId, device}) { const cli = useContext(MatrixClientContext); const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId); @@ -1296,7 +1305,7 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { const userVerified = cli.checkUserTrust(user.userId).isVerified(); let verifyButton; if (!userVerified) { - verifyButton = verifyDevice(user.userId, null)}> + verifyButton = verifyUser(user)}> {_t("Verify")} ; } @@ -1305,7 +1314,7 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {

{ _t("Security") }

{ text }

- {verifyButton} + { verifyButton } { devicesSection }
); @@ -1323,7 +1332,7 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
-
+

{ e2eIcon } { displayName } diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index f73693a0ea..e1f36cbc0f 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -33,7 +33,7 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

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

); } else if (request.ready) { - return (

{request.otherUserId} is ready, start Verify by emoji

); + return (

{request.otherUserId} is ready, start Verify by emoji

); } else if (request.started) { if (this.state.sasEvent) { const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); @@ -56,6 +56,8 @@ export default class VerificationPanel extends React.PureComponent { const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); try { await verifier.verify(); + } catch (err) { + console.error(err); } finally { this.setState({sasEvent: null}); } From 41cf18701fe45c502fcb1d30bcd98107db56f5bb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:09:08 +0000 Subject: [PATCH 006/109] remove now-unused import --- 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 e093e9dff9..88448311d2 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -62,7 +62,6 @@ import { countRoomsWithNotif } from '../../RoomNotifs'; import { ThemeWatcher } from "../../theme"; import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer } from "../../utils/promise"; -import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver'; /** constants for MatrixChat.state.view */ const VIEWS = { From b36df7330061d28c31c20ef4934e3d945a166af7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:09:19 +0000 Subject: [PATCH 007/109] fix for ref refactor pr --- src/components/structures/TimelinePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 9d929d313b..53ae240653 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1136,7 +1136,7 @@ const TimelinePanel = createReactClass({ const allowPartial = opts.allowPartial || false; const messagePanel = this._messagePanel.current; - if (messagePanel === undefined) return null; + if (!messagePanel) return null; const EventTile = sdk.getComponent('rooms.EventTile'); From db17321777ee7975cec10362cee11329482fd568 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:28:23 +0000 Subject: [PATCH 008/109] await verify so errors (like cancellation) are caught --- src/components/views/right_panel/VerificationPanel.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index e1f36cbc0f..2879916f37 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -75,12 +75,14 @@ export default class VerificationPanel extends React.PureComponent { this.setState({sasEvent}); }; - _onRequestChange = () => { + _onRequestChange = async () => { const {request} = this.props; if (!this._hasVerifier && !!request.verifier) { request.verifier.on('show_sas', this._onVerifierShowSas); try { - request.verifier.verify(); + // on the requester side, this is also awaited in _startSAS, + // but that's ok as verify should return the same promise. + await request.verifier.verify(); } catch (err) { console.error("error verify", err); } From b866c16071e81c5ed1c35e1cf5f10b80db4c2dc5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:28:44 +0000 Subject: [PATCH 009/109] show cancellation --- src/components/views/right_panel/VerificationPanel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 2879916f37..c3fce4314b 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -49,6 +49,8 @@ export default class VerificationPanel extends React.PureComponent { } } else if (request.done) { return

verified {request.otherUserId}!!

; + } else if (request.cancelled) { + return

cancelled by {request.cancellingUserId}!

; } } From b80bfd04b221a356232fd590619118db85ed1a1b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:43:10 +0000 Subject: [PATCH 010/109] make sure to have roomId of DM when starting verif. req. --- .../views/dialogs/DeviceVerifyDialog.js | 21 ++-------------- .../views/right_panel/EncryptionPanel.js | 6 ++--- src/createRoom.js | 25 ++++++++++++++++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js index ca05d74964..fcb5089785 100644 --- a/src/components/views/dialogs/DeviceVerifyDialog.js +++ b/src/components/views/dialogs/DeviceVerifyDialog.js @@ -24,8 +24,7 @@ import sdk from '../../../index'; import * as FormattingUtils from '../../../utils/FormattingUtils'; import { _t } from '../../../languageHandler'; import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import createRoom from "../../../createRoom"; +import {ensureDMExists} from "../../../createRoom"; import dis from "../../../dispatcher"; import SettingsStore from '../../../settings/SettingsStore'; @@ -322,23 +321,7 @@ export default class DeviceVerifyDialog extends React.Component { } async function ensureDMExistsAndOpen(userId) { - const client = MatrixClientPeg.get(); - const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); - const rooms = roomIds.map(id => client.getRoom(id)); - const suitableDMRooms = rooms.filter(r => { - if (r && r.getMyMembership() === "join") { - const member = r.getMember(userId); - return member && (member.membership === "invite" || member.membership === "join"); - } - return false; - }); - let roomId; - if (suitableDMRooms.length) { - const room = suitableDMRooms[0]; - roomId = room.roomId; - } else { - roomId = await createRoom({dmUserId: userId, spinner: false, andView: false}); - } + const roomId = ensureDMExists(MatrixClientPeg.get(), userId); // don't use andView and spinner in createRoom, together, they cause this dialog to close and reopen, // we causes us to loose the verifier and restart, and we end up having two verification requests dis.dispatch({ diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index 9b32dba30e..7992933864 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -18,6 +18,7 @@ import React from 'react'; import EncryptionInfo from "./EncryptionInfo"; import VerificationPanel from "./VerificationPanel"; import MatrixClientPeg from "../../../MatrixClientPeg"; +import {ensureDMExists} from "../../../createRoom"; export default class EncryptionPanel extends React.PureComponent { constructor(props) { @@ -40,9 +41,8 @@ export default class EncryptionPanel extends React.PureComponent { _onStartVerification = async () => { const client = MatrixClientPeg.get(); const {member} = this.props; - // TODO: get the room id of the DM here? - // will this panel be shown in non-DM rooms? - const verificationRequest = await client.requestVerificationDM(member.userId, member.roomId); + const roomId = await ensureDMExists(client, member.userId); + const verificationRequest = await client.requestVerificationDM(member.userId, roomId); this.setState({verificationRequest}); }; } diff --git a/src/createRoom.js b/src/createRoom.js index 0ee90beba8..766c9b0c8f 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +21,7 @@ import sdk from './index'; import { _t } from './languageHandler'; import dis from "./dispatcher"; import * as Rooms from "./Rooms"; - +import DMRoomMap from "./utils/DMRoomMap"; import {getAddressType} from "./UserAddress"; /** @@ -35,7 +36,7 @@ import {getAddressType} from "./UserAddress"; * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. */ -function createRoom(opts) { +export default function createRoom(opts) { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; @@ -140,4 +141,22 @@ function createRoom(opts) { }); } -module.exports = createRoom; +export async function ensureDMExists(client, userId) { + const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); + const rooms = roomIds.map(id => client.getRoom(id)); + const suitableDMRooms = rooms.filter(r => { + if (r && r.getMyMembership() === "join") { + const member = r.getMember(userId); + return member && (member.membership === "invite" || member.membership === "join"); + } + return false; + }); + let roomId; + if (suitableDMRooms.length) { + const room = suitableDMRooms[0]; + roomId = room.roomId; + } else { + roomId = await createRoom({dmUserId: userId, spinner: false, andView: false}); + } + return roomId; +} From 8c5f3d6d9428e6d971604cc95782b4b75988535c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:43:55 +0000 Subject: [PATCH 011/109] show devices and unverify action also in unencrypted rooms --- src/components/views/right_panel/UserInfo.js | 24 ++++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 542e010edd..0f248d65d4 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1249,15 +1249,13 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { setDevices(null); } } - if (isRoomEncrypted) { - _downloadDeviceList(); - } + _downloadDeviceList(); // Handle being unmounted return () => { cancelled = true; }; - }, [cli, user.userId, isRoomEncrypted]); + }, [cli, user.userId]); // Listen to changes useEffect(() => { @@ -1273,18 +1271,13 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { }); } }; - - if (isRoomEncrypted) { - cli.on("deviceVerificationChanged", onDeviceVerificationChanged); - } + cli.on("deviceVerificationChanged", onDeviceVerificationChanged); // Handle being unmounted return () => { cancel = true; - if (isRoomEncrypted) { - cli.removeListener("deviceVerificationChanged", onDeviceVerificationChanged); - } + cli.removeListener("deviceVerificationChanged", onDeviceVerificationChanged); }; - }, [cli, user.userId, isRoomEncrypted]); + }, [cli, user.userId]); let text; if (!isRoomEncrypted) { @@ -1299,9 +1292,6 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { text = _t("Messages in this room are end-to-end encrypted."); } - const devicesSection = isRoomEncrypted ? - () : null; - const userVerified = cli.checkUserTrust(user.userId).isVerified(); let verifyButton; if (!userVerified) { @@ -1310,6 +1300,10 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { ; } + const devicesSection = ; + const securitySection = (

{ _t("Security") }

From d57e76f3e271601a0b7c6367377c29eeaf121145 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 19 Dec 2019 16:48:15 +0000 Subject: [PATCH 012/109] hide verify button for own member --- src/components/views/right_panel/UserInfo.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 0f248d65d4..a69390d932 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1293,8 +1293,9 @@ const UserInfo = ({user, groupId, roomId, onClose}) => { } const userVerified = cli.checkUserTrust(user.userId).isVerified(); + const isMe = user.userId === cli.getUserId(); let verifyButton; - if (!userVerified) { + if (!userVerified && !isMe) { verifyButton = verifyUser(user)}> {_t("Verify")} ; From f4a276c052601abf3d5b3bb977894a9335e75ee8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 11:24:10 +0000 Subject: [PATCH 013/109] port MVerificationConclusion to use VerificationRequest --- .../messages/MKeyVerificationConclusion.js | 90 +++++-------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.js index 0bd8e2d3d8..fb83eda803 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.js @@ -19,102 +19,60 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import KeyVerificationStateObserver, {getNameForEventRoom, userLabelForEventRoom} +import {getNameForEventRoom, userLabelForEventRoom} from '../../../utils/KeyVerificationStateObserver'; export default class MKeyVerificationConclusion extends React.Component { constructor(props) { super(props); - this.keyVerificationState = null; - this.state = { - done: false, - cancelled: false, - otherPartyUserId: null, - cancelPartyUserId: null, - }; - const rel = this.props.mxEvent.getRelation(); - if (rel) { - const client = MatrixClientPeg.get(); - const room = client.getRoom(this.props.mxEvent.getRoomId()); - const requestEvent = room.findEventById(rel.event_id); - if (requestEvent) { - this._createStateObserver(requestEvent, client); - this.state = this._copyState(); - } else { - const findEvent = event => { - if (event.getId() === rel.event_id) { - this._createStateObserver(event, client); - this.setState(this._copyState()); - room.removeListener("Room.timeline", findEvent); - } - }; - room.on("Room.timeline", findEvent); - } - } - } - - _createStateObserver(requestEvent, client) { - this.keyVerificationState = new KeyVerificationStateObserver(requestEvent, client, () => { - this.setState(this._copyState()); - }); - } - - _copyState() { - const {done, cancelled, otherPartyUserId, cancelPartyUserId} = this.keyVerificationState; - return {done, cancelled, otherPartyUserId, cancelPartyUserId}; } componentDidMount() { - if (this.keyVerificationState) { - this.keyVerificationState.attach(); + const request = this.props.mxEvent.verificationRequest; + if (request) { + request.on("change", this._onRequestChanged); } } componentWillUnmount() { - if (this.keyVerificationState) { - this.keyVerificationState.detach(); + const request = this.props.mxEvent.verificationRequest; + if (request) { + request.off("change", this._onRequestChanged); } } - _getName(userId) { - const roomId = this.props.mxEvent.getRoomId(); - const client = MatrixClientPeg.get(); - const room = client.getRoom(roomId); - const member = room.getMember(userId); - return member ? member.name : userId; - } - - _userLabel(userId) { - const name = this._getName(userId); - if (name !== userId) { - return _t("%(name)s (%(userId)s)", {name, userId}); - } else { - return userId; - } - } + _onRequestChanged = () => { + this.forceUpdate(); + }; render() { const {mxEvent} = this.props; + const request = mxEvent.verificationRequest; const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); + + if (!request) { + return

This is a verification conclusion tile without a request.

; + } + let title; - if (this.state.done) { - title = _t("You verified %(name)s", {name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)}); - } else if (this.state.cancelled) { + if (request.done) { + title = _t("You verified %(name)s", {name: getNameForEventRoom(request.otherUserId, mxEvent)}); + } else if (request.cancelled) { if (mxEvent.getSender() === myUserId) { title = _t("You cancelled verifying %(name)s", - {name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)}); - } else if (mxEvent.getSender() === this.state.otherPartyUserId) { + {name: getNameForEventRoom(request.otherUserId, mxEvent)}); + } else if (mxEvent.getSender() === request.otherUserId) { title = _t("%(name)s cancelled verifying", - {name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)}); + {name: getNameForEventRoom(request.otherUserId, mxEvent)}); } } if (title) { - const subtitle = userLabelForEventRoom(this.state.otherPartyUserId, mxEvent); + const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent); const classes = classNames("mx_EventTile_bubble", "mx_KeyVerification", "mx_KeyVerification_icon", { - mx_KeyVerification_icon_verified: this.state.done, + mx_KeyVerification_icon_verified: request.done, }); return (
{title}
From 1aebc9579330d6b5e1793d173f77abf3013a413a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 11:24:27 +0000 Subject: [PATCH 014/109] slightly better copy --- src/components/views/messages/MKeyVerificationRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index c851cd30b5..0aa2188116 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -104,7 +104,7 @@ export default class MKeyVerificationRequest extends React.Component { let stateNode; if (!request) { - return

This is an invalid request, ho ho ho!

; + return

This is a verification request tile without a request.

; } if (request.ready || request.started || request.cancelled) { From dd633bd8fe0284eb4ceb6cc1f4c190a75d4d3593 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 12:02:37 +0000 Subject: [PATCH 015/109] port toast to use VerificationRequest and open right panel, not dialog --- .../views/toasts/VerificationRequestToast.js | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 7e043f4d83..30478ce281 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -18,10 +18,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from "../../../index"; import { _t } from '../../../languageHandler'; -import Modal from "../../../Modal"; import MatrixClientPeg from '../../../MatrixClientPeg'; -import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; -import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; +import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; +import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import dis from "../../../dispatcher"; export default class VerificationRequestToast extends React.PureComponent { @@ -33,16 +32,9 @@ export default class VerificationRequestToast extends React.PureComponent { const remaining = Math.max(0, timeout - age); const counter = Math.ceil(remaining / 1000); this.state = {counter}; - if (this.props.requestObserver) { - this.props.requestObserver.setCallback(this._checkRequestIsPending); - } } componentDidMount() { - if (this.props.requestObserver) { - this.props.requestObserver.attach(); - this._checkRequestIsPending(); - } this._intervalHandle = setInterval(() => { let {counter} = this.state; counter -= 1; @@ -52,20 +44,22 @@ export default class VerificationRequestToast extends React.PureComponent { this.setState({counter}); } }, 1000); + const {request} = this.props; + request.on("change", this._checkRequestIsPending); } componentWillUnmount() { clearInterval(this._intervalHandle); - if (this.props.requestObserver) { - this.props.requestObserver.detach(); - } + const {request} = this.props; + request.off("change", this._checkRequestIsPending); } _checkRequestIsPending = () => { - if (!this.props.requestObserver.pending) { + const {request} = this.props; + if (request.ready || request.started || request.done || request.cancelled) { this.props.dismiss(); } - } + }; cancel = () => { this.props.dismiss(); @@ -76,9 +70,10 @@ export default class VerificationRequestToast extends React.PureComponent { } } - accept = () => { + accept = async () => { this.props.dismiss(); - const {event} = this.props.request; + const {request} = this.props; + const {event} = request; // no room id for to_device requests if (event.getRoomId()) { dis.dispatch({ @@ -87,18 +82,24 @@ export default class VerificationRequestToast extends React.PureComponent { should_peek: false, }); } - - const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); - const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); - Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { - verifier, - }, null, /* priority = */ false, /* static = */ true); + try { + await request.accept(); + dis.dispatch({action: "show_right_panel"}); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {verificationRequest: request}, + }); + } catch (err) { + console.error(err.message); + } }; render() { const FormButton = sdk.getComponent("elements.FormButton"); - const {event} = this.props.request; - const userId = event.getSender(); + const {request} = this.props; + const {event} = request; + const userId = request.otherUserId; let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId; // for legacy to_device verification requests if (nameLabel === userId) { @@ -121,5 +122,4 @@ export default class VerificationRequestToast extends React.PureComponent { VerificationRequestToast.propTypes = { dismiss: PropTypes.func.isRequired, request: PropTypes.object.isRequired, - requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver), }; From 2dd1e93d84dbf5a0b20de70d09ecb8cb8fece4f2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:30:18 +0100 Subject: [PATCH 016/109] show message after clicking "they match" --- src/components/views/right_panel/VerificationPanel.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index c3fce4314b..ff7a356e70 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -35,7 +35,9 @@ export default class VerificationPanel extends React.PureComponent { } else if (request.ready) { return (

{request.otherUserId} is ready, start Verify by emoji

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

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

; + } else if (this.state.sasEvent) { const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); return (
{ + this.setState({sasWaitingForOtherParty: true}); this.state.sasEvent.confirm(); }; From de1c3e2f83b7f387adbb3432684545eb4882868d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:30:47 +0100 Subject: [PATCH 017/109] use same style as UserInfo for sections and paragraphs --- src/components/views/right_panel/EncryptionInfo.js | 13 +++++++------ .../views/right_panel/VerificationPanel.js | 8 ++++++++ src/i18n/strings/en_EN.json | 4 ++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/views/right_panel/EncryptionInfo.js b/src/components/views/right_panel/EncryptionInfo.js index f10d0b3499..49f4fc5da3 100644 --- a/src/components/views/right_panel/EncryptionInfo.js +++ b/src/components/views/right_panel/EncryptionInfo.js @@ -16,15 +16,16 @@ limitations under the License. import React from 'react'; import sdk from "../../.."; +import {_t} from "../../../languageHandler"; export default class EncryptionInfo extends React.PureComponent { render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - return (
-

End-to-end encryption is great! You should try it.

-
- Start verification -
-
); + return (
+

{_t("Verify User")}

+

{_t("For extra security, verify this user by checking a one-time code on both of your devices.")}

+

{_t("For maximum security, do this in person.")}

+ {_t("Start Verification")} +
); } } diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index ff7a356e70..9e5a448239 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -26,6 +26,14 @@ export default class VerificationPanel extends React.PureComponent { } render() { + return
+
+ { this.renderStatus() } +
+
; + } + + renderStatus() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const Spinner = sdk.getComponent('elements.Spinner'); const {request} = this.props; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6979759cd2..59c1684076 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1106,6 +1106,10 @@ "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", From 075e42c076d28ef6f252e183571ea066e3f06574 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:31:07 +0100 Subject: [PATCH 018/109] use transactionId as key for react --- src/components/structures/MatrixChat.js | 2 +- src/components/views/right_panel/EncryptionPanel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 88448311d2..0449c2b4fd 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1463,7 +1463,7 @@ export default createReactClass({ dis.dispatch({ action: "show_toast", toast: { - key: request.event.getId(), + key: request.channel.transactionId, title: _t("Verification Request"), icon: "verification", props: {request}, diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index 7992933864..e3f3b86940 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -30,7 +30,7 @@ export default class EncryptionPanel extends React.PureComponent { const request = this.props.verificationRequest || this.state.verificationRequest; const {member} = this.props; if (request) { - return ; + return ; } else if (member) { return ; } else { From b49c471f057dd51786ed2bbb297a1d37e42954ac Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:31:36 +0100 Subject: [PATCH 019/109] render empty tiles when no request --- .../views/messages/MKeyVerificationConclusion.js | 10 +++++++--- .../views/messages/MKeyVerificationRequest.js | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.js index fb83eda803..6dce9dcd48 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.js @@ -48,12 +48,14 @@ export default class MKeyVerificationConclusion extends React.Component { render() { const {mxEvent} = this.props; const request = mxEvent.verificationRequest; + + if (!request) { + return null; + } + const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); - if (!request) { - return

This is a verification conclusion tile without a request.

; - } let title; @@ -67,6 +69,8 @@ export default class MKeyVerificationConclusion extends React.Component { title = _t("%(name)s cancelled verifying", {name: getNameForEventRoom(request.otherUserId, mxEvent)}); } + } else { + title = `request conclusion tile with phase ${request.phase}`; } if (title) { diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 0aa2188116..ea99435d85 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -99,13 +99,14 @@ export default class MKeyVerificationRequest extends React.Component { const {mxEvent} = this.props; const request = mxEvent.verificationRequest; + if (!request) { + return null; + } + let title; let subtitle; let stateNode; - if (!request) { - return

This is a verification request tile without a request.

; - } if (request.ready || request.started || request.cancelled) { let stateLabel; From 52c7c5b8379dbb70912a15353993e2703d774949 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:31:53 +0100 Subject: [PATCH 020/109] render done tile as accepted --- src/components/views/messages/MKeyVerificationRequest.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index ea99435d85..ebdfa7c636 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -108,11 +108,12 @@ export default class MKeyVerificationRequest extends React.Component { let stateNode; - if (request.ready || request.started || request.cancelled) { + const accepted = request.ready || request.started || request.done; + if (accepted || request.cancelled) { let stateLabel; - if (request.ready || request.started) { + if (accepted) { stateLabel = this._acceptedLabel(request.receivingUserId); - } else if (request.cancelled) { + } else { stateLabel = this._cancelledLabel(request.cancellingUserId); } stateNode = (
{stateLabel}
); From 3b9c5c0a27aa7bdbc3fe8a2dbbe74ea4ef9ae9af Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Dec 2019 21:32:11 +0100 Subject: [PATCH 021/109] remove unused code --- src/utils/KeyVerificationStateObserver.js | 148 ---------------------- 1 file changed, 148 deletions(-) diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 7e33c29214..ef69a6b2f0 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -17,154 +17,6 @@ limitations under the License. import MatrixClientPeg from '../MatrixClientPeg'; import { _t } from '../languageHandler'; -const SUB_EVENT_TYPES_OF_INTEREST = ["start", "cancel", "done"]; - -export default class KeyVerificationStateObserver { - constructor(requestEvent, client, updateCallback) { - this._requestEvent = requestEvent; - this._client = client; - this._updateCallback = updateCallback; - this.accepted = false; - this.done = false; - this.cancelled = false; - this._updateVerificationState(); - } - - get concluded() { - return this.accepted || this.done || this.cancelled; - } - - get pending() { - return !this.concluded; - } - - setCallback(callback) { - this._updateCallback = callback; - } - - attach() { - this._requestEvent.on("Event.relationsCreated", this._onRelationsCreated); - for (const phaseName of SUB_EVENT_TYPES_OF_INTEREST) { - this._tryListenOnRelationsForType(`m.key.verification.${phaseName}`); - } - } - - detach() { - const roomId = this._requestEvent.getRoomId(); - const room = this._client.getRoom(roomId); - - for (const phaseName of SUB_EVENT_TYPES_OF_INTEREST) { - const relations = room.getUnfilteredTimelineSet() - .getRelationsForEvent(this._requestEvent.getId(), "m.reference", `m.key.verification.${phaseName}`); - if (relations) { - relations.removeListener("Relations.add", this._onRelationsUpdated); - relations.removeListener("Relations.remove", this._onRelationsUpdated); - relations.removeListener("Relations.redaction", this._onRelationsUpdated); - } - } - this._requestEvent.removeListener("Event.relationsCreated", this._onRelationsCreated); - } - - _onRelationsCreated = (relationType, eventType) => { - if (relationType !== "m.reference") { - return; - } - if ( - eventType !== "m.key.verification.start" && - eventType !== "m.key.verification.cancel" && - eventType !== "m.key.verification.done" - ) { - return; - } - this._tryListenOnRelationsForType(eventType); - this._updateVerificationState(); - this._updateCallback(); - }; - - _tryListenOnRelationsForType(eventType) { - const roomId = this._requestEvent.getRoomId(); - const room = this._client.getRoom(roomId); - const relations = room.getUnfilteredTimelineSet() - .getRelationsForEvent(this._requestEvent.getId(), "m.reference", eventType); - if (relations) { - relations.on("Relations.add", this._onRelationsUpdated); - relations.on("Relations.remove", this._onRelationsUpdated); - relations.on("Relations.redaction", this._onRelationsUpdated); - } - } - - _onRelationsUpdated = (event) => { - this._updateVerificationState(); - this._updateCallback && this._updateCallback(); - }; - - _updateVerificationState() { - const roomId = this._requestEvent.getRoomId(); - const room = this._client.getRoom(roomId); - const timelineSet = room.getUnfilteredTimelineSet(); - const fromUserId = this._requestEvent.getSender(); - const content = this._requestEvent.getContent(); - const toUserId = content.to; - - this.cancelled = false; - this.done = false; - this.accepted = false; - this.otherPartyUserId = null; - this.cancelPartyUserId = null; - - const startRelations = timelineSet.getRelationsForEvent( - this._requestEvent.getId(), "m.reference", "m.key.verification.start"); - if (startRelations) { - for (const startEvent of startRelations.getRelations()) { - if (startEvent.getSender() === toUserId) { - this.accepted = true; - } - } - } - - const doneRelations = timelineSet.getRelationsForEvent( - this._requestEvent.getId(), "m.reference", "m.key.verification.done"); - if (doneRelations) { - let senderDone = false; - let receiverDone = false; - for (const doneEvent of doneRelations.getRelations()) { - if (doneEvent.getSender() === toUserId) { - receiverDone = true; - } else if (doneEvent.getSender() === fromUserId) { - senderDone = true; - } - } - if (senderDone && receiverDone) { - this.done = true; - } - } - - if (!this.done) { - const cancelRelations = timelineSet.getRelationsForEvent( - this._requestEvent.getId(), "m.reference", "m.key.verification.cancel"); - - if (cancelRelations) { - let earliestCancelEvent; - for (const cancelEvent of cancelRelations.getRelations()) { - // only accept cancellation from the users involved - if (cancelEvent.getSender() === toUserId || cancelEvent.getSender() === fromUserId) { - this.cancelled = true; - if (!earliestCancelEvent || cancelEvent.getTs() < earliestCancelEvent.getTs()) { - earliestCancelEvent = cancelEvent; - } - } - } - if (earliestCancelEvent) { - this.cancelPartyUserId = earliestCancelEvent.getSender(); - } - } - } - - this.otherPartyUserId = fromUserId === this._client.getUserId() ? toUserId : fromUserId; - console.log("KeyVerificationStateObserver update for txnId", this._requestEvent.getId(), {accepted: this.accepted, cancelled: this.cancelled, done: this.done}); - } -} - export function getNameForEventRoom(userId, mxEvent) { const roomId = mxEvent.getRoomId(); const client = MatrixClientPeg.get(); From 7a88a94936bf3471bf82852a15775f2fca12f016 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 3 Jan 2020 13:40:20 +0100 Subject: [PATCH 022/109] fixes! --- .../messages/MKeyVerificationConclusion.js | 33 +++++++++++++++---- .../views/messages/MKeyVerificationRequest.js | 30 ++++++++++------- .../views/right_panel/EncryptionInfo.js | 2 +- .../views/right_panel/VerificationPanel.js | 4 +-- .../views/toasts/VerificationRequestToast.js | 19 +++-------- 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.js index 6dce9dcd48..90edb2f05d 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.js @@ -45,32 +45,51 @@ export default class MKeyVerificationConclusion extends React.Component { this.forceUpdate(); }; + _shouldRender(mxEvent, request) { + // normally should not happen + if (!request) { + return false; + } + // .cancel event that was sent after the verification finished, ignore + if (mxEvent.getType() === "m.key.verification.cancel" && !request.cancelled) { + return false; + } + // .done event that was sent after the verification cancelled, ignore + if (mxEvent.getType() === "m.key.verification.done" && !request.done) { + return false; + } + + // request hasn't concluded yet + if (request.pending) { + return false; + } + return true; + } + render() { const {mxEvent} = this.props; const request = mxEvent.verificationRequest; - if (!request) { + if (!this._shouldRender(mxEvent, request)) { return null; } const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); - let title; if (request.done) { title = _t("You verified %(name)s", {name: getNameForEventRoom(request.otherUserId, mxEvent)}); } else if (request.cancelled) { - if (mxEvent.getSender() === myUserId) { + const userId = request.cancellingUserId; + if (userId === myUserId) { title = _t("You cancelled verifying %(name)s", {name: getNameForEventRoom(request.otherUserId, mxEvent)}); - } else if (mxEvent.getSender() === request.otherUserId) { + } else { title = _t("%(name)s cancelled verifying", - {name: getNameForEventRoom(request.otherUserId, mxEvent)}); + {name: getNameForEventRoom(userId, mxEvent)}); } - } else { - title = `request conclusion tile with phase ${request.phase}`; } if (title) { diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index ebdfa7c636..1bef3b4469 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -43,6 +43,16 @@ export default class MKeyVerificationRequest extends React.Component { } } + _openRequest = () => { + const {verificationRequest} = this.props.mxEvent; + dis.dispatch({action: "show_right_panel"}); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {verificationRequest}, + }); + }; + _onRequestChanged = () => { this.forceUpdate(); }; @@ -52,12 +62,7 @@ export default class MKeyVerificationRequest extends React.Component { if (request) { try { await request.accept(); - dis.dispatch({action: "show_right_panel"}); - dis.dispatch({ - action: "set_right_panel_phase", - phase: RIGHT_PANEL_PHASES.EncryptionPanel, - refireParams: {verificationRequest: request}, - }); + this._openRequest(); } catch (err) { console.error(err.message); } @@ -96,10 +101,13 @@ export default class MKeyVerificationRequest extends React.Component { } render() { + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + const FormButton = sdk.getComponent("elements.FormButton"); + const {mxEvent} = this.props; const request = mxEvent.verificationRequest; - if (!request) { + if (!request || request.invalid) { return null; } @@ -107,12 +115,13 @@ export default class MKeyVerificationRequest extends React.Component { let subtitle; let stateNode; - const accepted = request.ready || request.started || request.done; if (accepted || request.cancelled) { let stateLabel; if (accepted) { - stateLabel = this._acceptedLabel(request.receivingUserId); + stateLabel = ( + {this._acceptedLabel(request.receivingUserId)} + ); } else { stateLabel = this._cancelledLabel(request.cancellingUserId); } @@ -124,8 +133,7 @@ export default class MKeyVerificationRequest extends React.Component { _t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}
); subtitle = (
{ userLabelForEventRoom(request.requestingUserId, mxEvent)}
); - if (request.requested) { - const FormButton = sdk.getComponent("elements.FormButton"); + if (request.requested && !request.observeOnly) { stateNode = (
diff --git a/src/components/views/right_panel/EncryptionInfo.js b/src/components/views/right_panel/EncryptionInfo.js index 49f4fc5da3..3d5de829b7 100644 --- a/src/components/views/right_panel/EncryptionInfo.js +++ b/src/components/views/right_panel/EncryptionInfo.js @@ -21,7 +21,7 @@ import {_t} from "../../../languageHandler"; export default class EncryptionInfo extends React.PureComponent { render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - return (
+ return (

{_t("Verify User")}

{_t("For extra security, verify this user by checking a one-time code on both of your devices.")}

{_t("For maximum security, do this in person.")}

diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 9e5a448239..e25aaabcfa 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -26,8 +26,8 @@ export default class VerificationPanel extends React.PureComponent { } render() { - return
-
+ return
+
{ this.renderStatus() }
; diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 30478ce281..f616575973 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -26,25 +26,16 @@ import dis from "../../../dispatcher"; export default class VerificationRequestToast extends React.PureComponent { constructor(props) { super(props); - const {event, timeout} = props.request; - // to_device requests don't have a timestamp, so consider them age=0 - const age = event.getTs() ? event.getLocalAge() : 0; - const remaining = Math.max(0, timeout - age); - const counter = Math.ceil(remaining / 1000); - this.state = {counter}; + this.state = {counter: Math.ceil(props.request.timeout / 1000)}; } componentDidMount() { + const {request} = this.props; this._intervalHandle = setInterval(() => { let {counter} = this.state; - counter -= 1; - if (counter <= 0) { - this.cancel(); - } else { - this.setState({counter}); - } + counter = Math.max(0, counter - 1); + this.setState({counter}); }, 1000); - const {request} = this.props; request.on("change", this._checkRequestIsPending); } @@ -56,7 +47,7 @@ export default class VerificationRequestToast extends React.PureComponent { _checkRequestIsPending = () => { const {request} = this.props; - if (request.ready || request.started || request.done || request.cancelled) { + if (request.ready || request.started || request.done || request.cancelled || request.observeOnly) { this.props.dismiss(); } }; From fb754b795cc37d2ac9077f6708dd0927319b0814 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jan 2020 20:30:01 +0000 Subject: [PATCH 023/109] Fix right panel buttons highlighting Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/GroupView.js | 2 +- src/components/views/right_panel/HeaderButtons.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 9df4630136..50b63b94b1 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1299,7 +1299,7 @@ export default createReactClass({ ); } - const rightPanel = !RightPanelStore.getSharedInstance().isOpenForGroup + const rightPanel = RightPanelStore.getSharedInstance().isOpenForGroup ? : undefined; diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.js index ebe1f5f915..dbcae4529a 100644 --- a/src/components/views/right_panel/HeaderButtons.js +++ b/src/components/views/right_panel/HeaderButtons.js @@ -74,7 +74,7 @@ export default class HeaderButtons extends React.Component { const rps = RightPanelStore.getSharedInstance(); if (this.state.headerKind === HEADER_KIND_ROOM) { this.setState({phase: rps.visibleRoomPanelPhase}); - } else if (this.state.head === HEADER_KIND_GROUP) { + } else if (this.state.headerKind === HEADER_KIND_GROUP) { this.setState({phase: rps.visibleGroupPanelPhase}); } } From 4118faa8340baf61610d78217ed80e715c83474d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jan 2020 20:31:07 +0000 Subject: [PATCH 024/109] When looking at a MemberInfo, clicking on MemberList should close not switch Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/GroupHeaderButtons.js | 9 +++++++-- src/components/views/right_panel/RoomHeaderButtons.js | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index c134a5d237..f164b6c578 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -66,8 +66,13 @@ export default class GroupHeaderButtons extends HeaderButtons { } _onMembersClicked() { - // This toggles for us, if needed - this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); + if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) { + // send the active phase to trigger a toggle + this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo); + } else { + // This toggles for us, if needed + this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); + } } _onRoomsClicked() { diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js index f59159d1d9..3831a5953e 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.js +++ b/src/components/views/right_panel/RoomHeaderButtons.js @@ -56,8 +56,13 @@ export default class RoomHeaderButtons extends HeaderButtons { } _onMembersClicked() { - // This toggles for us, if needed - this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); + if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) { + // send the active phase to trigger a toggle + this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo); + } else { + // This toggles for us, if needed + this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); + } } _onFilesClicked() { From 0a9d7f43c64da95e74046b589286f92648fbf746 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 9 Jan 2020 15:51:19 +0000 Subject: [PATCH 025/109] pass the correct phase params for consistency Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/GroupHeaderButtons.js | 3 ++- src/components/views/right_panel/RoomHeaderButtons.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index f164b6c578..1602f47347 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -23,6 +23,7 @@ import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HEADER_KIND_GROUP} from './HeaderButtons'; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; +import RightPanelStore from "../../../stores/RightPanelStore"; const GROUP_PHASES = [ RIGHT_PANEL_PHASES.GroupMemberInfo, @@ -68,7 +69,7 @@ export default class GroupHeaderButtons extends HeaderButtons { _onMembersClicked() { if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) { // send the active phase to trigger a toggle - this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo); + this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo, RightPanelStore.getSharedInstance().roomPanelPhaseParams); } else { // This toggles for us, if needed this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js index 3831a5953e..449ab3d686 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.js +++ b/src/components/views/right_panel/RoomHeaderButtons.js @@ -23,6 +23,7 @@ import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HEADER_KIND_ROOM} from './HeaderButtons'; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; +import RightPanelStore from "../../../stores/RightPanelStore"; const MEMBER_PHASES = [ RIGHT_PANEL_PHASES.RoomMemberList, @@ -58,7 +59,7 @@ export default class RoomHeaderButtons extends HeaderButtons { _onMembersClicked() { if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) { // send the active phase to trigger a toggle - this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo); + this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo, RightPanelStore.getSharedInstance().roomPanelPhaseParams); } else { // This toggles for us, if needed this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); From eeee736e9fd87e86b2bfad59bffb43f3406bc119 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 13 Jan 2020 13:22:03 +0000 Subject: [PATCH 026/109] Translated using Weblate (Hungarian) Currently translated at 100.0% (2001 of 2001 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 68af34ae2d..900694e2ab 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2037,5 +2037,6 @@ "Below is a list of bridges connected to this room.": "Alább látható a lista a szobához kapcsolódó hidakról.", "Suggestions": "Javaslatok", "Failed to find the following users": "Az alábbi felhasználók nem találhatók", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Az alábbi felhasználók nem léteznek vagy hibásan vannak megadva és nem lehet őket meghívni: %(csvNames)s" + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Az alábbi felhasználók nem léteznek vagy hibásan vannak megadva és nem lehet őket meghívni: %(csvNames)s", + "Show a presence dot next to DMs in the room list": "Jelenlét pötty mutatása a Közvetlen beszélgetések mellett a szobák listájában" } From ce8520493728ab883f540eaa722683581a6fcd40 Mon Sep 17 00:00:00 2001 From: call_xz Date: Mon, 13 Jan 2020 12:50:52 +0000 Subject: [PATCH 027/109] Translated using Weblate (Japanese) Currently translated at 59.5% (1190 of 2001 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 | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index cce9a81188..c4c231249f 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -357,7 +357,7 @@ "(could not connect media)": "(メディアを接続できませんでした)", "(no answer)": "(応答なし)", "(unknown failure: %(reason)s)": "(不明なエラー: %(reason)s)", - "%(senderName)s ended the call.": "%(senderName)s は通話を終了しました。", + "%(senderName)s ended the call.": "%(senderName)s が通話を終了しました。", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s は部屋に加わるよう %(targetDisplayName)s に招待状を送りました。", "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s は、部屋のメンバー全員に招待された時点からの部屋履歴を参照できるようにしました。", "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s は、部屋のメンバー全員に参加した時点からの部屋履歴を参照できるようにしました。", @@ -658,7 +658,7 @@ "Popout widget": "ウィジェットをポップアウト", "Unblacklist": "ブラックリスト解除", "Blacklist": "ブラックリスト", - "Verify...": "検証中...", + "Verify...": "検証する...", "No results": "結果がありません", "Communities": "コミュニティ", "Home": "ホーム", @@ -947,9 +947,9 @@ "click to reveal": "クリックすると表示されます", "Homeserver is": "ホームサーバーは", "Identity Server is": "アイデンティティ・サーバー", - "matrix-react-sdk version:": "matrix-react-sdk version:", - "riot-web version:": "riot-web version:", - "olm version:": "olm version:", + "matrix-react-sdk version:": "matrix-react-sdk のバージョン:", + "riot-web version:": "riot-web のバージョン:", + "olm version:": "olm のバージョン:", "Failed to send email": "メールを送信できませんでした", "The email address linked to your account must be entered.": "あなたのアカウントにリンクされているメールアドレスを入力する必要があります。", "A new password must be entered.": "新しいパスワードを入力する必要があります。", @@ -1124,7 +1124,7 @@ "Enable Community Filter Panel": "コミュニティーフィルターパネルを有効にする", "Show recently visited rooms above the room list": "最近訪問した部屋をリストの上位に表示する", "Low bandwidth mode": "低帯域通信モード", - "Public Name": "パブリック名", + "Public Name": "公開名", "Upload profile picture": "プロフィール画像をアップロード", "Upgrade to your own domain": "あなた自身のドメインにアップグレード", "Phone numbers": "電話番号", @@ -1134,7 +1134,7 @@ "General": "一般", "Preferences": "環境設定", "Security & Privacy": "セキュリティとプライバシー", - "A device's public name is visible to people you communicate with": "デバイスのパブリック名はあなたと会話するすべての人が閲覧できます", + "A device's public name is visible to people you communicate with": "デバイスの公開名はあなたと会話するすべての人が閲覧できます", "Room information": "部屋の情報", "Internal room ID:": "内部 部屋ID:", "Room version": "部屋のバージョン", @@ -1195,5 +1195,28 @@ "Other servers": "他のサーバー", "Sign in to your Matrix account on %(serverName)s": "%(serverName)s上のMatrixアカウントにサインインします", "Sign in to your Matrix account on ": "上のMatrixアカウントにサインインします", - "Create account": "アカウントを作成" + "Create account": "アカウントを作成", + "Error upgrading room": "部屋のアップグレード中にエラーが発生しました", + "%(senderName)s placed a voice call.": "%(senderName)s が音声通話を行いました。", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s が音声通話を行いました。 (このブラウザではサポートされていません)", + "%(senderName)s placed a video call.": "%(senderName)s がビデオ通話を行いました。", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s がビデオ通話を行いました。 (このブラウザではサポートされていません)", + "Match system theme": "システムテーマに合わせる", + "Allow Peer-to-Peer for 1:1 calls": "1対1通話でP2P(ピアツーピア)を許可する", + "Delete Backup": "バックアップを削除", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "暗号化されたメッセージは、エンドツーエンドの暗号化によって保護されています。これらの暗号化されたメッセージを読むための鍵を持っているのは、あなたと参加者だけです。", + "Restore from Backup": "バックアップから復元", + "This device is backing up your keys. ": "このデバイスは鍵をバックアップしています。 ", + "Enable desktop notifications for this device": "このデバイスでデスクトップ通知を有効にする", + "Credits": "クレジット", + "For help with using Riot, click here.": "Riotの使用方法に関するヘルプはこちらをご覧ください。", + "Help & About": "ヘルプと概要", + "Bug reporting": "バグの報告", + "FAQ": "よくある質問", + "Versions": "バージョン", + "Key backup": "鍵のバックアップ", + "Voice & Video": "音声とビデオ", + "Remove recent messages": "最近のメッセージを削除する", + "%(creator)s created and configured the room.": "%(creator)s が部屋を作成して構成しました。", + "Add room": "部屋を追加" } From 1aec21562467dca4d25ecbdc06fde06efce138b6 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 14 Jan 2020 03:18:51 +0000 Subject: [PATCH 028/109] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2001 of 2001 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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 9401bb4a65..1216ad79a9 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2036,5 +2036,7 @@ "Below is a list of bridges connected to this room.": "以下是連線到此聊天室的橋接列表。", "Suggestions": "建議", "Failed to find the following users": "找不到以下使用者", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "以下使用者可能不存在或無效,且無法被邀請:%(csvNames)s" + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "以下使用者可能不存在或無效,且無法被邀請:%(csvNames)s", + "Show a presence dot next to DMs in the room list": "在聊天室清單中的直接訊息旁顯示上線狀態點", + "Lock": "鎖定" } From 1542ea75c9702d30faa0d99380a85dd2a188a956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Tue, 14 Jan 2020 08:15:09 +0000 Subject: [PATCH 029/109] Translated using Weblate (French) Currently translated at 100.0% (2001 of 2001 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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index d526c53430..84cdf623ee 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2036,5 +2036,7 @@ "Below is a list of bridges connected to this room.": "Vous trouverez ci-dessous la liste des passerelles connectées à ce salon.", "Suggestions": "Suggestions", "Failed to find the following users": "Impossible de trouver les utilisateurs suivants", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Les utilisateurs suivant n’existent peut-être pas ou ne sont pas valides, et ne peuvent pas être invités : %(csvNames)s" + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Les utilisateurs suivant n’existent peut-être pas ou ne sont pas valides, et ne peuvent pas être invités : %(csvNames)s", + "Show a presence dot next to DMs in the room list": "Afficher une pastille de présence à côté des messages directs dans la liste des salons", + "Lock": "Cadenas" } From b556208014620de8de1d72b688c64bf7c2a1e1af Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 13 Jan 2020 21:43:10 +0000 Subject: [PATCH 030/109] Translated using Weblate (Hungarian) Currently translated at 100.0% (2001 of 2001 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 900694e2ab..0dcea60fda 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2038,5 +2038,6 @@ "Suggestions": "Javaslatok", "Failed to find the following users": "Az alábbi felhasználók nem találhatók", "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Az alábbi felhasználók nem léteznek vagy hibásan vannak megadva és nem lehet őket meghívni: %(csvNames)s", - "Show a presence dot next to DMs in the room list": "Jelenlét pötty mutatása a Közvetlen beszélgetések mellett a szobák listájában" + "Show a presence dot next to DMs in the room list": "Jelenlét pötty mutatása a Közvetlen beszélgetések mellett a szobák listájában", + "Lock": "Zár" } From d37c19d785f06e2ba0a42712f29036f9cc8f9142 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Jan 2020 00:27:54 +0000 Subject: [PATCH 031/109] Fix timing of when Composer considers itself to be modified Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/BasicMessageComposer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 15585ffea0..080d248a29 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -126,6 +126,7 @@ export default class BasicMessageEditor extends React.Component { } _updateEditorState = (selection, inputType, diff) => { + this._modifiedFlag = true; renderModel(this._editorRef, this.props.model); if (selection) { // set the caret/selection try { @@ -205,7 +206,6 @@ export default class BasicMessageEditor extends React.Component { const {partCreator} = model; const text = event.clipboardData.getData("text/plain"); if (text) { - this._modifiedFlag = true; const range = getRangeForSelection(this._editorRef, model, document.getSelection()); const parts = parsePlainTextMessage(text, partCreator); replaceRangeAndMoveCaret(range, parts); @@ -218,7 +218,6 @@ export default class BasicMessageEditor extends React.Component { if (this._isIMEComposing) { return; } - this._modifiedFlag = true; const sel = document.getSelection(); const {caret, text} = getCaretOffsetAndText(this._editorRef, sel); this.props.model.update(text, event.inputType, caret); @@ -230,7 +229,6 @@ export default class BasicMessageEditor extends React.Component { const newText = text.substr(0, caret.offset) + textToInsert + text.substr(caret.offset); caret.offset += textToInsert.length; this.props.model.update(newText, inputType, caret); - this._modifiedFlag = true; } // this is used later to see if we need to recalculate the caret From 443744733dc8fbea8c3bb805c29b1dbcb80a2a98 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 14 Jan 2020 23:32:00 -0700 Subject: [PATCH 032/109] Move DM creation logic into DMInviteDialog Fixes https://github.com/vector-im/riot-web/issues/11645 The copy hasn't been reviewed by anyone and could probably use some work. --- res/css/views/dialogs/_DMInviteDialog.scss | 11 +++ src/RoomInvite.js | 14 ++- .../views/dialogs/DMInviteDialog.js | 92 +++++++++++++++++-- src/i18n/strings/en_EN.json | 2 + src/utils/DMRoomMap.js | 21 +++++ 5 files changed, 122 insertions(+), 18 deletions(-) diff --git a/res/css/views/dialogs/_DMInviteDialog.scss b/res/css/views/dialogs/_DMInviteDialog.scss index f806e85120..5d58f3ae8b 100644 --- a/res/css/views/dialogs/_DMInviteDialog.scss +++ b/res/css/views/dialogs/_DMInviteDialog.scss @@ -67,6 +67,17 @@ limitations under the License. height: 25px; line-height: 25px; } + + .mx_DMInviteDialog_buttonAndSpinner { + .mx_Spinner { + // Width and height are required to trick the layout engine. + width: 20px; + height: 20px; + margin-left: 5px; + display: inline-block; + vertical-align: middle; + } + } } .mx_DMInviteDialog_section { diff --git a/src/RoomInvite.js b/src/RoomInvite.js index ba9fe1f541..675efe53c8 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -36,21 +36,19 @@ import SettingsStore from "./settings/SettingsStore"; * @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids. * @returns {Promise} Promise */ -function inviteMultipleToRoom(roomId, addrs) { +export function inviteMultipleToRoom(roomId, addrs) { const inviter = new MultiInviter(roomId); return inviter.invite(addrs).then(states => Promise.resolve({states, inviter})); } 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 DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog"); - Modal.createTrackedDialog('Start DM', '', DMInviteDialog, { - onFinished: (inviteIds) => { - // TODO: Replace _onStartDmFinished with less hacks - if (inviteIds.length > 0) _onStartDmFinished(true, inviteIds.map(i => ({address: i}))); - // else ignore and just do nothing - }, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); + Modal.createTrackedDialog( + 'Start DM', '', DMInviteDialog, {}, + /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, + ); return; } diff --git a/src/components/views/dialogs/DMInviteDialog.js b/src/components/views/dialogs/DMInviteDialog.js index 371768eb4e..e82d63acad 100644 --- a/src/components/views/dialogs/DMInviteDialog.js +++ b/src/components/views/dialogs/DMInviteDialog.js @@ -31,6 +31,8 @@ import {abbreviateUrl} from "../../../utils/UrlUtils"; import dis from "../../../dispatcher"; import IdentityAuthClient from "../../../IdentityAuthClient"; import Modal from "../../../Modal"; +import createRoom from "../../../createRoom"; +import {inviteMultipleToRoom} from "../../../RoomInvite"; // TODO: [TravisR] Make this generic for all kinds of invites @@ -295,6 +297,10 @@ export default class DMInviteDialog extends React.PureComponent { threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(), tryingIdentityServer: false, + + // These two flags are used for the 'Go' button to communicate what is going on. + busy: true, + errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."), }; this._editorRef = createRef(); @@ -381,11 +387,66 @@ export default class DMInviteDialog extends React.PureComponent { } _startDm = () => { - this.props.onFinished(this.state.targets.map(t => t.userId)); + this.setState({busy: true}); + const targetIds = this.state.targets.map(t => t.userId); + + // Check if there is already a DM with these people and reuse it if possible. + const existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds); + if (existingRoom) { + dis.dispatch({ + action: 'view_room', + room_id: existingRoom.roomId, + should_peek: false, + joining: false, + }); + this.props.onFinished(); + return; + } + + // 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]}) + } else { + // Create a boring room and try to invite the targets manually. + let room; + createRoomPromise = createRoom().then(roomId => { + room = MatrixClientPeg.get().getRoom(roomId); + return inviteMultipleToRoom(roomId, targetIds); + }).then(result => { + const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); + if (failedUsers.length > 0) { + console.log("Failed to invite users: ", result); + this.setState({ + busy: false, + errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", { + csvUsers: failedUsers.join(", "), + }), + }); + return true; // abort + } + }); + } + + // the createRoom call will show the room for us, so we don't need to worry about that. + createRoomPromise.then(abort => { + if (abort === true) return; // only abort on true booleans, not roomIds or something + this.props.onFinished(); + }).catch(err => { + console.error(err); + this.setState({ + busy: false, + errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."), + }); + }); }; _cancel = () => { - this.props.onFinished([]); + // We do not want the user to close the dialog while an action is in progress + if (this.state.busy) return; + + this.props.onFinished(); }; _updateFilter = (e) => { @@ -735,6 +796,12 @@ export default class DMInviteDialog extends React.PureComponent { render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + const Spinner = sdk.getComponent("elements.Spinner"); + + let spinner = null; + if (this.state.busy) { + spinner = ; + } const userId = MatrixClientPeg.get().getUserId(); return ( @@ -755,15 +822,20 @@ export default class DMInviteDialog extends React.PureComponent {

{this._renderEditor()} - {this._renderIdentityServerWarning()} - - {_t("Go")} - +
+ + {_t("Go")} + + {spinner} +
+ {this._renderIdentityServerWarning()} +
{this.state.errorText}
{this._renderSection('recents')} {this._renderSection('suggestions')}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9627ac0e14..82f0cf8521 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1423,6 +1423,8 @@ "View Servers in Room": "View Servers in Room", "Toolbox": "Toolbox", "Developer Tools": "Developer Tools", + "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.", "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", diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js index 498c073e0e..e42d724748 100644 --- a/src/utils/DMRoomMap.js +++ b/src/utils/DMRoomMap.js @@ -124,6 +124,27 @@ export default class DMRoomMap { return this._getUserToRooms()[userId] || []; } + /** + * Gets the DM room which the given IDs share, if any. + * @param {string[]} ids The identifiers (user IDs and email addresses) to look for. + * @returns {Room} The DM room which all IDs given share, or falsey if no common room. + */ + getDMRoomForIdentifiers(ids) { + // TODO: [Canonical DMs] Handle lookups for email addresses. + // For now we'll pretend we only get user IDs and end up returning nothing for email addresses + + let commonRooms = this.getDMRoomsForUserId(ids[0]); + for (let i = 1; i < ids.length; i++) { + const userRooms = this.getDMRoomsForUserId(ids[i]); + commonRooms = commonRooms.filter(r => userRooms.includes(r)); + } + + const joinedRooms = commonRooms.map(r => MatrixClientPeg.get().getRoom(r)) + .filter(r => r && r.getMyMembership() === 'join'); + + return joinedRooms[0]; + } + getUserIdForRoomId(roomId) { if (this.roomToUser == null) { // we lazily populate roomToUser so you can use From b9852c7264cff54246bfb3bbd9f42b5dd8cf047d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 14 Jan 2020 23:35:07 -0700 Subject: [PATCH 033/109] Appease the linter --- src/components/views/dialogs/DMInviteDialog.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/dialogs/DMInviteDialog.js b/src/components/views/dialogs/DMInviteDialog.js index e82d63acad..904d531c60 100644 --- a/src/components/views/dialogs/DMInviteDialog.js +++ b/src/components/views/dialogs/DMInviteDialog.js @@ -407,12 +407,10 @@ export default class DMInviteDialog extends React.PureComponent { // 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]}) + createRoomPromise = createRoom({dmUserId: targetIds[0]}); } else { // Create a boring room and try to invite the targets manually. - let room; createRoomPromise = createRoom().then(roomId => { - room = MatrixClientPeg.get().getRoom(roomId); return inviteMultipleToRoom(roomId, targetIds); }).then(result => { const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); From 8b6a5d37aa10d4155a833c0b91ddd8de5ed903c4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 14 Jan 2020 23:35:45 -0700 Subject: [PATCH 034/109] Remove simulated error state used for screenshot --- src/components/views/dialogs/DMInviteDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/DMInviteDialog.js b/src/components/views/dialogs/DMInviteDialog.js index 904d531c60..6422749d60 100644 --- a/src/components/views/dialogs/DMInviteDialog.js +++ b/src/components/views/dialogs/DMInviteDialog.js @@ -299,8 +299,8 @@ export default class DMInviteDialog extends React.PureComponent { tryingIdentityServer: false, // These two flags are used for the 'Go' button to communicate what is going on. - busy: true, - errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."), + busy: false, + errorText: null, }; this._editorRef = createRef(); From 93b9bf1647600192e619390360cd67423daa5f6e Mon Sep 17 00:00:00 2001 From: Osoitz Date: Wed, 15 Jan 2020 06:06:42 +0000 Subject: [PATCH 035/109] Translated using Weblate (Basque) Currently translated at 100.0% (2004 of 2004 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 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 11904a95f0..036f70d9cb 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -2035,5 +2035,11 @@ "Go": "Joan", "Suggestions": "Proposamenak", "Failed to find the following users": "Ezin izan dira honako erabiltzaile hauek aurkitu", - "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Honako erabiltzaile hauek agian ez dira existitzen edo baliogabeak dira, eta ezin dira gonbidatu: %(csvNames)s" + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Honako erabiltzaile hauek agian ez dira existitzen edo baliogabeak dira, eta ezin dira gonbidatu: %(csvNames)s", + "Show a presence dot next to DMs in the room list": "Erakutsi presentzia puntua mezu zuzenen ondoan gelen zerrendan", + "Lock": "Blokeatu", + "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.": "Gakoen babes-kopia gaituta dago zure kontuan baina ez da saio honetarako ezarri. Biltegi sekretua ezartzeko, berrezarri zure gakoen babes-kopia.", + "Restore": "Berrezarri", + "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 ezarriko da zure oraingo gakoen babes-kopiaren xehetasunak erabiliz. Zure biltegi sekretuaren pasa-esaldia eta berreskuratze gakoa lehen gakoen babes-kopiarako ziren berberak izango dira", + "Restore your Key Backup": "Berrezarri zure gakoen babes-kopia" } From c74dc2c87026ed600f77c6abf8ebd86740ad71b3 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 15 Jan 2020 03:45:57 +0000 Subject: [PATCH 036/109] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2004 of 2004 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 1216ad79a9..2d17d643ce 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2038,5 +2038,9 @@ "Failed to find the following users": "找不到以下使用者", "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "以下使用者可能不存在或無效,且無法被邀請:%(csvNames)s", "Show a presence dot next to DMs in the room list": "在聊天室清單中的直接訊息旁顯示上線狀態點", - "Lock": "鎖定" + "Lock": "鎖定", + "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": "還原", + "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": "秘密儲存空間將會使用您既有的金鑰備份詳細資訊設定。您的秘密儲存空間通關密語與復原金鑰會與您的金鑰備份相同", + "Restore your Key Backup": "復原您的金鑰備份" } From a9d5c7e7205d8ef5a8cb169a47c8844ad6132311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 15 Jan 2020 08:00:57 +0000 Subject: [PATCH 037/109] Translated using Weblate (French) Currently translated at 100.0% (2004 of 2004 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 84cdf623ee..c2863d713f 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2038,5 +2038,9 @@ "Failed to find the following users": "Impossible de trouver les utilisateurs suivants", "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Les utilisateurs suivant n’existent peut-être pas ou ne sont pas valides, et ne peuvent pas être invités : %(csvNames)s", "Show a presence dot next to DMs in the room list": "Afficher une pastille de présence à côté des messages directs dans la liste des salons", - "Lock": "Cadenas" + "Lock": "Cadenas", + "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.": "La sauvegarde de clés est activée pour votre compte mais n’a pas été configurée pour cette session. Pour configurer le coffre secret, restaurez votre sauvegarde de clés.", + "Restore": "Restaurer", + "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 paramètres existants de la 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", + "Restore your Key Backup": "Restaurer votre sauvegarde de clés" } From 6f351ed2257c4876b4fb6a668be5696bbd9888c5 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Wed, 15 Jan 2020 18:38:26 +0000 Subject: [PATCH 038/109] Translated using Weblate (Basque) Currently translated at 100.0% (2018 of 2018 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 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 036f70d9cb..31dc6497ef 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -2041,5 +2041,19 @@ "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.": "Gakoen babes-kopia gaituta dago zure kontuan baina ez da saio honetarako ezarri. Biltegi sekretua ezartzeko, berrezarri zure gakoen babes-kopia.", "Restore": "Berrezarri", "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 ezarriko da zure oraingo gakoen babes-kopiaren xehetasunak erabiliz. Zure biltegi sekretuaren pasa-esaldia eta berreskuratze gakoa lehen gakoen babes-kopiarako ziren berberak izango dira", - "Restore your Key Backup": "Berrezarri zure gakoen babes-kopia" + "Restore your Key Backup": "Berrezarri zure gakoen babes-kopia", + "a few seconds ago": "duela segundo batzuk", + "about a minute ago": "duela minutu bat inguru", + "%(num)s minutes ago": "duela %(num)s minutu", + "about an hour ago": "duela ordubete inguru", + "%(num)s hours ago": "duela %(num)s ordu", + "about a day ago": "duela egun bat inguru", + "%(num)s days ago": "duela %(num)s egun", + "a few seconds from now": "hemendik segundo batzuetara", + "about a minute from now": "hemendik minutu batera", + "%(num)s minutes from now": "hemendik %(num)s minututara", + "about an hour from now": "hemendik ordubetera", + "%(num)s hours from now": "hemendik %(num)s ordutara", + "about a day from now": "hemendik egun batera", + "%(num)s days from now": "hemendik %(num)s egunetara" } From 27ee90cad58666a97e3a61993f3a7c2575753aa2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 Jan 2020 21:13:56 +0000 Subject: [PATCH 039/109] Add post-login complete security flow This adds a step after login to complete security for your new session. At the moment, the only verification method is entering your SSSS passphrase, but nicer paths will be added soon. This new step only appears when crypto is available and the account has cross-signing enabled in SSSS. Fixes https://github.com/vector-im/riot-web/issues/11214 --- res/css/_components.scss | 1 + .../structures/auth/_CompleteSecurity.scss | 51 ++++++ src/MatrixClientPeg.js | 9 +- .../CreateSecretStorageDialog.js | 2 +- src/components/structures/MatrixChat.js | 90 ++++++--- .../structures/auth/CompleteSecurity.js | 173 ++++++++++++++++++ src/components/structures/auth/SoftLogout.js | 2 +- src/i18n/strings/en_EN.json | 10 +- src/settings/Settings.js | 3 +- 9 files changed, 309 insertions(+), 32 deletions(-) create mode 100644 res/css/structures/auth/_CompleteSecurity.scss create mode 100644 src/components/structures/auth/CompleteSecurity.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 7a9ebfdf26..a9a114a4cf 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -28,6 +28,7 @@ @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_ViewSource.scss"; +@import "./structures/auth/_CompleteSecurity.scss"; @import "./structures/auth/_Login.scss"; @import "./views/auth/_AuthBody.scss"; @import "./views/auth/_AuthButtons.scss"; diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss new file mode 100644 index 0000000000..c258ce4ec7 --- /dev/null +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -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. +*/ + +.mx_CompleteSecurity_header { + display: flex; + align-items: center; +} + +.mx_CompleteSecurity_headerIcon { + width: 24px; + height: 24px; + margin: 0 4px; + position: relative; +} + +.mx_CompleteSecurity_heroIcon { + width: 128px; + height: 128px; + position: relative; + margin: 0 auto; +} + +.mx_CompleteSecurity_body { + font-size: 15px; +} + +.mx_CompleteSecurity_actionRow { + display: flex; + justify-content: flex-end; + + .mx_AccessibleButton { + margin-inline-start: 18px; + + &.warning { + color: $warning-color; + } + } +} diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 9c939f2fd3..dbc570c872 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -2,7 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd. Copyright 2017, 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. @@ -223,9 +223,10 @@ class _MatrixClientPeg { }; opts.cryptoCallbacks = {}; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - Object.assign(opts.cryptoCallbacks, crossSigningCallbacks); - } + // These are always installed regardless of the labs flag so that + // cross-signing features can toggle on without reloading and also be + // accessed immediately after login. + Object.assign(opts.cryptoCallbacks, crossSigningCallbacks); this.matrixClient = createMatrixClient(opts); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 628214a2bb..01b9c9c7c8 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -313,7 +313,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {

{_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", + "they were for your key backup.", )}

{ + const actionHandlerRef = dis.register(payload => { + if (payload.action !== "on_logged_in") { + return; + } + dis.unregister(actionHandlerRef); + resolve(); + }); + }); + + const cli = MatrixClientPeg.get(); + // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` + // because the client hasn't been started yet. + if (!isCryptoAvailable()) { + this._onLoggedIn(); + } + + // Test for the master cross-signing key in SSSS as a quick proxy for + // whether cross-signing has been set up on the account. + let masterKeyInStorage = false; + try { + masterKeyInStorage = !!await cli.getAccountDataFromServer("m.cross_signing.master"); + } catch (e) { + if (e.errcode !== "M_NOT_FOUND") throw e; + } + + if (masterKeyInStorage) { + this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY }); + } else { + this._onLoggedIn(); + } + }, + + onCompleteSecurityFinished() { + this._onLoggedIn(); + }, + render: function() { // console.log(`Rendering MatrixChat with view ${this.state.view}`); let view; - if ( - this.state.view === VIEWS.LOADING || - this.state.view === VIEWS.LOGGING_IN - ) { + if (this.state.view === VIEWS.LOADING) { const Spinner = sdk.getComponent('elements.Spinner'); view = (
); + } else if (this.state.view === VIEWS.COMPLETE_SECURITY) { + const CompleteSecurity = sdk.getComponent('structures.auth.CompleteSecurity'); + view = ( + + ); } else if (this.state.view === VIEWS.POST_REGISTRATION) { // needs to be before normal PageTypes as you are logged in technically const PostRegistration = sdk.getComponent('structures.auth.PostRegistration'); @@ -1921,7 +1965,7 @@ export default createReactClass({ const Login = sdk.getComponent('structures.auth.Login'); view = ( { + const cli = MatrixClientPeg.get(); + await accessSecretStorage(async () => { + await cli.checkOwnCrossSigningTrust(); + }); + this.setState({ + phase: PHASE_DONE, + }); + } + + onSkipClick = () => { + this.setState({ + phase: PHASE_CONFIRM_SKIP, + }); + } + + onSkipConfirmClick = () => { + this.props.onFinished(); + } + + onSkipBackClick = () => { + this.setState({ + phase: PHASE_INTRO, + }); + } + + onDoneClick = () => { + this.props.onFinished(); + } + + 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"); + + const { + phase, + } = this.state; + + let icon; + let title; + let body; + if (phase === PHASE_INTRO) { + icon = ; + title = _t("Complete security"); + body = ( +
+

{_t( + "Verify this session to grant it access to encrypted messages.", + )}

+
+ + {_t("Skip")} + + + {_t("Start")} + +
+
+ ); + } else if (phase === PHASE_DONE) { + icon = ; + title = _t("Session verified"); + body = ( +
+
+

{_t( + "Your new session is now verified. It has access to your " + + "encrypted messages, and other users will see it as trusted.", + )}

+
+ + {_t("Done")} + +
+
+ ); + } else if (phase === PHASE_CONFIRM_SKIP) { + icon = ; + title = _t("Are you sure?"); + body = ( +
+

{_t( + "Without completing security on this device, it won’t have " + + "access to encrypted messages.", + )}

+
+ + {_t("Skip")} + + + {_t("Go Back")} + +
+
+ ); + } else { + throw new Error(`Unknown phase ${phase}`); + } + + return ( + + + +

+ {icon} + {title} +

+
+ {body} +
+
+
+ ); + } +} diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js index 63f590da2e..40800ad907 100644 --- a/src/components/structures/auth/SoftLogout.js +++ b/src/components/structures/auth/SoftLogout.js @@ -66,7 +66,7 @@ export default class SoftLogout extends React.Component { componentDidMount(): void { // We've ended up here when we don't need to - navigate to login if (!Lifecycle.isSoftLogout()) { - dis.dispatch({action: "on_logged_in"}); + dis.dispatch({action: "start_login"}); return; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 42c87172b8..3756b4c60b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1848,6 +1848,14 @@ "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.", @@ -1952,7 +1960,7 @@ "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", + "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.", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 8ee8d89890..2b8c0aef89 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,7 +1,7 @@ /* Copyright 2017 Travis Ralston 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. @@ -153,7 +153,6 @@ export const SETTINGS = { displayName: _td("Enable cross-signing to verify per-user instead of per-device (in development)"), supportedLevels: LEVELS_FEATURE, default: false, - controller: new ReloadOnChangeController(), }, "feature_event_indexing": { isFeature: true, From 6e027badc0b0f5e7a32ec340b0aa33033d658896 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 Jan 2020 22:10:59 +0000 Subject: [PATCH 040/109] Tweak comparison Co-Authored-By: Travis Ralston --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d9aa5e902d..edc0501086 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1566,7 +1566,7 @@ export default createReactClass({ dis.dispatch({ action: 'view_my_groups', }); - } else if (screen == 'complete_security') { + } else if (screen === 'complete_security') { dis.dispatch({ action: 'start_complete_security', }); From 71fa3222fef38715584c877c7e499e2f98941ded Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 Jan 2020 22:11:22 +0000 Subject: [PATCH 041/109] Fix component index import Co-Authored-By: Travis Ralston --- src/components/structures/auth/CompleteSecurity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 0bc41a8fbb..77f7fe26e4 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -import sdk from '../../../index'; +import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { accessSecretStorage } from '../../../CrossSigningManager'; From 3afa2b1d5b491d09df3755ad0cf003c8d1a5ebf3 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 15 Jan 2020 21:19:52 +0000 Subject: [PATCH 042/109] Translated using Weblate (Hungarian) Currently translated at 100.0% (2018 of 2018 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 | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 0dcea60fda..a59b4fd658 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2039,5 +2039,23 @@ "Failed to find the following users": "Az alábbi felhasználók nem találhatók", "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Az alábbi felhasználók nem léteznek vagy hibásan vannak megadva és nem lehet őket meghívni: %(csvNames)s", "Show a presence dot next to DMs in the room list": "Jelenlét pötty mutatása a Közvetlen beszélgetések mellett a szobák listájában", - "Lock": "Zár" + "Lock": "Zár", + "a few seconds ago": "néhány másodperce", + "about a minute ago": "percekkel ezelőtt", + "%(num)s minutes ago": "%(num)s perccel ezelőtt", + "about an hour ago": "egy órája", + "%(num)s hours ago": "%(num)s órával ezelőtt", + "about a day ago": "egy napja", + "%(num)s days ago": "%(num)s nappal ezelőtt", + "a few seconds from now": "másodpercek múlva", + "about a minute from now": "percek múlva", + "%(num)s minutes from now": "%(num)s perc múlva", + "about an hour from now": "egy óra múlva", + "%(num)s hours from now": "%(num)s óra múlva", + "about a day from now": "egy nap múlva", + "%(num)s days from now": "%(num)s nap múlva", + "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.": "A Kulcs Mentés a fiókhoz igen de ehhez a munkamenethez nincs beállítva. A Biztonsági tároló beállításához állítsd vissza a kulcs mentést.", + "Restore": "Visszaállí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": "A Biztonsági Tároló a meglévő kulcs mentés adatai alapján lesz beállítva. A biztonsági tárolóhoz tartozó jelmondat és a visszaállítási kulcs azonos lesz ahogy azok a kulcs mentéshez voltak", + "Restore your Key Backup": "Kulcs Mentés visszaállítása" } From ed9bce21f5ec22f70ce2509eb49aeeb240475eb6 Mon Sep 17 00:00:00 2001 From: Alexey Murz Korepov Date: Wed, 15 Jan 2020 20:21:16 +0000 Subject: [PATCH 043/109] Translated using Weblate (Russian) Currently translated at 89.1% (1798 of 2018 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index aeab97caf3..608c7a3328 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -1440,7 +1440,7 @@ "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.": "Ранее вы использовали Riot на %(host)s с отложенной загрузкой участников. В этой версии отложенная загрузка отключена. Поскольку локальный кеш не совместим между этими двумя настройками, Riot необходимо повторно синхронизировать вашу учётную запись.", "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.": "Если другая версия Riot все еще открыта на другой вкладке, закройте ее, так как использование Riot на том же хосте с включенной и отключенной ленивой загрузкой одновременно вызовет проблемы.", "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 теперь использует в 3-5 раз меньше памяти, загружая информацию только о других пользователях, когда это необходимо. Пожалуйста, подождите, пока мы ресинхронизируемся с сервером!", - "I don't want my encrypted messages": "Мне не нужны мои зашифрованные сообщения", + "I don't want my encrypted messages": "Продолжить выход, мне не нужны мои зашифрованные сообщения", "Manually export keys": "Ручной экспорт ключей", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Если вы столкнулись с какими-либо ошибками или хотите оставить отзыв, которым хотите поделиться, сообщите нам об этом на 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.": "Чтобы избежать повторяющихся проблем, сначала просмотрите существующие проблемы (и добавьте +1), либо создайте новую проблему , если вы не можете ее найти.", From be3ef2b50e2f0b754f7effd67acd2ebc89baf55f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 15 Jan 2020 19:08:14 -0700 Subject: [PATCH 044/109] Remove all text when cutting in the composer The previous function did in fact remove the elements, but left the model thinking there was a zero-length string. This approach deletes the text from the model and the DOM, resulting in the placeholder being shown when cutting all the text. Part of https://github.com/vector-im/riot-web/issues/11378 --- src/components/views/rooms/BasicMessageComposer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index d9604cf030..c605953473 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -209,8 +209,9 @@ export default class BasicMessageEditor extends React.Component { const selectedParts = range.parts.map(p => p.serialize()); event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts)); if (type === "cut") { - selection.deleteFromDocument(); - range.replace([]); + // Remove the text from the composer + const {caret} = getCaretOffsetAndText(this._editorRef, selection); + this.props.model.update("", event.inputType, caret); } event.preventDefault(); } From b137cd21d3832213ba55ad0c8d74c466f67e96b3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 15 Jan 2020 21:04:18 -0700 Subject: [PATCH 045/109] Replace the selected range instead of force-setting it This gives people the option of cutting parts or all of their message. --- src/components/views/rooms/BasicMessageComposer.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index c605953473..0dd9d16e51 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -209,9 +209,8 @@ export default class BasicMessageEditor extends React.Component { const selectedParts = range.parts.map(p => p.serialize()); event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts)); if (type === "cut") { - // Remove the text from the composer - const {caret} = getCaretOffsetAndText(this._editorRef, selection); - this.props.model.update("", event.inputType, caret); + // Remove the text, updating the model as appropriate + replaceRangeAndMoveCaret(range, []); } event.preventDefault(); } From 1b4ab856c9b9fdfe79a2e97685fe6816db68bf9a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 15 Jan 2020 21:05:00 -0700 Subject: [PATCH 046/109] Assume the position is at the end when the offset has no last part We get an NPE when the user cuts their entire message, and this fixes it. --- src/editor/position.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/position.js b/src/editor/position.js index 4693f62999..726377ef48 100644 --- a/src/editor/position.js +++ b/src/editor/position.js @@ -117,7 +117,7 @@ export default class DocumentPosition { } offset += this.offset; const lastPart = model.parts[this.index]; - const atEnd = offset >= lastPart.text.length; + const atEnd = !lastPart || offset >= lastPart.text.length; // if no last part, we're at the end return new DocumentOffset(offset, atEnd); } From ff05041a5a240410460ce3176fd07351679dccb6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Wed, 15 Jan 2020 13:57:29 +0000 Subject: [PATCH 047/109] Update room shield icons to represent encrypted but unverified chats in less alarmed tones --- src/components/structures/RoomView.js | 58 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a717f485f0..3adcd22d87 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -172,6 +172,7 @@ module.exports = createReactClass({ MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus); MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); + MatrixClientPeg.get().on("userTrustStatusChanged", this.onUserVerificationChanged); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(true); @@ -491,6 +492,7 @@ module.exports = createReactClass({ MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus); MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); + MatrixClientPeg.get().removeListener("userTrustStatusChanged", this.onUserVerificationChanged); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -761,6 +763,14 @@ module.exports = createReactClass({ this._updateE2EStatus(room); }, + onUserVerificationChanged: function(userId, _trustStatus) { + const room = this.state.room; + if (!room.currentState.getMember(userId)) { + return; + } + this._updateE2EStatus(room); + }, + _updateE2EStatus: async function(room) { const cli = MatrixClientPeg.get(); if (!cli.isRoomEncrypted(room.roomId)) { @@ -784,29 +794,57 @@ module.exports = createReactClass({ return; } const e2eMembers = await room.getEncryptionTargetMembers(); + + /* + Ensure we trust our own signing key, ie, nobody's used our credentials to + replace it and sign all our devices + */ + if (!cli.checkUserTrust(cli.getUserId())) { + this.setState({ + e2eStatus: "warning", + }); + debuglog("e2e status set to warning due to not trusting our own signing key"); + return; + } + + /* + Gather verification state of every user in the room. + If _any_ user is verified then _every_ user must be verified, or we'll bail. + Note we don't count our own user so that the all/any check behaves properly. + */ + const verificationState = e2eMembers.map(({userId}) => userId) + .filter((userId) => userId !== cli.getUserId()) + .map((userId) => cli.checkUserTrust(userId).isCrossSigningVerified()); + if (verificationState.includes(true) && verificationState.includes(false)) { + this.setState({ + e2eStatus: "warning", + }); + debuglog("e2e status set to warning as some, but not all, users are verified"); + return; + } + + /* + Whether we verify or not, a user having an untrusted device requires warnings. + Check every user's devices, including ourselves. + */ for (const member of e2eMembers) { const { userId } = member; - const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified(); - if (!userVerified) { - this.setState({ - e2eStatus: "warning", - }); - return; - } const devices = await cli.getStoredDevicesForUser(userId); - const allDevicesVerified = devices.every(device => { - const { deviceId } = device; + const allDevicesVerified = devices.every(({deviceId}) => { return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); }); if (!allDevicesVerified) { this.setState({ e2eStatus: "warning", }); + debuglog("e2e status set to warning as not all users trust all of their devices." + + " Aborted on user", userId); return; } } + this.setState({ - e2eStatus: "verified", + e2eStatus: verificationState.includes(true) ? "verified" : "normal", }); }, From 5926e277c485d7833c6b75b52ccdbfc33db6034e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 Jan 2020 11:52:02 +0000 Subject: [PATCH 048/109] Avoid logged in event race --- src/components/structures/MatrixChat.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index edc0501086..c59c44ebd8 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1820,12 +1820,9 @@ export default createReactClass({ }, async onUserCompletedLoginFlow(credentials) { - // Create and start the client in the background - Lifecycle.setLoggedIn(credentials); - // Wait for the client to be logged in (but not started) // which is enough to ask the server about account data. - await new Promise(resolve => { + const loggedIn = new Promise(resolve => { const actionHandlerRef = dis.register(payload => { if (payload.action !== "on_logged_in") { return; @@ -1835,6 +1832,10 @@ export default createReactClass({ }); }); + // Create and start the client in the background + Lifecycle.setLoggedIn(credentials); + await loggedIn; + const cli = MatrixClientPeg.get(); // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` // because the client hasn't been started yet. From 0dfd58c7844eb13a599fd9a4f1a43fa0d153ed6e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 Jan 2020 11:54:36 +0000 Subject: [PATCH 049/109] Compute download file icon immediately Build process changes may have changed the load order, so this tintable is now registered too late (after the theme is set). Fixes https://github.com/vector-im/riot-web/issues/11881 --- src/Tinter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tinter.js b/src/Tinter.js index de9ae94097..24a4d25a00 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -143,10 +143,14 @@ class Tinter { * over time then the best bet is to register a single callback for the * entire set. * + * To ensure the tintable work happens at least once, it is also called as + * part of registration. + * * @param {Function} tintable Function to call when the tint changes. */ registerTintable(tintable) { this.tintables.push(tintable); + tintable(); } getKeyRgb() { From dab31d724dcc45cb3dfed87d8eed442e6526b147 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 Jan 2020 14:01:15 +0000 Subject: [PATCH 050/109] Support uri option in request mock --- __mocks__/browser-request.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 45f83a1763..8038262091 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,14 +1,15 @@ const en = require("../src/i18n/strings/en_EN"); module.exports = jest.fn((opts, cb) => { - if (opts.url.endsWith("languages.json")) { + const url = opts.url || opts.uri; + if (url && url.endsWith("languages.json")) { cb(undefined, {status: 200}, JSON.stringify({ "en": { "fileName": "en_EN.json", "label": "English", }, })); - } else if (opts.url.endsWith("en_EN.json")) { + } else if (url && url.endsWith("en_EN.json")) { cb(undefined, {status: 200}, JSON.stringify(en)); } else { cb(undefined, {status: 404}, ""); From 0ef362a79317c2a1dc77f987816b6fddef32e4c7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 Jan 2020 14:06:54 +0000 Subject: [PATCH 051/109] Request mock should send truthy for errors --- __mocks__/browser-request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 8038262091..7d231fb9db 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -12,6 +12,6 @@ module.exports = jest.fn((opts, cb) => { } else if (url && url.endsWith("en_EN.json")) { cb(undefined, {status: 200}, JSON.stringify(en)); } else { - cb(undefined, {status: 404}, ""); + cb(true, {status: 404}, ""); } }); From 82c5349c4ed1cf516985f5d5de693d9caf4a7fa6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 16 Jan 2020 16:31:50 +0000 Subject: [PATCH 052/109] Updated to properly handle logic --- src/components/structures/RoomView.js | 47 +++++++++++---------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 1843a7b64a..3cdc308758 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -792,31 +792,25 @@ export default createReactClass({ e2eStatus: hasUnverifiedDevices ? "warning" : "verified", }); }); + debuglog("e2e check is warning/verified only as cross-signing is off"); return; } + + /* At this point, the user has encryption on and cross-signing on */ const e2eMembers = await room.getEncryptionTargetMembers(); - - /* - Ensure we trust our own signing key, ie, nobody's used our credentials to - replace it and sign all our devices - */ - if (!cli.checkUserTrust(cli.getUserId())) { - this.setState({ - e2eStatus: "warning", - }); - debuglog("e2e status set to warning due to not trusting our own signing key"); - return; - } - - /* - Gather verification state of every user in the room. - If _any_ user is verified then _every_ user must be verified, or we'll bail. - Note we don't count our own user so that the all/any check behaves properly. - */ - const verificationState = e2eMembers.map(({userId}) => userId) + const verified = []; + const unverified = []; + e2eMembers.map(({userId}) => userId) .filter((userId) => userId !== cli.getUserId()) - .map((userId) => cli.checkUserTrust(userId).isCrossSigningVerified()); - if (verificationState.includes(true) && verificationState.includes(false)) { + .forEach((userId) => { + (cli.checkUserTrust(userId).isCrossSigningVerified() ? + verified : unverified).push(userId) + }); + + debuglog("e2e verified", verified, "unverified", unverified); + + /* If we verify any users in this room, expect to verify every user in the room */ + if (verified.length > 0 && unverified.length > 0) { this.setState({ e2eStatus: "warning", }); @@ -824,12 +818,9 @@ export default createReactClass({ return; } - /* - Whether we verify or not, a user having an untrusted device requires warnings. - Check every user's devices, including ourselves. - */ - for (const member of e2eMembers) { - const { userId } = member; + /* At this point, either `verified` or `unverified` is empty, or both */ + /* Check all verified user devices. We don't care if everyone's unverified anyway. */ + for (const userId of [...verified, cli.getUserId()]) { const devices = await cli.getStoredDevicesForUser(userId); const allDevicesVerified = devices.every(({deviceId}) => { return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); @@ -845,7 +836,7 @@ export default createReactClass({ } this.setState({ - e2eStatus: verificationState.includes(true) ? "verified" : "normal", + e2eStatus: unverified.length === 0 ? "verified" : "normal", }); }, From 869fd0b2c91a0a225a2b1dda5559f2d288c14216 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 09:40:11 -0700 Subject: [PATCH 053/109] Fix not being able to open profiles from the timeline MemberAvatar was referencing the wrong dispatcher (it was imported as `dis`, like everywhere else, not `dispatcher`). Fixes https://github.com/vector-im/riot-web/issues/11887 --- src/components/views/avatars/MemberAvatar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/avatars/MemberAvatar.js b/src/components/views/avatars/MemberAvatar.js index 7cd14f67d4..a07a184aa1 100644 --- a/src/components/views/avatars/MemberAvatar.js +++ b/src/components/views/avatars/MemberAvatar.js @@ -83,7 +83,7 @@ export default createReactClass({ if (viewUserOnClick) { onClick = () => { - dispatcher.dispatch({ + dis.dispatch({ action: 'view_user', member: this.props.member, }); From eb6c7ec275e4503771aae6c84af6b579cbd7921c Mon Sep 17 00:00:00 2001 From: Samu Voutilainen Date: Thu, 16 Jan 2020 13:13:25 +0000 Subject: [PATCH 054/109] Translated using Weblate (Finnish) Currently translated at 98.5% (1988 of 2018 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fi/ --- src/i18n/strings/fi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index e53afb2598..38d7556f52 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -2025,5 +2025,6 @@ "Recent Conversations": "Viimeaikaiset keskustelut", "Direct Messages": "Yksityisviestit", "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "Jos et löydä jotakuta, kysy hänen käyttäjätunnusta, tai anna oma käyttäjätunnuksesi (%(userId)s) tai linkin profiiliisi hänelle.", - "Go": "Mene" + "Go": "Mene", + "Lock": "Lukko" } From 086b3f811e9491ebd604cfc33d0f703ee9ce7680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 16 Jan 2020 08:12:23 +0000 Subject: [PATCH 055/109] Translated using Weblate (French) Currently translated at 100.0% (2018 of 2018 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 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index c2863d713f..b18bf44d73 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2042,5 +2042,19 @@ "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.": "La sauvegarde de clés est activée pour votre compte mais n’a pas été configurée pour cette session. Pour configurer le coffre secret, restaurez votre sauvegarde de clés.", "Restore": "Restaurer", "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 paramètres existants de la 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", - "Restore your Key Backup": "Restaurer votre sauvegarde de clés" + "Restore your Key Backup": "Restaurer votre sauvegarde de clés", + "a few seconds ago": "il y a quelques secondes", + "about a minute ago": "il y a environ une minute", + "%(num)s minutes ago": "il y a %(num)s minutes", + "about an hour ago": "il y a environ une heure", + "%(num)s hours ago": "il y a %(num)s heures", + "about a day ago": "il y a environ un jour", + "%(num)s days ago": "il y a %(num)s jours", + "a few seconds from now": "dans quelques secondes", + "about a minute from now": "dans une minute environ", + "%(num)s minutes from now": "dans %(num)s minutes", + "about an hour from now": "dans une heure environ", + "%(num)s hours from now": "dans %(num)s heures", + "about a day from now": "dans un jour environ", + "%(num)s days from now": "dans %(num)s jours" } From f8d12b11f5f17781cc468311cd417a9c2dd4c474 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 16 Jan 2020 11:57:17 +0000 Subject: [PATCH 056/109] Translated using Weblate (Italian) Currently translated at 100.0% (2018 of 2018 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 | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 22fc083d08..bb35bd6d69 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2032,5 +2032,28 @@ "Show more": "Mostra altro", "Direct Messages": "Messaggi diretti", "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.": "Se non riesci a trovare qualcuno, chiedigli il nome utente o condividi il tuo (%(userId)s) o il link al profilo.", - "Go": "Vai" + "Go": "Vai", + "a few seconds ago": "pochi secondi fa", + "about a minute ago": "circa un minuto fa", + "%(num)s minutes ago": "%(num)s minuti fa", + "about an hour ago": "circa un'ora fa", + "%(num)s hours ago": "%(num)s ore fa", + "about a day ago": "circa un giorno fa", + "%(num)s days ago": "%(num)s giorni fa", + "a few seconds from now": "pochi secondi da adesso", + "about a minute from now": "circa un minuto da adesso", + "%(num)s minutes from now": "%(num)s minuti da adesso", + "about an hour from now": "circa un'ora da adesso", + "%(num)s hours from now": "%(num)s ore da adesso", + "about a day from now": "circa un giorno da adesso", + "%(num)s days from now": "%(num)s giorni da adesso", + "Show a presence dot next to DMs in the room list": "Mostra un punto di presenza accanto ai mess. diretti nell'elenco stanza", + "Lock": "Lucchetto", + "Bootstrap cross-signing and secret storage": "Inizializza firma incrociata e archivio segreto", + "Failed to find the following users": "Impossibile trovare i seguenti utenti", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "I seguenti utenti potrebbero non esistere o non sono validi, perciò non possono essere invitati: %(csvNames)s", + "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" } From 0f61aa57ffae4c3e813390b43aaa93d639b8cc6f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2020 17:00:04 +0000 Subject: [PATCH 057/109] Apply suggestions from code review Co-Authored-By: Travis Ralston --- src/components/views/right_panel/GroupHeaderButtons.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index 1602f47347..f164b6c578 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -23,7 +23,6 @@ import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HEADER_KIND_GROUP} from './HeaderButtons'; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; -import RightPanelStore from "../../../stores/RightPanelStore"; const GROUP_PHASES = [ RIGHT_PANEL_PHASES.GroupMemberInfo, @@ -69,7 +68,7 @@ export default class GroupHeaderButtons extends HeaderButtons { _onMembersClicked() { if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) { // send the active phase to trigger a toggle - this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo, RightPanelStore.getSharedInstance().roomPanelPhaseParams); + this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo); } else { // This toggles for us, if needed this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); From 83b15054015c06b7201b11c22fda4592c34b24e9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Jan 2020 20:23:47 +0000 Subject: [PATCH 058/109] Add a ToastStore To store toast. Rather than them being stored in the state of the ToastContainer component, they now have a dedicated store. This mostly fixes problems involving showing toasts when the app loaded because we would otherwise have a race condition where something tries to show a toast before the ToastContainer is mounted. --- src/components/structures/MatrixChat.js | 16 +++--- src/components/structures/ToastContainer.js | 25 +++------ .../views/toasts/VerificationRequestToast.js | 9 ++-- src/stores/ToastStore.js | 52 +++++++++++++++++++ 4 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 src/stores/ToastStore.js diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index c59c44ebd8..978743ca87 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -64,6 +64,7 @@ import { ThemeWatcher } from "../../theme"; import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer } from "../../utils/promise"; import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver'; +import ToastStore from "../../stores/ToastStore"; /** constants for MatrixChat.state.view */ export const VIEWS = { @@ -1458,15 +1459,12 @@ export default createReactClass({ } if (!requestObserver || requestObserver.pending) { - dis.dispatch({ - action: "show_toast", - toast: { - key: request.event.getId(), - title: _t("Verification Request"), - icon: "verification", - props: {request, requestObserver}, - component: sdk.getComponent("toasts.VerificationRequestToast"), - }, + ToastStore.sharedInstance().addOrReplaceToast({ + key: 'verifreq_' + request.event.getId(), + title: _t("Verification Request"), + icon: "verification", + props: {request, requestObserver}, + component: sdk.getComponent("toasts.VerificationRequestToast"), }); } }); diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index a8dca35747..bc74133433 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ limitations under the License. */ import * as React from "react"; -import dis from "../../dispatcher"; import { _t } from '../../languageHandler'; +import ToastStore from "../../stores/ToastStore"; import classNames from "classnames"; export default class ToastContainer extends React.Component { @@ -26,26 +26,15 @@ export default class ToastContainer extends React.Component { } componentDidMount() { - this._dispatcherRef = dis.register(this.onAction); + ToastStore.sharedInstance().on('update', this._onToastStoreUpdate); } componentWillUnmount() { - dis.unregister(this._dispatcherRef); + ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate); } - onAction = (payload) => { - if (payload.action === "show_toast") { - this._addToast(payload.toast); - } - }; - - _addToast(toast) { - this.setState({toasts: this.state.toasts.concat(toast)}); - } - - dismissTopToast = () => { - const [, ...remaining] = this.state.toasts; - this.setState({toasts: remaining}); + _onToastStoreUpdate = () => { + this.setState({toasts: ToastStore.sharedInstance().getToasts()}); }; render() { @@ -62,8 +51,8 @@ export default class ToastContainer extends React.Component { const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null; const toastProps = Object.assign({}, props, { - dismiss: this.dismissTopToast, key, + toastKey: key, }); toast = (

{title}{countIndicator}

diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 6d53c23743..18db5eae66 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -23,6 +23,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import dis from "../../../dispatcher"; +import ToastStore from "../../../stores/ToastStore"; export default class VerificationRequestToast extends React.PureComponent { constructor(props) { @@ -63,12 +64,12 @@ export default class VerificationRequestToast extends React.PureComponent { _checkRequestIsPending = () => { if (!this.props.requestObserver.pending) { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); } } cancel = () => { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); try { this.props.request.cancel(); } catch (err) { @@ -77,7 +78,7 @@ export default class VerificationRequestToast extends React.PureComponent { } accept = () => { - this.props.dismiss(); + ToastStore.sharedInstance().dismissToast(this.props.toastKey); const {event} = this.props.request; // no room id for to_device requests if (event.getRoomId()) { @@ -119,7 +120,7 @@ export default class VerificationRequestToast extends React.PureComponent { } VerificationRequestToast.propTypes = { - dismiss: PropTypes.func.isRequired, request: PropTypes.object.isRequired, requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver), + toastKey: PropTypes.string.isRequired, }; diff --git a/src/stores/ToastStore.js b/src/stores/ToastStore.js new file mode 100644 index 0000000000..f6cc30db67 --- /dev/null +++ b/src/stores/ToastStore.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 EventEmitter from 'events'; + +/** + * Holds the active toasts + */ +export default class ToastStore extends EventEmitter { + static sharedInstance() { + if (!global.mx_ToastStore) global.mx_ToastStore = new ToastStore(); + return global.mx_ToastStore; + } + + constructor() { + super(); + this._dispatcherRef = null; + this._toasts = []; + } + + addOrReplaceToast(newToast) { + const oldIndex = this._toasts.findIndex(t => t.key === newToast.key); + if (oldIndex === -1) { + this._toasts.push(newToast); + } else { + this._toasts[oldIndex] = newToast; + } + this.emit('update'); + } + + dismissToast(key) { + this._toasts = this._toasts.filter(t => t.key !== key); + this.emit('update'); + } + + getToasts() { + return this._toasts; + } +} From 45dea0152fa9bd1fdb4fe6b48f2a53b524488ee2 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 16 Jan 2020 20:29:23 +0000 Subject: [PATCH 059/109] Translated using Weblate (Hungarian) Currently translated at 100.0% (2026 of 2026 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index a59b4fd658..1803ac7601 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2057,5 +2057,14 @@ "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.": "A Kulcs Mentés a fiókhoz igen de ehhez a munkamenethez nincs beállítva. A Biztonsági tároló beállításához állítsd vissza a kulcs mentést.", "Restore": "Visszaállí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": "A Biztonsági Tároló a meglévő kulcs mentés adatai alapján lesz beállítva. A biztonsági tárolóhoz tartozó jelmondat és a visszaállítási kulcs azonos lesz ahogy azok a kulcs mentéshez voltak", - "Restore your Key Backup": "Kulcs Mentés visszaállítása" + "Restore your Key Backup": "Kulcs Mentés visszaállítása", + "Complete security": "Biztonság beállítása", + "Verify this session to grant it access to encrypted messages.": "A titkosított üzenetekhez való hozzáféréshez hitelesítsd ezt a munkamenetet.", + "Start": "Indít", + "Session verified": "Munkamenet hitelesítve", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Ez a munkameneted hitelesítve van. A titkosított üzenetekhez hozzáférése van és más felhasználók megbízhatónak látják.", + "Done": "Kész", + "Without completing security on this device, it won’t have access to encrypted messages.": "Az eszköz biztonságának beállítása nélkül nem férhet hozzá a titkosított üzenetekhez.", + "Go Back": "Vissza", + "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.": "A Biztonsági Tároló a kulcs mentés adatainak felhasználásával lesz beállítva. A biztonsági tároló jelmondata és a visszaállítási kulcs ugyanaz lesz mint a kulcs mentéshez használt." } From fa0d7127735e5883440ac317b1886c289a95dfb6 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 16 Jan 2020 19:42:11 +0000 Subject: [PATCH 060/109] Translated using Weblate (Turkish) Currently translated at 65.9% (1336 of 2026 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 | 53 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 659c973d35..af4bab9689 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1297,5 +1297,56 @@ "Copied!": "Kopyalandı!", "Failed to copy": "Kopyalama başarısız", "edited": "düzenlendi", - "Message removed by %(userId)s": "Mesaj %(userId)s tarafından silindi" + "Message removed by %(userId)s": "Mesaj %(userId)s tarafından silindi", + "You are still sharing your personal data on the identity server .": "Kimlik sunucusu üzerinde hala kişisel veri paylaşımı yapıyorsunuz.", + "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Kimlik sunucusundan bağlantıyı kesmeden önce telefon numaranızı ve e-posta adreslerinizi silmenizi tavsiye ederiz.", + "Set a new account password...": "Yeni bir hesap şifresi belirle...", + "Deactivating your account is a permanent action - be careful!": "Hesabınızı pasifleştirmek bir kalıcı eylemdir - dikkat edin!", + "Deactivate account": "Hesabı pasifleştir", + "For help with using Riot, click here.": "Riot kullanarak yardım etmek için, buraya tıklayın.", + "Chat with Riot Bot": "Riot Bot ile Sohbet Et", + "Submit debug logs": "Hata ayıklama kayıtlarını gönder", + "Something went wrong. Please try again or view your console for hints.": "Bir şeyler hatalı gitti. Lütfen yeniden deneyin veya ipuçları için konsolunuza bakın.", + "Please verify the room ID or alias and try again.": "Lütfen odanın ID si veya lakabı doğrulayın ve yeniden deneyin.", + "Please try again or view your console for hints.": "Lütfen yeniden deneyin veya ipuçları için konsolunuza bakın.", + "None": "Yok", + "Ban list rules - %(roomName)s": "Yasak Liste Kuralları - %(roomName)s", + "You have not ignored anyone.": "Kimseyi yok saymamışsınız.", + "You are currently ignoring:": "Halihazırda yoksaydıklarınız:", + "Unsubscribe": "Abonelikten Çık", + "You are currently subscribed to:": "Halizhazırdaki abonelikleriniz:", + "Ignored users": "Yoksayılan kullanıcılar", + "Personal ban list": "Kişisel yasak listesi", + "Server or user ID to ignore": "Yoksaymak için sunucu veya kullanıcı ID", + "eg: @bot:* or example.org": "örn: @bot:* veya example.org", + "Ignore": "Yoksay", + "Subscribed lists": "Abone olunmuş listeler", + "If this isn't what you want, please use a different tool to ignore users.": "Eğer istediğiniz bu değilse, kullanıcıları yoksaymak için lütfen farklı bir araç kullanın.", + "Room ID or alias of ban list": "Yasak listesinin Oda ID veya lakabı", + "Subscribe": "Abone ol", + "Always show the window menu bar": "Pencerenin menü çubuğunu her zaman göster", + "Bulk options": "Toplu işlem seçenekleri", + "Accept all %(invitedRooms)s invites": "Bütün %(invitedRooms)s davetlerini kabul et", + "Request media permissions": "Medya izinleri talebi", + "Upgrade this room to the recommended room version": "Bu odayı önerilen oda sürümüne yükselt", + "View older messages in %(roomName)s.": "%(roomName)s odasında daha eski mesajları göster.", + "This bridge is managed by .": "Bu köprü tarafından yönetiliyor.", + "Connected via %(protocolName)s": "%(protocolName)s yoluyla bağlandı", + "Bridge Info": "Köprü Bilgisi", + "Set a new custom sound": "Özel bir ses ayarla", + "Change main address for the room": "Oda için ana adresi değiştir", + "Error changing power level requirement": "Güç düzey gereksinimi değiştirmede hata", + "Error changing power level": "Güç düzeyi değiştirme hatası", + "Send %(eventType)s events": "%(eventType)s olaylarını gönder", + "To link to this room, please add an alias.": "Bu odaya bağlanmak için, lütfen bir lakap ekle.", + "This user has not verified all of their devices.": "Bu kullanıcı tüm cihazlarda doğrulanmadı.", + "You have verified this user. This user has verified all of their devices.": "Bu kullanıcıyı doğruladınız. Bu kullanıcı tüm cihazlarında doğrulandı.", + "This event could not be displayed": "Bu olay görüntülenemedi", + "Demote yourself?": "Kendinin rütbeni düşür?", + "Demote": "Rütbe Düşür", + "Mention": "Bahsetme", + "Remove recent messages": "Son mesajları sil", + "The conversation continues here.": "Sohbet buradan devam ediyor.", + "You can only join it with a working invite.": "Sadece çalışan bir davet ile katılınabilir.", + "Try to join anyway": "Katılmak için yinede deneyin" } From 7da9e0582f3887f3dab030d5279c200ce97f1ce8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 13:42:34 -0700 Subject: [PATCH 061/109] Rename DMInviteDialog to be a generic Invite Dialog --- res/css/_components.scss | 2 +- ...DMInviteDialog.scss => _InviteDialog.scss} | 40 ++++++++--------- src/RoomInvite.js | 4 +- .../{DMInviteDialog.js => InviteDialog.js} | 44 +++++++++---------- 4 files changed, 45 insertions(+), 45 deletions(-) rename res/css/views/dialogs/{_DMInviteDialog.scss => _InviteDialog.scss} (86%) rename src/components/views/dialogs/{DMInviteDialog.js => InviteDialog.js} (94%) diff --git a/res/css/_components.scss b/res/css/_components.scss index a9a114a4cf..60f749de9c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -57,13 +57,13 @@ @import "./views/dialogs/_ConfirmUserActionDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; -@import "./views/dialogs/_DMInviteDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DeviceVerifyDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; +@import "./views/dialogs/_InviteDialog.scss"; @import "./views/dialogs/_MessageEditHistoryDialog.scss"; @import "./views/dialogs/_RoomSettingsDialog.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss"; diff --git a/res/css/views/dialogs/_DMInviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss similarity index 86% rename from res/css/views/dialogs/_DMInviteDialog.scss rename to res/css/views/dialogs/_InviteDialog.scss index 5d58f3ae8b..d0b53b7766 100644 --- a/res/css/views/dialogs/_DMInviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_DMInviteDialog_addressBar { +.mx_InviteDialog_addressBar { display: flex; flex-direction: row; - .mx_DMInviteDialog_editor { + .mx_InviteDialog_editor { flex: 1; width: 100%; // Needed to make the Field inside grow background-color: $user-tile-hover-bg-color; @@ -28,7 +28,7 @@ limitations under the License. overflow-x: hidden; overflow-y: auto; - .mx_DMInviteDialog_userTile { + .mx_InviteDialog_userTile { display: inline-block; float: left; position: relative; @@ -61,14 +61,14 @@ limitations under the License. } } - .mx_DMInviteDialog_goButton { + .mx_InviteDialog_goButton { width: 48px; margin-left: 10px; height: 25px; line-height: 25px; } - .mx_DMInviteDialog_buttonAndSpinner { + .mx_InviteDialog_buttonAndSpinner { .mx_Spinner { // Width and height are required to trick the layout engine. width: 20px; @@ -80,7 +80,7 @@ limitations under the License. } } -.mx_DMInviteDialog_section { +.mx_InviteDialog_section { padding-bottom: 10px; h3 { @@ -91,7 +91,7 @@ limitations under the License. } } -.mx_DMInviteDialog_roomTile { +.mx_InviteDialog_roomTile { cursor: pointer; padding: 5px 10px; @@ -104,7 +104,7 @@ limitations under the License. vertical-align: middle; } - .mx_DMInviteDialog_roomTile_avatarStack { + .mx_InviteDialog_roomTile_avatarStack { display: inline-block; position: relative; width: 36px; @@ -117,7 +117,7 @@ limitations under the License. } } - .mx_DMInviteDialog_roomTile_selected { + .mx_InviteDialog_roomTile_selected { width: 36px; height: 36px; border-radius: 36px; @@ -141,20 +141,20 @@ limitations under the License. } } - .mx_DMInviteDialog_roomTile_name { + .mx_InviteDialog_roomTile_name { font-weight: 600; font-size: 14px; color: $primary-fg-color; margin-left: 7px; } - .mx_DMInviteDialog_roomTile_userId { + .mx_InviteDialog_roomTile_userId { font-size: 12px; color: $muted-fg-color; margin-left: 7px; } - .mx_DMInviteDialog_roomTile_time { + .mx_InviteDialog_roomTile_time { text-align: right; font-size: 12px; color: $muted-fg-color; @@ -162,16 +162,16 @@ limitations under the License. line-height: 36px; // Height of the avatar to keep the time vertically aligned } - .mx_DMInviteDialog_roomTile_highlight { + .mx_InviteDialog_roomTile_highlight { font-weight: 900; } } // Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog. -.mx_DMInviteDialog_userTile { +.mx_InviteDialog_userTile { margin-right: 8px; - .mx_DMInviteDialog_userTile_pill { + .mx_InviteDialog_userTile_pill { background-color: $username-variant1-color; border-radius: 12px; display: inline-block; @@ -181,27 +181,27 @@ limitations under the License. padding-right: 8px; color: #ffffff; // this is fine without a var because it's for both themes - .mx_DMInviteDialog_userTile_avatar { + .mx_InviteDialog_userTile_avatar { border-radius: 20px; position: relative; left: -5px; top: 2px; } - img.mx_DMInviteDialog_userTile_avatar { + img.mx_InviteDialog_userTile_avatar { vertical-align: top; } - .mx_DMInviteDialog_userTile_name { + .mx_InviteDialog_userTile_name { vertical-align: top; } - .mx_DMInviteDialog_userTile_threepidAvatar { + .mx_InviteDialog_userTile_threepidAvatar { background-color: #ffffff; // this is fine without a var because it's for both themes } } - .mx_DMInviteDialog_userTile_remove { + .mx_InviteDialog_userTile_remove { display: inline-block; margin-left: 4px; } diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 8b7324d4f5..aaddd58d0b 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -44,9 +44,9 @@ 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 DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog"); + const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); Modal.createTrackedDialog( - 'Start DM', '', DMInviteDialog, {}, + 'Start DM', '', InviteDialog, {}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); return; diff --git a/src/components/views/dialogs/DMInviteDialog.js b/src/components/views/dialogs/InviteDialog.js similarity index 94% rename from src/components/views/dialogs/DMInviteDialog.js rename to src/components/views/dialogs/InviteDialog.js index 2a5c896a75..6b8e532854 100644 --- a/src/components/views/dialogs/DMInviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -140,11 +140,11 @@ class DMUserTile extends React.PureComponent { const avatarSize = 20; const avatar = this.props.member.isEmail ? : ; return ( - - + + {avatar} - {this.props.member.name} + {this.props.member.name} {_t('Remove')} @@ -211,7 +211,7 @@ class DMRoomTile extends React.PureComponent { // Highlight the word the user entered const substr = str.substring(i, filterStr.length + i); - result.push({substr}); + result.push({substr}); i += substr.length; } @@ -229,7 +229,7 @@ class DMRoomTile extends React.PureComponent { let timestamp = null; if (this.props.lastActiveTs) { const humanTs = humanizeTime(this.props.lastActiveTs); - timestamp = {humanTs}; + timestamp = {humanTs}; } const avatarSize = 36; @@ -249,30 +249,30 @@ class DMRoomTile extends React.PureComponent { let checkmark = null; if (this.props.isSelected) { // To reduce flickering we put the 'selected' room tile above the real avatar - checkmark =
; + checkmark =
; } // To reduce flickering we put the checkmark on top of the actual avatar (prevents // the browser from reloading the image source when the avatar remounts). const stackedAvatar = ( - + {avatar} {checkmark} ); return ( -
+
{stackedAvatar} - {this._highlightName(this.props.member.name)} - {this._highlightName(this.props.member.userId)} + {this._highlightName(this.props.member.name)} + {this._highlightName(this.props.member.userId)} {timestamp}
); } } -export default class DMInviteDialog extends React.PureComponent { +export default class InviteDialog extends React.PureComponent { static propTypes = { // Takes an array of user IDs/emails to invite. onFinished: PropTypes.func.isRequired, @@ -690,7 +690,7 @@ export default class DMInviteDialog extends React.PureComponent { if (sourceMembers.length === 0 && additionalMembers.length === 0) { return ( -
+

{sectionName}

{_t("No results")}

@@ -731,7 +731,7 @@ export default class DMInviteDialog extends React.PureComponent { /> )); return ( -
+

{sectionName}

{tiles} {showMore} @@ -754,7 +754,7 @@ export default class DMInviteDialog extends React.PureComponent { /> ); return ( -
+
{targets} {input}
@@ -808,12 +808,12 @@ export default class DMInviteDialog extends React.PureComponent { const userId = MatrixClientPeg.get().getUserId(); return ( -
+

{_t( "If you can't find someone, ask them for their username, or share your " + @@ -822,13 +822,13 @@ export default class DMInviteDialog extends React.PureComponent { {a: (sub) => {sub}}, )}

-
+
{this._renderEditor()} -
+
{_t("Go")} From 73fc91aa20a76f8b3a8d2b4a13d3407e4084151f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 13:44:59 -0700 Subject: [PATCH 062/109] Rename feature flag for use in both code paths --- src/settings/Settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 2b8c0aef89..eacf63e55d 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -130,7 +130,7 @@ export const SETTINGS = { }, "feature_ftue_dms": { isFeature: true, - displayName: _td("New DM invite dialog (under development)"), + displayName: _td("New invite dialog"), supportedLevels: LEVELS_FEATURE, default: false, }, From f350167408b916db2fd3fefea84e500f7016f993 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 14:40:12 -0700 Subject: [PATCH 063/109] Support using the InviteDialog for both DMs and invites For https://github.com/vector-im/riot-web/issues/11201 --- src/RoomInvite.js | 14 +- src/components/views/dialogs/InviteDialog.js | 127 +++++++++++++++---- src/i18n/strings/en_EN.json | 26 ++-- 3 files changed, 129 insertions(+), 38 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index aaddd58d0b..2eccf69b0f 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017, 2018 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. @@ -26,6 +27,7 @@ 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"; /** * Invites multiple addresses to a room @@ -46,7 +48,7 @@ export function showStartChatInviteDialog() { // 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, {}, + 'Start DM', '', InviteDialog, {kind: KIND_DM}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); return; @@ -72,6 +74,16 @@ export function showStartChatInviteDialog() { } 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, { diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 6b8e532854..7448b1a5a3 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import {_t} from "../../../languageHandler"; import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; +import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import DMRoomMap from "../../../utils/DMRoomMap"; import {RoomMember} from "matrix-js-sdk/src/matrix"; import SdkConfig from "../../../SdkConfig"; @@ -34,7 +34,8 @@ import {humanizeTime} from "../../../utils/humanize"; import createRoom from "../../../createRoom"; import {inviteMultipleToRoom} from "../../../RoomInvite"; -// TODO: [TravisR] Make this generic for all kinds of invites +export const KIND_DM = "dm"; +export const KIND_INVITE = "invite"; const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked @@ -276,13 +277,28 @@ export default class InviteDialog extends React.PureComponent { static propTypes = { // Takes an array of user IDs/emails to invite. onFinished: PropTypes.func.isRequired, + + // The kind of invite being performed. Assumed to be KIND_DM if + // not provided. + kind: PropTypes.string, + + // The room ID this dialog is for. Only required for KIND_INVITE. + roomId: PropTypes.string, + }; + + static defaultProps = { + kind: KIND_DM, }; _debounceTimer: number = null; _editorRef: any = null; - constructor() { - super(); + constructor(props) { + super(props); + + if (props.kind === KIND_INVITE && !props.roomId) { + throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog"); + } this.state = { targets: [], // array of Member objects (see interface above) @@ -390,6 +406,21 @@ export default class InviteDialog extends React.PureComponent { return members.map(m => ({userId: m.member.userId, user: m.member})); } + _shouldAbortAfterInviteError(result): boolean { + const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); + if (failedUsers.length > 0) { + console.log("Failed to invite users: ", result); + this.setState({ + busy: false, + errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", { + csvUsers: failedUsers.join(", "), + }), + }); + return true; // abort + } + return false; + } + _startDm = () => { this.setState({busy: true}); const targetIds = this.state.targets.map(t => t.userId); @@ -417,15 +448,7 @@ export default class InviteDialog extends React.PureComponent { createRoomPromise = createRoom().then(roomId => { return inviteMultipleToRoom(roomId, targetIds); }).then(result => { - const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); - if (failedUsers.length > 0) { - console.log("Failed to invite users: ", result); - this.setState({ - busy: false, - errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", { - csvUsers: failedUsers.join(", "), - }), - }); + if (this._shouldAbortAfterInviteError(result)) { return true; // abort } }); @@ -444,6 +467,33 @@ export default class InviteDialog extends React.PureComponent { }); }; + _inviteUsers = () => { + this.setState({busy: true}); + const targetIds = this.state.targets.map(t => t.userId); + + const room = MatrixClientPeg.get().getRoom(this.props.roomId); + if (!room) { + console.error("Failed to find the room to invite users to"); + this.setState({ + busy: false, + errorText: _t("Something went wrong trying to invite the users."), + }); + return; + } + + inviteMultipleToRoom(this.props.roomId, targetIds).then(result => { + if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too + this.props.onFinished(); + } + }).catch(err => { + console.error(err); + this.setState({ + busy: false, + errorText: _t("We couldn't invite those users. Please check the users you want to invite and try again."), + }); + }); + }; + _cancel = () => { // We do not want the user to close the dialog while an action is in progress if (this.state.busy) return; @@ -658,7 +708,11 @@ export default class InviteDialog extends React.PureComponent { let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); const lastActive = (m) => kind === 'recents' ? m.lastActive : null; - const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); + let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); + + if (this.props.kind === KIND_INVITE) { + sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions"); + } // Mix in the server results if we have any, but only if we're searching. We track the additional // members separately because we want to filter sourceMembers but trust the mixin arrays to have @@ -805,33 +859,54 @@ export default class InviteDialog extends React.PureComponent { spinner = ; } - const userId = MatrixClientPeg.get().getUserId(); + + let title; + let helpText; + let buttonText; + let goButtonFn; + + if (this.props.kind === KIND_DM) { + const userId = MatrixClientPeg.get().getUserId(); + + title = _t("Direct Messages"); + helpText = _t( + "If you can't find someone, ask them for their username, or share your " + + "username (%(userId)s) or profile link.", + {userId}, + {a: (sub) => {sub}}, + ); + buttonText = _t("Go"); + goButtonFn = this._startDm; + } else { // KIND_INVITE + title = _t("Invite to this room"); + helpText = _t( + "If you can't find someone, ask them for their username (e.g. @user:server.com) or " + + "share this room.", {}, + {a: (sub) => {sub}}, + ); + buttonText = _t("Invite"); + goButtonFn = this._inviteUsers; + } + return (
-

- {_t( - "If you can't find someone, ask them for their username, or share your " + - "username (%(userId)s) or profile link.", - {userId}, - {a: (sub) => {sub}}, - )} -

+

{helpText}

{this._renderEditor()}
- {_t("Go")} + {buttonText} {spinner}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b6f61570cd..f8b17db7c5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -372,7 +372,7 @@ "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 DM invite dialog (under development)": "New DM invite dialog (under development)", + "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)", @@ -1438,16 +1438,6 @@ "View Servers in Room": "View Servers in Room", "Toolbox": "Toolbox", "Developer Tools": "Developer Tools", - "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.", - "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", - "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", "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.", @@ -1457,6 +1447,20 @@ "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", From 1a961358f0fe7956cefc246281960c9761f500ae Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 14:40:25 -0700 Subject: [PATCH 064/109] Don't show recents and suggestions for users already in the room --- src/components/views/dialogs/InviteDialog.js | 28 +++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 7448b1a5a3..e176d3b105 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -300,12 +300,24 @@ export default class InviteDialog extends React.PureComponent { throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog"); } + let alreadyInvited = []; + if (props.roomId) { + const room = MatrixClientPeg.get().getRoom(props.roomId); + if (!room) throw new Error("Room ID given to InviteDialog does not look like a room"); + alreadyInvited = [ + ...room.getMembersWithMembership('invite'), + ...room.getMembersWithMembership('join'), + ...room.getMembersWithMembership('ban'), // so we don't try to invite them + ].map(m => m.userId); + } + + this.state = { targets: [], // array of Member objects (see interface above) filterText: "", - recents: this._buildRecents(), + recents: this._buildRecents(alreadyInvited), numRecentsShown: INITIAL_ROOMS_SHOWN, - suggestions: this._buildSuggestions(), + suggestions: this._buildSuggestions(alreadyInvited), numSuggestionsShown: INITIAL_ROOMS_SHOWN, serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions @@ -320,10 +332,13 @@ export default class InviteDialog extends React.PureComponent { this._editorRef = createRef(); } - _buildRecents(): {userId: string, user: RoomMember, lastActive: number} { + _buildRecents(excludedTargetIds: string[]): {userId: string, user: RoomMember, lastActive: number} { const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); 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; + const room = rooms[userId]; const member = room.getMember(userId); if (!member) continue; // just skip people who don't have memberships for some reason @@ -342,7 +357,7 @@ export default class InviteDialog extends React.PureComponent { return recents; } - _buildSuggestions(): {userId: string, user: RoomMember} { + _buildSuggestions(excludedTargetIds: string[]): {userId: string, user: RoomMember} { const maxConsideredMembers = 200; const client = MatrixClientPeg.get(); const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']]; @@ -359,6 +374,11 @@ export default class InviteDialog extends React.PureComponent { const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId)); for (const member of joinedMembers) { + // Filter out user IDs that are already in the room / should be excluded + if (excludedTargetIds.includes(member.userId)) { + continue; + } + if (!members[member.userId]) { members[member.userId] = { member: member, From e42663fc627187cee9a59e65cd8ff1abccb95bf3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 14:45:17 -0700 Subject: [PATCH 065/109] Appease the linter --- src/components/views/dialogs/InviteDialog.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index e176d3b105..1b7a50c084 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -509,7 +509,9 @@ export default class InviteDialog extends React.PureComponent { console.error(err); this.setState({ busy: false, - errorText: _t("We couldn't invite those users. Please check the users you want to invite and try again."), + errorText: _t( + "We couldn't invite those users. Please check the users you want to invite and try again.", + ), }); }); }; From 03448313e6b4f0ad41b329d08d95f1d8188bb84c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 Jan 2020 21:52:33 +0000 Subject: [PATCH 066/109] Fix event handler leak in MemberStatusMessageAvatar A typo led to an event handler leak with the custom status labs feature. A new handler would leak each time you change rooms, which can add up over the course of a long-lived session. --- src/components/views/avatars/MemberStatusMessageAvatar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/avatars/MemberStatusMessageAvatar.js b/src/components/views/avatars/MemberStatusMessageAvatar.js index aaac61ce7d..54f11e8e91 100644 --- a/src/components/views/avatars/MemberStatusMessageAvatar.js +++ b/src/components/views/avatars/MemberStatusMessageAvatar.js @@ -63,7 +63,7 @@ export default class MemberStatusMessageAvatar extends React.Component { user.on("User._unstable_statusMessage", this._onStatusMessageCommitted); } - componentWillUmount() { + componentWillUnmount() { const { user } = this.props.member; if (!user) { return; From f535fdbcaa5f6f43f1b10ee61feaa8071953ff32 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Jan 2020 15:39:07 -0700 Subject: [PATCH 067/109] Update chokidar to fix reskindex not working The major version bump doesn't appear to affect us. It wasn't working before on Windows, but now it is. --- package.json | 2 +- yarn.lock | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 16e7f943f1..3686966870 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@peculiar/webcrypto": "^1.0.22", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", - "chokidar": "^2.1.2", + "chokidar": "^3.3.1", "concurrently": "^4.0.1", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.15.1", diff --git a/yarn.lock b/yarn.lock index d2135f7aa6..81602b4e3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1570,6 +1570,14 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1884,6 +1892,11 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + bluebird@^3.5.0, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -1928,6 +1941,13 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -2232,7 +2252,7 @@ cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@^2.0.2, chokidar@^2.1.2, chokidar@^2.1.8: +chokidar@^2.0.2, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -2251,6 +2271,21 @@ chokidar@^2.0.2, chokidar@^2.1.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" @@ -3654,6 +3689,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -3815,6 +3857,11 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" +fsevents@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3909,6 +3956,13 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== + dependencies: + is-glob "^4.0.1" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -4463,6 +4517,13 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" @@ -4622,7 +4683,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -4663,6 +4724,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" @@ -6104,7 +6170,7 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -6578,6 +6644,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picomatch@^2.0.4, picomatch@^2.0.7: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -7169,6 +7240,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -8375,6 +8453,13 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" From 8efc45b31a504cfe65593077a9c6e8fc01f3c857 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 10:04:34 +0000 Subject: [PATCH 068/109] no need to verify our own devices for every room --- 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 3cdc308758..e0997f87da 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -820,7 +820,7 @@ export default createReactClass({ /* At this point, either `verified` or `unverified` is empty, or both */ /* Check all verified user devices. We don't care if everyone's unverified anyway. */ - for (const userId of [...verified, cli.getUserId()]) { + for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); const allDevicesVerified = devices.every(({deviceId}) => { return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); From 9877fd9e85c04fe03d9dfa0ae8cfd08ab3694392 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Jan 2020 10:04:38 +0000 Subject: [PATCH 069/109] Fix Array.concat undefined --- src/components/views/settings/tabs/room/BridgeSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.js b/src/components/views/settings/tabs/room/BridgeSettingsTab.js index 71b0169788..19c19d3bc6 100644 --- a/src/components/views/settings/tabs/room/BridgeSettingsTab.js +++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.js @@ -137,7 +137,7 @@ export default class BridgeSettingsTab extends React.Component { const client = MatrixClientPeg.get(); const roomState = (client.getRoom(roomId)).currentState; - const bridgeEvents = Array.concat(...BRIDGE_EVENT_TYPES.map((typeName) => + const bridgeEvents = [].concat(...BRIDGE_EVENT_TYPES.map((typeName) => Object.values(roomState.events[typeName] || {}), )); From 15e5552507c5584f0b160bc85326269df57500d3 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 17 Jan 2020 03:54:38 +0000 Subject: [PATCH 070/109] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2028 of 2028 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 | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 2d17d643ce..04a4792a77 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2042,5 +2042,30 @@ "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": "還原", "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": "秘密儲存空間將會使用您既有的金鑰備份詳細資訊設定。您的秘密儲存空間通關密語與復原金鑰會與您的金鑰備份相同", - "Restore your Key Backup": "復原您的金鑰備份" + "Restore your Key Backup": "復原您的金鑰備份", + "a few seconds ago": "數秒前", + "about a minute ago": "大約一分鐘前", + "%(num)s minutes ago": "%(num)s 分鐘前", + "about an hour ago": "大約一小時前", + "%(num)s hours ago": "%(num)s 小時前", + "about a day ago": "大約一天前", + "%(num)s days ago": "%(num)s 天前", + "a few seconds from now": "從現在開始數秒鐘", + "about a minute from now": "從現在開始大約一分鐘", + "%(num)s minutes from now": "從現在開始 %(num)s 分鐘", + "about an hour from now": "從現在開始大約一小時", + "%(num)s hours from now": "從現在開始 %(num)s 小時", + "about a day from now": "從現在開始大約一天", + "%(num)s days from now": "從現在開始 %(num)s 天", + "Failed to invite the following users to chat: %(csvUsers)s": "邀請使用者加入聊天失敗:%(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "我們無法建立您的直接對話。請檢查您想要邀請的使用者並再試一次。", + "Complete security": "完全安全", + "Verify this session to grant it access to encrypted messages.": "驗證此工作階段以取得對已加密訊息的存取權限。", + "Start": "開始", + "Session verified": "工作階段已驗證", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "您的新工作階段已驗證。其對您的已加密訊息有存取權,其他使用者也將會看到其受信任。", + "Done": "完成", + "Without completing security on this device, it won’t have access to encrypted messages.": "此裝置上沒有完全安全性,其對已加密訊息沒有存取權。", + "Go Back": "返回", + "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.": "秘密儲存空間將會使用您既有的金鑰備份詳細資訊設定。您的秘密儲存空間通關密語與復原金鑰將會與您的金鑰備份相同。" } From 8e11389820023231bc8ca347aadf443fb9e4ecf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 17 Jan 2020 07:37:12 +0000 Subject: [PATCH 071/109] Translated using Weblate (French) Currently translated at 100.0% (2032 of 2032 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 | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index b18bf44d73..3c70407ace 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2056,5 +2056,21 @@ "about an hour from now": "dans une heure environ", "%(num)s hours from now": "dans %(num)s heures", "about a day from now": "dans un jour environ", - "%(num)s days from now": "dans %(num)s jours" + "%(num)s days from now": "dans %(num)s jours", + "New invite dialog": "Nouvelle boîte de dialogue d’invitation", + "Failed to invite the following users to chat: %(csvUsers)s": "Échec de l’invitation des utilisateurs suivants à discuter : %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Impossible de créer votre Message direct. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez.", + "Something went wrong trying to invite the users.": "Une erreur est survenue en essayant d’inviter les utilisateurs.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "Impossible d’inviter ces utilisateurs. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez.", + "Recently Direct Messaged": "Messages directs récents", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "S’il y a quelqu’un que vous n’arrivez pas à trouver, demandez-lui son nom d’utilisateur (par ex. @utilisateur:serveur.com) ou partagez ce salon.", + "Complete security": "Compléter la sécurité", + "Verify this session to grant it access to encrypted messages.": "Vérifiez cette session pour l’autoriser à accéder à vos messages chiffrés.", + "Start": "Commencer", + "Session verified": "Session vérifiée", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Votre nouvelle session est maintenant vérifiée. Elle a accès à vos messages chiffrés et les autres utilisateurs la verront comme fiable.", + "Done": "Terminé", + "Without completing security on this device, it won’t have access to encrypted messages.": "Si vous ne complétez pas la sécurité sur cet appareil, vous n’aurez pas accès aux messages chiffrés.", + "Go Back": "Retourner en arrière", + "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." } From 510b08c88bd6f0dfeac1f56c39a12855f67990c1 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 10:18:50 +0000 Subject: [PATCH 072/109] changed logic to reflect the task --- src/components/structures/RoomView.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index e0997f87da..aa3e86fa60 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -809,17 +809,16 @@ export default createReactClass({ debuglog("e2e verified", verified, "unverified", unverified); - /* If we verify any users in this room, expect to verify every user in the room */ - if (verified.length > 0 && unverified.length > 0) { + /* If we've not verified anyone, set state to "normal" */ + if (verified.length == 0) { this.setState({ - e2eStatus: "warning", + e2eStatus: "normal", }); - debuglog("e2e status set to warning as some, but not all, users are verified"); + debuglog("e2e state set to normal as we have no verified users to worry about"); return; } - /* At this point, either `verified` or `unverified` is empty, or both */ - /* Check all verified user devices. We don't care if everyone's unverified anyway. */ + /* Check all verified user devices. */ for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); const allDevicesVerified = devices.every(({deviceId}) => { @@ -836,7 +835,7 @@ export default createReactClass({ } this.setState({ - e2eStatus: unverified.length === 0 ? "verified" : "normal", + e2eStatus: "verified", }); }, From d02185e4af661542e39a568d43aa58f19670ec0c Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 10:22:53 +0000 Subject: [PATCH 073/109] whoops, the number of unverified users matters to the logic --- src/components/structures/RoomView.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index aa3e86fa60..8ecb6a6a02 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -809,15 +809,6 @@ export default createReactClass({ debuglog("e2e verified", verified, "unverified", unverified); - /* If we've not verified anyone, set state to "normal" */ - if (verified.length == 0) { - this.setState({ - e2eStatus: "normal", - }); - debuglog("e2e state set to normal as we have no verified users to worry about"); - return; - } - /* Check all verified user devices. */ for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); @@ -835,7 +826,7 @@ export default createReactClass({ } this.setState({ - e2eStatus: "verified", + e2eStatus: unverified.length === 0 ? "verified" : "normal", }); }, From 908630c0d942de6ec9115c7b197b0d2b47f87488 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 11:30:45 +0000 Subject: [PATCH 074/109] *rude grumbling noises about @dbkr* --- 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 8ecb6a6a02..9b02f6d503 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -813,7 +813,7 @@ export default createReactClass({ for (const userId of verified) { const devices = await cli.getStoredDevicesForUser(userId); const allDevicesVerified = devices.every(({deviceId}) => { - return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); + return cli.checkDeviceTrust(userId, deviceId).isVerified(); }); if (!allDevicesVerified) { this.setState({ From 9e43abaf3aa5eb4e76492c34209a46f8e8796c7c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 11:43:35 +0000 Subject: [PATCH 075/109] Toasts for new, unverified sessions Fixes https://github.com/vector-im/riot-web/issues/11218 --- res/css/structures/_ToastContainer.scss | 6 +- src/DeviceListener.js | 90 +++++++++++++++++++ src/Lifecycle.js | 8 ++ src/components/structures/ToastContainer.js | 2 +- .../views/toasts/NewSessionToast.js | 57 ++++++++++++ src/i18n/strings/en_EN.json | 5 +- src/stores/ToastStore.js | 4 + 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 src/DeviceListener.js create mode 100644 src/components/views/toasts/NewSessionToast.js diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 4c5e746e66..5634a97c53 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -51,7 +51,7 @@ limitations under the License. &.mx_Toast_hasIcon { &::after { content: ""; - width: 20px; + width: 21px; height: 20px; grid-column: 1; grid-row: 1; @@ -64,6 +64,10 @@ limitations under the License. background-color: $primary-fg-color; } + &.mx_Toast_icon_verification_warning::after { + background-image: url("$(res)/img/e2e/warning.svg"); + } + h2, .mx_Toast_body { grid-column: 2; } diff --git a/src/DeviceListener.js b/src/DeviceListener.js new file mode 100644 index 0000000000..4b779377e8 --- /dev/null +++ b/src/DeviceListener.js @@ -0,0 +1,90 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClientPeg } from './MatrixClientPeg'; +import SettingsStore from './settings/SettingsStore'; +import * as sdk from './index'; +import { _t } from './languageHandler'; +import ToastStore from './stores/ToastStore'; + +function toastKey(device) { + return 'newsession_' + device.deviceId; +} + +export default class DeviceListener { + static sharedInstance() { + if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); + return global.mx_DeviceListener; + } + + constructor() { + // device IDs for which the user has dismissed the verify toast ('Later') + this._dismissed = new Set(); + } + + start() { + MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); + MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); + this.recheck(); + } + + stop() { + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); + MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); + } + this._dismissed.clear(); + } + + dismissVerification(deviceId) { + this._dismissed.add(deviceId); + this.recheck(); + } + + _onDevicesUpdated = (users) => { + if (!users.includes(MatrixClientPeg.get().getUserId())) return; + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; + this.recheck(); + } + + _onDeviceVerificationChanged = (users) => { + if (!users.includes(MatrixClientPeg.get().getUserId())) return; + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; + this.recheck(); + } + + async recheck() { + const cli = MatrixClientPeg.get(); + + 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.isVerified() || this._dismissed.has(device.deviceId)) { + ToastStore.sharedInstance().dismissToast(toastKey(device)); + } else { + ToastStore.sharedInstance().addOrReplaceToast({ + key: toastKey(device), + title: _t("New Session"), + icon: "verification_warning", + props: {deviceId: device.deviceId}, + component: sdk.getComponent("toasts.NewSessionToast"), + }); + } + } + } +} diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0796e326a0..1603c73d25 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,8 +36,10 @@ import { sendLoginRequest } from "./Login"; import * as StorageManager from './utils/StorageManager'; import SettingsStore from "./settings/SettingsStore"; import TypingStore from "./stores/TypingStore"; +import ToastStore from "./stores/ToastStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {Mjolnir} from "./mjolnir/Mjolnir"; +import DeviceListener from "./DeviceListener"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -575,6 +578,7 @@ async function startMatrixClient(startSyncing=true) { Notifier.start(); UserActivity.sharedInstance().start(); TypingStore.sharedInstance().reset(); // just in case + ToastStore.sharedInstance().reset(); if (!SettingsStore.getValue("lowBandwidth")) { Presence.start(); } @@ -595,6 +599,9 @@ async function startMatrixClient(startSyncing=true) { await MatrixClientPeg.assign(); } + // This needs to be started after crypto is set up + DeviceListener.sharedInstance().start(); + // dispatch that we finished starting up to wire up any other bits // of the matrix client that cannot be set prior to starting up. dis.dispatch({action: 'client_started'}); @@ -651,6 +658,7 @@ export function stopMatrixClient(unsetClient=true) { ActiveWidgetStore.stop(); IntegrationManagers.sharedInstance().stopWatching(); Mjolnir.sharedInstance().stop(); + DeviceListener.sharedInstance().stop(); if (DMRoomMap.shared()) DMRoomMap.shared().stop(); EventIndexPeg.stop(); const cli = MatrixClientPeg.get(); diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js index bc74133433..8a05f62e61 100644 --- a/src/components/structures/ToastContainer.js +++ b/src/components/structures/ToastContainer.js @@ -22,7 +22,7 @@ import classNames from "classnames"; export default class ToastContainer extends React.Component { constructor() { super(); - this.state = {toasts: []}; + this.state = {toasts: ToastStore.sharedInstance().getToasts()}; } componentDidMount() { diff --git a/src/components/views/toasts/NewSessionToast.js b/src/components/views/toasts/NewSessionToast.js new file mode 100644 index 0000000000..f83326121b --- /dev/null +++ b/src/components/views/toasts/NewSessionToast.js @@ -0,0 +1,57 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import * as sdk from "../../../index"; +import { _t } from '../../../languageHandler'; +import Modal from "../../../Modal"; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import DeviceListener from '../../../DeviceListener'; + +export default class VerifySessionToast extends React.PureComponent { + static propTypes = { + toastKey: PropTypes.string.isRequired, + deviceId: PropTypes.string, + }; + + _onLaterClick = () => { + DeviceListener.sharedInstance().dismissVerification(this.props.deviceId); + }; + + _onVerifyClick = async () => { + const cli = MatrixClientPeg.get(); + const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); + + const device = await cli.getStoredDevice(cli.getUserId(), this.props.deviceId); + + Modal.createTrackedDialog('New Session Verify', 'Starting dialog', DeviceVerifyDialog, { + userId: MatrixClientPeg.get().getUserId(), + device, + }, null, /* priority = */ false, /* static = */ true); + }; + + render() { + const FormButton = sdk.getComponent("elements.FormButton"); + return (
+
{_t("Other users may not trust it")}
+
+ + +
+
); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f8b17db7c5..4af203177c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -85,6 +85,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", + "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", @@ -513,6 +514,9 @@ "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", @@ -1130,7 +1134,6 @@ "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.", - "Verify": "Verify", "Security": "Security", "Sunday": "Sunday", "Monday": "Monday", diff --git a/src/stores/ToastStore.js b/src/stores/ToastStore.js index f6cc30db67..2c4464813b 100644 --- a/src/stores/ToastStore.js +++ b/src/stores/ToastStore.js @@ -31,6 +31,10 @@ export default class ToastStore extends EventEmitter { this._toasts = []; } + reset() { + this._toasts = []; + } + addOrReplaceToast(newToast) { const oldIndex = this._toasts.findIndex(t => t.key === newToast.key); if (oldIndex === -1) { From fb9962b08e702081572fbf7c1263496f747fd3fc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 Jan 2020 13:09:08 +0000 Subject: [PATCH 076/109] Disable key request dialogs with cross-signing Cross-signing verification is meant to replace the old key share between devices flow. This disables it when the cross-signing lab is enabled. Fixes https://github.com/vector-im/riot-web/issues/11904 --- src/KeyRequestHandler.js | 13 +++++++++++++ src/components/structures/MatrixChat.js | 2 ++ src/components/views/dialogs/KeyShareDialog.js | 3 +++ 3 files changed, 18 insertions(+) diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js index 65dc7fdb0f..0aca6529e4 100644 --- a/src/KeyRequestHandler.js +++ b/src/KeyRequestHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +18,8 @@ limitations under the License. import * as sdk from './index'; import Modal from './Modal'; +// TODO: We can remove this once cross-signing is the only way. +// https://github.com/vector-im/riot-web/issues/11908 export default class KeyRequestHandler { constructor(matrixClient) { this._matrixClient = matrixClient; @@ -30,6 +33,11 @@ export default class KeyRequestHandler { } handleKeyRequest(keyRequest) { + // Ignore own device key requests if cross-signing lab enabled + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + return; + } + const userId = keyRequest.userId; const deviceId = keyRequest.deviceId; const requestId = keyRequest.requestId; @@ -60,6 +68,11 @@ export default class KeyRequestHandler { } handleKeyRequestCancellation(cancellation) { + // Ignore own device key requests if cross-signing lab enabled + if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + return; + } + // see if we can find the request in the queue const userId = cancellation.userId; const deviceId = cancellation.deviceId; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 978743ca87..2797887f29 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1383,6 +1383,8 @@ export default createReactClass({ cli.on("Session.logged_out", () => dft.stop()); cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err)); + // TODO: We can remove this once cross-signing is the only way. + // https://github.com/vector-im/riot-web/issues/11908 const krh = new KeyRequestHandler(cli); cli.on("crypto.roomKeyRequest", (req) => { krh.handleKeyRequest(req); diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js index 3afb5546cf..507f8b4678 100644 --- a/src/components/views/dialogs/KeyShareDialog.js +++ b/src/components/views/dialogs/KeyShareDialog.js @@ -22,6 +22,9 @@ import * as sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; +// TODO: We can remove this once cross-signing is the only way. +// https://github.com/vector-im/riot-web/issues/11908 + /** * Dialog which asks the user whether they want to share their keys with * an unverified device. From 066a01ae94444f7c13f5709bcd74dbd445bd0e2c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 13:35:51 +0000 Subject: [PATCH 077/109] Check for a matrixclient before trying to use it Was being caught by the try block but still logging an error to the console unnecessarily: we should not expect there to necessarily be a matrix client since we run this from the constructor and there's a shared instance which could be constructed at any point. --- src/integrations/IntegrationManagers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index b482ec73ce..c933e5c433 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -83,6 +83,7 @@ export class IntegrationManagers { } async _setupHomeserverManagers() { + if (!MatrixClientPeg.get()) return; try { console.log("Updating homeserver-configured integration managers..."); const homeserverDomain = MatrixClientPeg.getHomeserverName(); From 5e4dab1f151422fa7220c766bfb13752ba06a1dc Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 17 Jan 2020 11:05:36 +0000 Subject: [PATCH 078/109] Translated using Weblate (Hungarian) Currently translated at 99.7% (2026 of 2032 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 1803ac7601..77952b994c 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2066,5 +2066,6 @@ "Done": "Kész", "Without completing security on this device, it won’t have access to encrypted messages.": "Az eszköz biztonságának beállítása nélkül nem férhet hozzá a titkosított üzenetekhez.", "Go Back": "Vissza", - "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.": "A Biztonsági Tároló a kulcs mentés adatainak felhasználásával lesz beállítva. A biztonsági tároló jelmondata és a visszaállítási kulcs ugyanaz lesz mint a kulcs mentéshez használt." + "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.": "A Biztonsági Tároló a kulcs mentés adatainak felhasználásával lesz beállítva. A biztonsági tároló jelmondata és a visszaállítási kulcs ugyanaz lesz mint a kulcs mentéshez használt.", + "New invite dialog": "Új meghívó párbeszédablak" } From 9b64686041b9ef4c28fc76bbf3ab54cc795ce5f1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 Jan 2020 13:50:24 +0000 Subject: [PATCH 079/109] Add missing import --- src/KeyRequestHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js index 0aca6529e4..30f3b7d50e 100644 --- a/src/KeyRequestHandler.js +++ b/src/KeyRequestHandler.js @@ -17,6 +17,7 @@ limitations under the License. import * as sdk from './index'; import Modal from './Modal'; +import SettingsStore from './settings/SettingsStore'; // TODO: We can remove this once cross-signing is the only way. // https://github.com/vector-im/riot-web/issues/11908 From 42fe69aec9777d868da6504f3981e6023959c550 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 14:08:37 +0000 Subject: [PATCH 080/109] Don't check devices if crypto is disabled --- src/DeviceListener.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 4b779377e8..15ca931fc8 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -69,6 +69,8 @@ export default class DeviceListener { async recheck() { const cli = MatrixClientPeg.get(); + if (!cli.isCryptoEnabled()) return false; + const devices = await cli.getStoredDevicesForUser(cli.getUserId()); for (const device of devices) { if (device.deviceId == cli.deviceId) continue; From d69c5f6a1b2be338560217a21ef1f79c2c6f803f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 14:46:20 +0000 Subject: [PATCH 081/109] Catch exception if passphrase dialog cancelled As hopefully explained by comment --- .../structures/auth/CompleteSecurity.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 77f7fe26e4..b64f368908 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -40,12 +40,16 @@ export default class CompleteSecurity extends React.Component { onStartClick = async () => { const cli = MatrixClientPeg.get(); - await accessSecretStorage(async () => { - await cli.checkOwnCrossSigningTrust(); - }); - this.setState({ - phase: PHASE_DONE, - }); + try { + await accessSecretStorage(async () => { + await cli.checkOwnCrossSigningTrust(); + }); + this.setState({ + phase: PHASE_DONE, + }); + } catch (e) { + // this will throw if the user hits cancel, so ignore + } } onSkipClick = () => { From d20db3560a20c2a7075cfb77ef1e3e35fceed897 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 Jan 2020 15:50:27 +0100 Subject: [PATCH 082/109] fix import paths after build system refactor --- src/components/views/right_panel/EncryptionInfo.js | 2 +- src/components/views/right_panel/EncryptionPanel.js | 2 +- src/components/views/right_panel/VerificationPanel.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/right_panel/EncryptionInfo.js b/src/components/views/right_panel/EncryptionInfo.js index 3d5de829b7..5770e9b086 100644 --- a/src/components/views/right_panel/EncryptionInfo.js +++ b/src/components/views/right_panel/EncryptionInfo.js @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import sdk from "../../.."; +import * as sdk from '../../../index'; import {_t} from "../../../languageHandler"; export default class EncryptionInfo extends React.PureComponent { diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index e3f3b86940..4b3473935a 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import EncryptionInfo from "./EncryptionInfo"; import VerificationPanel from "./VerificationPanel"; -import MatrixClientPeg from "../../../MatrixClientPeg"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {ensureDMExists} from "../../../createRoom"; export default class EncryptionPanel extends React.PureComponent { diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index fff9c37358..4dee3e6ae8 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import sdk from "../../.."; +import * as sdk from '../../../index'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; export default class VerificationPanel extends React.PureComponent { From a73b7229a7ec50a65f3b147ce7f760691e03a254 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 Jan 2020 16:31:38 +0100 Subject: [PATCH 083/109] fix lint --- src/components/structures/MatrixChat.js | 1 - src/components/views/right_panel/VerificationPanel.js | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 7cbec11e08..3ac8a93e3d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1455,7 +1455,6 @@ export default createReactClass({ cli.on("crypto.verification.request", request => { console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId()); if (request.pending) { - console.log(`emitting toast for verification request with txnid ${request.channel.transactionId}`, request.event && request.event.getId()); ToastStore.sharedInstance().addOrReplaceToast({ key: 'verifreq_' + request.channel.transactionId, title: _t("Verification Request"), diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 4dee3e6ae8..0d28e1568f 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -41,7 +41,10 @@ export default class VerificationPanel extends React.PureComponent { if (request.requested) { return (

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

); } else if (request.ready) { - return (

{request.otherUserId} is ready, start Verify by emoji

); + const verifyButton = + Verify by emoji + ; + return (

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

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

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

; From 942b391d8e251c8d5eaa96293594dffaf17f7368 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 17 Jan 2020 15:29:08 +0000 Subject: [PATCH 084/109] Translated using Weblate (Hungarian) 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/hu/ --- src/i18n/strings/hu.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 77952b994c..c131e77302 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2067,5 +2067,14 @@ "Without completing security on this device, it won’t have access to encrypted messages.": "Az eszköz biztonságának beállítása nélkül nem férhet hozzá a titkosított üzenetekhez.", "Go Back": "Vissza", "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.": "A Biztonsági Tároló a kulcs mentés adatainak felhasználásával lesz beállítva. A biztonsági tároló jelmondata és a visszaállítási kulcs ugyanaz lesz mint a kulcs mentéshez használt.", - "New invite dialog": "Új meghívó párbeszédablak" + "New invite dialog": "Új meghívó párbeszédablak", + "New Session": "Új Munkamenet", + "Other users may not trust it": "Más felhasználók lehet, hogy nem bíznak benne", + "Later": "Később", + "Failed to invite the following users to chat: %(csvUsers)s": "Az alábbi felhasználókat nem sikerült meghívni a beszélgetésbe: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "A közvetlen üzenetedet nem sikerült elkészíteni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra.", + "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." } From 716c8ba68f392d3d1f727918475381aae26f24c4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 Jan 2020 17:02:31 +0100 Subject: [PATCH 085/109] pr feedback --- src/components/views/messages/MKeyVerificationRequest.js | 1 - src/components/views/toasts/VerificationRequestToast.js | 1 - src/stores/RightPanelStorePhases.js | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 32f75d9895..ae793556d8 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -45,7 +45,6 @@ export default class MKeyVerificationRequest extends React.Component { _openRequest = () => { const {verificationRequest} = this.props.mxEvent; - dis.dispatch({action: "show_right_panel"}); dis.dispatch({ action: "set_right_panel_phase", phase: RIGHT_PANEL_PHASES.EncryptionPanel, diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index c681ca0951..479a3e3f93 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -76,7 +76,6 @@ export default class VerificationRequestToast extends React.PureComponent { } try { await request.accept(); - dis.dispatch({action: "show_right_panel"}); dis.dispatch({ action: "set_right_panel_phase", phase: RIGHT_PANEL_PHASES.EncryptionPanel, diff --git a/src/stores/RightPanelStorePhases.js b/src/stores/RightPanelStorePhases.js index 7783f960d6..d9af320233 100644 --- a/src/stores/RightPanelStorePhases.js +++ b/src/stores/RightPanelStorePhases.js @@ -21,8 +21,9 @@ export const RIGHT_PANEL_PHASES = Object.freeze({ FilePanel: 'FilePanel', NotificationPanel: 'NotificationPanel', RoomMemberInfo: 'RoomMemberInfo', - Room3pidMemberInfo: 'Room3pidMemberInfo', EncryptionPanel: 'EncryptionPanel', + + Room3pidMemberInfo: 'Room3pidMemberInfo', // Group stuff GroupMemberList: 'GroupMemberList', GroupRoomList: 'GroupRoomList', From 03cb76861f42d0b8e06555c0cb80df515032d74d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 16:26:47 +0000 Subject: [PATCH 086/109] Catch exception in checkTerms if no ID server This line will throw if it can't get to the ID server, so move it inside the catch block too. --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 908968b051..b9eaa3efa3 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -160,8 +160,8 @@ export default class GeneralUserSettingsTab extends React.Component { // for free. So we might as well use that for our own purposes. const idServerUrl = MatrixClientPeg.get().getIdentityServerUrl(); const authClient = new IdentityAuthClient(); - const idAccessToken = await authClient.getAccessToken({ check: false }); try { + const idAccessToken = await authClient.getAccessToken({ check: false }); await startTermsFlow([new Service( SERVICE_TYPES.IS, idServerUrl, From 4a82e868595160a0494dbb1efd0a09b1f4c2817f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 Jan 2020 17:59:08 +0000 Subject: [PATCH 087/109] Adjust secret storage to work before sync This adjusts to changed JS SDK APIs that allow secret storage to optionally ask the server for any account data needed at login. Fixes https://github.com/vector-im/riot-web/issues/11901 --- src/CrossSigningManager.js | 2 +- src/components/views/settings/CrossSigningPanel.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 0773a8d32d..085764214f 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -107,7 +107,7 @@ export async function accessSecretStorage(func = async () => { }) { cachingAllowed = true; try { - if (!cli.hasSecretStorageKey()) { + if (!await cli.hasSecretStorageKey()) { // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 2b191454f6..49046cd051 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -60,14 +60,14 @@ export default class CrossSigningPanel extends React.PureComponent { this.setState(this._getUpdatedStatus()); }; - _getUpdatedStatus() { + async _getUpdatedStatus() { // XXX: Add public accessors if we keep this around in production const cli = MatrixClientPeg.get(); const crossSigning = cli._crypto._crossSigningInfo; const secretStorage = cli._crypto._secretStorage; const crossSigningPublicKeysOnDevice = crossSigning.getId(); - const crossSigningPrivateKeysInStorage = crossSigning.isStoredInSecretStorage(secretStorage); - const secretStorageKeyInAccount = secretStorage.hasKey(); + const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); + const secretStorageKeyInAccount = await secretStorage.hasKey(); return { crossSigningPublicKeysOnDevice, From c7ddba786bdabaf61f473b5162c8fe7f945920f4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 Jan 2020 20:06:44 +0000 Subject: [PATCH 088/109] Move feature flag check for new session toast Forgot the path where it checks on startup. Just put it in recheck which covers everything. Fixes https://github.com/vector-im/riot-web/issues/11921 --- src/DeviceListener.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 15ca931fc8..9ae6a62ab1 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -56,17 +56,16 @@ export default class DeviceListener { _onDevicesUpdated = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; this.recheck(); } _onDeviceVerificationChanged = (users) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; - if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; this.recheck(); } async recheck() { + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return; const cli = MatrixClientPeg.get(); if (!cli.isCryptoEnabled()) return false; From 2faa4254baa06e0f4f0ed53d8289cd47fb65eea6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 14:36:23 -0700 Subject: [PATCH 089/109] Score users who have recently spoken higher in invite suggestions Fixes https://github.com/vector-im/riot-web/issues/11769 The algorithm should be documented in the diff as comments. --- src/components/views/dialogs/InviteDialog.js | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 1b7a50c084..0c2a6785e8 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -411,6 +411,57 @@ export default class InviteDialog extends React.PureComponent { return scores; }, {}); + // Now that we have scores for being in rooms, boost those people who have sent messages + // recently, as a way to improve the quality of suggestions. We do this by checking every + // room to see who has sent a message in the last few hours, and giving them a score + // which correlates to the freshness of their message. In theory, this results in suggestions + // which are closer to "continue this conversation" rather than "this person exists". + const trueJoinedRooms = client.getRooms().filter(r => r.getMyMembership() === 'join'); + const now = (new Date()).getTime(); + const maxAgeConsidered = now - (60 * 60 * 1000); // 1 hour ago + const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic + const lastSpoke = {}; // userId: timestamp + const lastSpokeMembers = {}; // userId: room member + for (const room of trueJoinedRooms) { + // Skip low priority rooms and DMs + if (Object.keys(room.tags).includes("m.lowpriority") || DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + continue; + } + + const events = room.getLiveTimeline().getEvents(); // timelines are most recent last + for (let i = events.length - 1; i >= Math.max(0, events.length - maxMessagesConsidered); i--) { + const ev = events[i]; + if (ev.getSender() === MatrixClientPeg.get().getUserId() || excludedUserIds.includes(ev.getSender())) { + continue; + } + if (ev.getTs() <= maxAgeConsidered) { + break; // give up: all events from here on out are too old + } + + if (!lastSpoke[ev.getSender()] || lastSpoke[ev.getSender()] < ev.getTs()) { + lastSpoke[ev.getSender()] = ev.getTs(); + lastSpokeMembers[ev.getSender()] = room.getMember(ev.getSender()); + } + } + } + for (const userId in lastSpoke) { + const ts = lastSpoke[userId]; + const member = lastSpokeMembers[userId]; + if (!member) continue; // skip people we somehow don't have profiles for + + // Scores from being in a room give a 'good' score of about 1.0-1.5, so for our + // boost we'll try and award at least +1.0 for making the list, with +4.0 being + // an approximate maximum for being selected. + const distanceFromNow = Math.abs(now - ts); // abs to account for slight future messages + const inverseTime = (now - maxAgeConsidered) - distanceFromNow; + const scoreBoost = Math.max(1, inverseTime / (15 * 60 * 1000)); // 15min segments to keep scores sane + + let record = memberScores[userId]; + if (!record) record = memberScores[userId] = {score: 0}; + record.member = member; + record.score += scoreBoost; + } + const members = Object.values(memberScores); members.sort((a, b) => { if (a.score === b.score) { From 3850377e275d6ccc27cbe38a07586c266010e264 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 14:40:33 -0700 Subject: [PATCH 090/109] Appease the linter --- src/components/views/dialogs/InviteDialog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 0c2a6785e8..02a96f7f37 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -424,7 +424,8 @@ export default class InviteDialog extends React.PureComponent { const lastSpokeMembers = {}; // userId: room member for (const room of trueJoinedRooms) { // Skip low priority rooms and DMs - if (Object.keys(room.tags).includes("m.lowpriority") || DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId); + if (Object.keys(room.tags).includes("m.lowpriority") || isDm) { continue; } From df3fe5139d70bd8a4ffe1fb0f91b0d17147798f4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 17:07:37 -0700 Subject: [PATCH 091/109] Fix error about MessagePanel not being available for read markers Stacktrace: ``` TimelinePanel.js?b9ae:1139 Uncaught (in promise) TypeError: Cannot read property 'getBoundingClientRect' of null at Object._getLastDisplayedEventIndex (TimelinePanel.js?b9ae:1139) at Object.updateReadMarker (TimelinePanel.js?b9ae:751) at Object._callee$ (TimelinePanel.js?b9ae:613) at tryCatch (runtime.js?4422:45) at Generator.invoke [as _invoke] (runtime.js?4422:271) at Generator.prototype. [as next] (runtime.js?4422:97) at asyncGeneratorStep (asyncToGenerator.js?56ef:3) at _next (asyncToGenerator.js?56ef:25) ``` --- src/components/structures/TimelinePanel.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 30b02bfcca..68e3e3b3ab 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1136,7 +1136,9 @@ const TimelinePanel = createReactClass({ const messagePanel = this._messagePanel.current; if (messagePanel === undefined) return null; - const wrapperRect = ReactDOM.findDOMNode(messagePanel).getBoundingClientRect(); + const messagePanelNode = ReactDOM.findDOMNode(messagePanel); + if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync + const wrapperRect = messagePanelNode.getBoundingClientRect(); const myUserId = MatrixClientPeg.get().credentials.userId; const isNodeInView = (node) => { From 21f8130ebe601cc257e0ec0ec7ed581b3b8e2fc6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Jan 2020 17:12:35 -0700 Subject: [PATCH 092/109] Prevent the invite dialog from jumping around when elements change Part of https://github.com/vector-im/riot-web/issues/11201 --- res/css/views/dialogs/_InviteDialog.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index d0b53b7766..221ad7d48c 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -206,3 +206,8 @@ limitations under the License. margin-left: 4px; } } + +.mx_InviteDialog { + // Prevent the dialog from jumping around randomly when elements change. + height: 590px; +} From 8ba54f5f7d7227b8de0a2809bab7a0f4dc4ae231 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 18 Jan 2020 01:38:22 +0000 Subject: [PATCH 093/109] Don't use expect and jest-mock anymore as they're implicit from jest Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- test/components/views/messages/TextualBody-test.js | 1 - test/components/views/rooms/RoomSettings-test.js | 1 - 2 files changed, 2 deletions(-) diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index 755751ffb7..180a2f7e54 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -15,7 +15,6 @@ limitations under the License. */ import React from "react"; -import expect from 'expect'; import Adapter from "enzyme-adapter-react-16"; import { configure, mount } from "enzyme"; diff --git a/test/components/views/rooms/RoomSettings-test.js b/test/components/views/rooms/RoomSettings-test.js index 21d22a964c..870d7f0aab 100644 --- a/test/components/views/rooms/RoomSettings-test.js +++ b/test/components/views/rooms/RoomSettings-test.js @@ -1,7 +1,6 @@ // TODO: Rewrite room settings tests for dialog support import React from 'react'; import ReactDOM from 'react-dom'; -import jest from 'jest-mock'; import * as testUtils from '../../../test-utils'; import sdk from '../../../skinned-sdk'; import {MatrixClientPeg} from '../../../../src/MatrixClientPeg'; From 9365a9cb3040e8d7d0d7652175ec52ac8dce1d2e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 18 Jan 2020 01:39:14 +0000 Subject: [PATCH 094/109] Remove lolex where its not needed and move to dev-deps. Remove unused optimist Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 7 +++--- test/UserActivity-test.js | 5 ++--- .../components/views/rooms/MemberList-test.js | 6 ----- yarn.lock | 22 +++++++++++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3686966870..d5f56aded0 100644 --- a/package.json +++ b/package.json @@ -80,9 +80,7 @@ "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "lolex": "4.2", "matrix-js-sdk": "3.0.0", - "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", "prop-types": "^15.5.8", @@ -138,6 +136,7 @@ "file-loader": "^3.0.1", "flow-parser": "^0.57.3", "jest": "^24.9.0", + "lolex": "^5.1.2", "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "react-test-renderer": "^16.9.0", @@ -157,7 +156,9 @@ "testMatch": [ "/test/**/*-test.js" ], - "setupTestFrameworkScriptFile": "/test/setupTests.js", + "setupFilesAfterEnv": [ + "/test/setupTests.js" + ], "moduleNameMapper": { "\\.(gif|png|svg|ttf|woff2)$": "/__mocks__/imageMock.js", "\\$webapp/i18n/languages.json": "/__mocks__/languages.json" diff --git a/test/UserActivity-test.js b/test/UserActivity-test.js index a30df527ae..1b0fbafb48 100644 --- a/test/UserActivity-test.js +++ b/test/UserActivity-test.js @@ -15,7 +15,6 @@ limitations under the License. */ import lolex from 'lolex'; -import jest from 'jest-mock'; import EventEmitter from 'events'; import UserActivity from '../src/UserActivity'; @@ -36,8 +35,8 @@ describe('UserActivity', function() { let clock; beforeEach(function() { - fakeWindow = new FakeDomEventEmitter(), - fakeDocument = new FakeDomEventEmitter(), + fakeWindow = new FakeDomEventEmitter(); + fakeDocument = new FakeDomEventEmitter(); userActivity = new UserActivity(fakeWindow, fakeDocument); userActivity.start(); clock = lolex.install(); diff --git a/test/components/views/rooms/MemberList-test.js b/test/components/views/rooms/MemberList-test.js index cac2c5287c..24b6391c93 100644 --- a/test/components/views/rooms/MemberList-test.js +++ b/test/components/views/rooms/MemberList-test.js @@ -1,7 +1,6 @@ import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; -import lolex from 'lolex'; import * as TestUtils from '../../../test-utils'; @@ -27,7 +26,6 @@ describe('MemberList', () => { let parentDiv = null; let client = null; let root = null; - let clock = null; let memberListRoom; let memberList = null; @@ -40,8 +38,6 @@ describe('MemberList', () => { client = MatrixClientPeg.get(); client.hasLazyLoadMembersEnabled = () => false; - clock = lolex.install(); - parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); @@ -114,8 +110,6 @@ describe('MemberList', () => { parentDiv = null; } - clock.uninstall(); - done(); }); diff --git a/yarn.lock b/yarn.lock index 81602b4e3d..ce718c0900 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1137,6 +1137,13 @@ tslib "^1.10.0" webcrypto-core "^1.0.17" +"@sinonjs/commons@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" + integrity sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg== + dependencies: + type-detect "4.0.8" + "@types/babel__core@^7.1.0": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30" @@ -5610,10 +5617,12 @@ loglevel@^1.6.4: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ== -lolex@4.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7" - integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg== +lolex@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" longest-streak@^2.0.1: version "2.0.3" @@ -8580,6 +8589,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" From 2eaafa71a22276190ac176b673c9a63e73d00da2 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 18 Jan 2020 01:46:39 +0000 Subject: [PATCH 095/109] Translated using Weblate (Chinese (Traditional)) 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/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 04a4792a77..dbd8319b8a 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2067,5 +2067,13 @@ "Done": "完成", "Without completing security on this device, it won’t have access to encrypted messages.": "此裝置上沒有完全安全性,其對已加密訊息沒有存取權。", "Go Back": "返回", - "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.": "秘密儲存空間將會使用您既有的金鑰備份詳細資訊設定。您的秘密儲存空間通關密語與復原金鑰將會與您的金鑰備份相同。", + "New Session": "新工作階段", + "New invite dialog": "新邀請對話框", + "Other users may not trust it": "其他使用者可能不會信任它", + "Later": "稍後", + "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)或分享此聊天室。" } From c97de43f69095b64d0db325959c43dae8ccbda77 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 18 Jan 2020 02:01:45 +0000 Subject: [PATCH 096/109] Remove unused fetch polyfills, querystring, require-json. Move glob to dev-dep Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 6 +----- src/autocomplete/DuckDuckGoProvider.js | 1 - src/components/views/elements/AppTile.js | 2 +- src/utils/DecryptFile.js | 2 -- yarn.lock | 14 ++------------ 5 files changed, 4 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 3686966870..a18186ef6e 100644 --- a/package.json +++ b/package.json @@ -72,12 +72,10 @@ "fuse.js": "^2.2.0", "gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566", "gfm.css": "^1.1.1", - "glob": "^5.0.14", "glob-to-regexp": "^0.4.1", "highlight.js": "^9.15.8", "html-entities": "^1.2.1", "is-ip": "^2.0.0", - "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", "lolex": "4.2", @@ -88,7 +86,6 @@ "prop-types": "^15.5.8", "qrcode-react": "^0.1.16", "qs": "^6.6.0", - "querystring": "^0.2.0", "react": "^16.9.0", "react-addons-css-transition-group": "15.6.2", "react-beautiful-dnd": "^4.0.1", @@ -101,7 +98,6 @@ "url": "^0.11.0", "velocity-animate": "^1.5.2", "what-input": "^5.2.6", - "whatwg-fetch": "^1.1.1", "zxcvbn": "^4.4.2" }, "devDependencies": { @@ -137,11 +133,11 @@ "estree-walker": "^0.5.0", "file-loader": "^3.0.1", "flow-parser": "^0.57.3", + "glob": "^5.0.14", "jest": "^24.9.0", "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "react-test-renderer": "^16.9.0", - "require-json": "0.0.1", "rimraf": "^2.4.3", "source-map-loader": "^0.2.3", "stylelint": "^9.10.1", diff --git a/src/autocomplete/DuckDuckGoProvider.js b/src/autocomplete/DuckDuckGoProvider.js index ca1b1478cc..8cff83554a 100644 --- a/src/autocomplete/DuckDuckGoProvider.js +++ b/src/autocomplete/DuckDuckGoProvider.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; -import 'whatwg-fetch'; import {TextualCompletion} from './Components'; import type {SelectionRange} from "./Autocompleter"; diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 4b586b1553..b12ace708d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -17,7 +17,7 @@ limitations under the License. */ import url from 'url'; -import qs from 'querystring'; +import qs from 'qs'; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.js index b87b723ed7..f5a1b0aa62 100644 --- a/src/utils/DecryptFile.js +++ b/src/utils/DecryptFile.js @@ -17,8 +17,6 @@ limitations under the License. // Pull in the encryption lib so that we can decrypt attachments. import encrypt from 'browser-encrypt-attachment'; -// Pull in a fetch polyfill so we can download encrypted attachments. -import 'isomorphic-fetch'; // Grab the client so that we can turn mxc:// URLs into https:// URLS. import {MatrixClientPeg} from '../MatrixClientPeg'; diff --git a/yarn.lock b/yarn.lock index 81602b4e3d..db405d2480 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4869,7 +4869,7 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: +isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= @@ -6995,7 +6995,7 @@ querystring-es3@^0.2.0: resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0, querystring@^0.2.0: +querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= @@ -7473,11 +7473,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-json@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/require-json/-/require-json-0.0.1.tgz#3c8914f93d7442de8cbf4e681ac62a72aa3367fe" - integrity sha1-PIkU+T10Qt6Mv05oGsYqcqozZ/4= - require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -9014,11 +9009,6 @@ whatwg-fetch@^0.9.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= -whatwg-fetch@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz#ac3c9d39f320c6dce5339969d054ef43dd333319" - integrity sha1-rDydOfMgxtzlM5lp0FTvQ90zMxk= - whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" From ef7aeda4d6a6b68711aba33205844a68389d050a Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 18 Jan 2020 11:42:52 +0000 Subject: [PATCH 097/109] Translated using Weblate (Basque) 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/eu/ --- src/i18n/strings/eu.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 31dc6497ef..97cef0c70d 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -2055,5 +2055,24 @@ "about an hour from now": "hemendik ordubetera", "%(num)s hours from now": "hemendik %(num)s ordutara", "about a day from now": "hemendik egun batera", - "%(num)s days from now": "hemendik %(num)s egunetara" + "%(num)s days from now": "hemendik %(num)s egunetara", + "New Session": "Saio berria", + "New invite dialog": "Gonbidapen elkarrizketa-koadro berria", + "Other users may not trust it": "Beste erabiltzaile batzuk ez fidagarritzat jo lezakete", + "Later": "Geroago", + "Failed to invite the following users to chat: %(csvUsers)s": "Ezin izan dira honako erabiltzaile hauek gonbidatu txatera: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Ezin izan dugu zure mezu zuena sortu. Egiaztatu gonbidatu nahi dituzun erabiltzaileak eta saiatu berriro.", + "Something went wrong trying to invite the users.": "Okerren bat egon da erabiltzaileak gonbidatzen saiatzean.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "Ezin izan ditugu erabiltzaile horiek gonbidatu. Egiaztatu gonbidatu nahi dituzun erabiltzaileak eta saiatu berriro.", + "Recently Direct Messaged": "Berriki mezu zuzena bidalita", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "Ez baduzu baten bat aurkitzen, eskatu bere erabiltzaile-izena (adib. @erabiltzailea:zerbitzaria.eus) edo partekatu gela hau.", + "Complete security": "Segurtasun osoa", + "Verify this session to grant it access to encrypted messages.": "Egiaztatu saio hau zifratutako mezuetara sarbidea emateko.", + "Start": "Hasi", + "Session verified": "Saioa egiaztatuta", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Zure saio berria orain egiaztatuta dago. Zure zifratutako mezuetara sarbidea du, eta beste erabiltzaileek fidagarri gisa ikusiko zaituzte.", + "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." } From 4d08cbecf79a1fbe9766a36d317cb2559bda2d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 18 Jan 2020 07:39:49 +0000 Subject: [PATCH 098/109] Translated using Weblate (French) 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/fr/ --- src/i18n/strings/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 3c70407ace..d1c4f7379c 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2072,5 +2072,8 @@ "Done": "Terminé", "Without completing security on this device, it won’t have access to encrypted messages.": "Si vous ne complétez pas la sécurité sur cet appareil, vous n’aurez pas accès aux messages chiffrés.", "Go Back": "Retourner en arrière", - "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." + "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" } From 1ec55b611f3f218997c1f3d6bc76a1ebcfd5d084 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 18 Jan 2020 08:27:11 +0000 Subject: [PATCH 099/109] Translated using Weblate (Hungarian) 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/hu/ --- src/i18n/strings/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index c131e77302..e5b9f637f1 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -269,7 +269,7 @@ "Show Text Formatting Toolbar": "Szöveg formázási eszköztár megjelenítése", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Az időbélyegek 12 órás formátumban mutatása (pl.: 2:30pm)", "Signed Out": "Kijelentkezett", - "Sign in": "Bejelentkezett", + "Sign in": "Bejelentkezés", "Sign out": "Kijelentkezés", "%(count)s of your messages have not been sent.|other": "Néhány üzeneted nem lett elküldve.", "Someone": "Valaki", From 0e2f90a31ef4ce460827c107d39a81fd2f9cf092 Mon Sep 17 00:00:00 2001 From: take100yen Date: Sat, 18 Jan 2020 14:37:38 +0000 Subject: [PATCH 100/109] Translated using Weblate (Japanese) Currently translated at 60.2% (1226 of 2035 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 | 54 +++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index c4c231249f..55cc8782ef 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -575,8 +575,8 @@ "New address (e.g. #foo:%(localDomain)s)": "新しいアドレス (例 #foo:%(localDomain)s)", "Invalid community ID": "無効なコミュニティID", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' は有効なコミュニティIDではありません", - "Showing flair for these communities:": "これらのコミュニティの特色を示す:", - "This room is not showing flair for any communities": "この部屋はどんなコミュニティに対しても特色を表示していません", + "Showing flair for these communities:": "次のコミュニティのバッジを表示:", + "This room is not showing flair for any communities": "この部屋はどんなコミュニティに対してもバッジを表示していません", "New community ID (e.g. +foo:%(localDomain)s)": "新しいコミュニティID (例 +foo:%(localDomain)s)", "You have enabled URL previews by default.": "デフォルトでURLプレビューが有効です。", "You have disabled URL previews by default.": "デフォルトでURLプレビューが無効です。", @@ -638,7 +638,7 @@ "Only visible to community members": "コミュニティメンバーにのみ表示されます", "Filter community rooms": "コミュニティルームを絞り込む", "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.": "表示するよう設定した部屋であなたのコミュニティ バッジを表示", "Show developer tools": "開発者ツールを表示", "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).": "匿名利用データを送信して、Riot.imの改善を支援してください。 これはCookieを使用します (クッキーポリシーをご覧ください)>。", @@ -713,8 +713,8 @@ "%(items)s and %(count)s others|other": "%(items)s と 他 %(count)s 回", "%(items)s and %(count)s others|one": "%(items)s と他の1つ", "%(items)s and %(lastItem)s": "%(items)s と %(lastItem)s", - "collapse": "崩壊", - "expand": "拡大する", + "collapse": "折りたたむ", + "expand": "展開", "Custom level": "カスタムレベル", "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 ": "返信 ", @@ -744,7 +744,7 @@ "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "これにより、あなたのアカウントは永久に使用できなくなります。ログインできなくなり、誰も同じユーザーIDを再登録できなくなります。これにより、参加しているすべてのルームから退室し、 IDサーバからあなたのアカウントの詳細が削除されます。この操作は元に戻すことができません。", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "アカウントを無効にしても、送信されたメッセージはデフォルトではなくなりません。メッセージを忘れてしまった場合は、下のボックスにチェックを入れてください。", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Matrixのメッセージの可視性は電子メールと似ています。メッセージを忘れると、新規または未登録のユーザーと共有することができませんが、既にこれらのメッセージにアクセスしている登録ユーザーは、依然としてそのコピーにアクセスできます。", - "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "アカウントを無効にしたときに送信したすべてのメッセージを忘れてください (警告:これにより、今後のユーザーは会話履歴の全文を見ることができなくなります)", + "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 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:": "この端末が信頼できることを確認するには、他の方法 (個人や電話など) で所有者に連絡し、端末のユーザー設定で表示される鍵が以下のキーと一致するかどうかを尋ねてください:", "Device name": "端末名", @@ -1017,7 +1017,7 @@ "bulleted-list": "bulleted-list", "numbered-list": "numbered-list", "People": "人々", - "Flair": "特色", + "Flair": "バッジ", "Fill screen": "フィルスクリーン", "Light theme": "明るいテーマ", "Dark theme": "暗いテーマ", @@ -1214,9 +1214,45 @@ "Bug reporting": "バグの報告", "FAQ": "よくある質問", "Versions": "バージョン", - "Key backup": "鍵のバックアップ", + "Key backup": "キーのバックアップ", "Voice & Video": "音声とビデオ", "Remove recent messages": "最近のメッセージを削除する", "%(creator)s created and configured the room.": "%(creator)s が部屋を作成して構成しました。", - "Add room": "部屋を追加" + "Add room": "部屋を追加", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)sがこの部屋に%(groups)sのバッジを追加しました。", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)sがこの部屋から%(groups)sのバッジを削除しました。", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)sが%(newGroups)sのバッジを追加し、%(oldGroups)sのバッジを削除しました。", + "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "キーが正常にバックアップされていない場合、暗号化されたメッセージにアクセスできなくなります。本当によろしいですか?", + "not stored": "保存されていません", + "All keys backed up": "すべてのキーがバックアップされました", + "Backup version: ": "バックアップのバージョン: ", + "Algorithm: ": "アルゴリズム: ", + "Backup key stored: ": "バックアップキーの保存: ", + "Back up your keys before signing out to avoid losing them.": "暗号化キーを失くさないために、サインアウトする前にキーをバックアップしてください。", + "Start using Key Backup": "キーのバックアップをはじめる", + "Error updating flair": "バッジの更新でエラーが発生しました。", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "この部屋のバッジの更新でエラーが発生しました。サーバーが許可していないか、一時的なエラーが発生しました。", + "Edited at %(date)s. Click to view edits.": "%(date)sに編集。クリックして編集を表示。", + "edited": "編集済", + "I don't want my encrypted messages": "暗号化されたメッセージは必要ありません", + "Manually export keys": "手動でキーをエクスポート", + "You'll lose access to your encrypted messages": "暗号化されたメッセージにアクセスできなくなります", + "You'll upgrade this room from to .": "このルームをからにアップグレードします。", + "Warning: You should only set up key backup from a trusted computer.": "警告: 信頼できるコンピュータからのみキーのバックアップをセットアップしてください。", + "For maximum security, this should be different from your account password.": "セキュリティの効果を高めるために、アカウントのパスワードと別のものを設定するべきです。", + "Enter a passphrase...": "パスワードを入力...", + "That matches!": "同じです!", + "Please enter your passphrase a second time to confirm.": "確認のために、パスワードをもう一度入力してください。", + "Repeat 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.": "リカバリキーは安全網です - パスワードを忘れた際は、リカバリキーを使って復元できます。", + "Copy to clipboard": "クリップボードにコピー", + "Download": "ダウンロード", + "Your recovery key has been copied to your clipboard, paste it to:": "リカバリキーがクリップボードにコピーされました。ペーストして:", + "Print it and store it somewhere safe": "印刷して安全な場所に保管しましょう", + "Save it on a USB key or backup drive": "USB メモリーやバックアップ用のドライブに保存しましょう", + "Copy it to your personal cloud storage": "個人のクラウドストレージにコピーしましょう", + "Confirm your passphrase": "パスワードを確認", + "Recovery key": "リカバリキー", + "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "キーの暗号化されたコピーがサーバーに保存されます。バックアップを保護するために、パスワードを設定してください。", + "Secure your backup with a passphrase": "バックアップをパスワードで保護" } From 931cace323f6abfe4429b2dc5e5fce87b362b648 Mon Sep 17 00:00:00 2001 From: catborise Date: Sat, 18 Jan 2020 09:17:19 +0000 Subject: [PATCH 101/109] Translated using Weblate (Turkish) Currently translated at 66.5% (1354 of 2035 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, 21 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index af4bab9689..52e837637b 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -8,7 +8,7 @@ "Add": "Ekle", "Add a topic": "Bir konu(topic) ekle", "Admin": "Admin", - "Admin Tools": "Admin araçları", + "Admin Tools": "Admin Araçları", "No Microphones detected": "Hiçbir Mikrofon bulunamadı", "No Webcams detected": "Hiçbir Web kamerası bulunamadı", "No media permissions": "Medya izinleri yok", @@ -344,7 +344,7 @@ "You cannot place VoIP calls in this browser.": "Bu tarayıcıda VoIP çağrısı yapamazsınız.", "You do not have permission to post to this room": "Bu odaya göndermeye izniniz yok", "You have disabled URL previews by default.": "URL önizlemelerini varsayılan olarak devre dışı bıraktınız.", - "You have enabled URL previews by default.": "URL önizlemelerini varsayılan olarak etkinleştirdiniz .", + "You have enabled URL previews by default.": "URL önizlemelerini varsayılan olarak etkinleştirdiniz.", "You have no visible notifications": "Hiçbir görünür bildiriminiz yok", "You must register to use this functionality": "Bu işlevi kullanmak için Kayıt Olun ", "You need to be able to invite users to do that.": "Bunu yapmak için kullanıcıları davet etmeye ihtiyacınız var.", @@ -1348,5 +1348,23 @@ "Remove recent messages": "Son mesajları sil", "The conversation continues here.": "Sohbet buradan devam ediyor.", "You can only join it with a working invite.": "Sadece çalışan bir davet ile katılınabilir.", - "Try to join anyway": "Katılmak için yinede deneyin" + "Try to join anyway": "Katılmak için yinede deneyin", + "Preferences": "Tercihler", + "Timeline": "Zaman Çizelgesi", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "%(user)s tarafından gönderilen 1 mesajı silmek üzeresiniz. Bu işlem geri alınamaz. Devam etmek istediğinize emin misiniz?", + "This room has been replaced and is no longer active.": "Bu oda değiştirildi ve artık aktif değil.", + "Idle for %(duration)s": "%(duration)s süresince boşta", + "You were kicked from %(roomName)s by %(memberName)s": "%(memberName)s tarafından %(roomName)s dan kovuldunuz", + "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s tarafından %(roomName)s odası size yasaklandı", + "Something went wrong with your invite to %(roomName)s": "%(roomName)s odasına davet işleminizde birşeyler yanlış gitti", + "This invite to %(roomName)s was sent to %(email)s": "%(roomName)s odası daveti %(email)s adresine gönderildi", + "You're previewing %(roomName)s. Want to join it?": "%(roomName)s odasını inceliyorsunuz. Katılmak ister misiniz?", + "You don't currently have any stickerpacks enabled": "Açılmış herhangi bir çıkartma paketine sahip değilsiniz", + "Error creating alias": "Lakap oluştururken hata", + "Room Topic": "Oda Başlığı", + "Verify this session to grant it access to encrypted messages.": "Şifrelenmiş mesajlara erişmek için bu oturumu doğrula.", + "Start": "Başlat", + "Session verified": "Oturum doğrulandı", + "Done": "Bitti", + "Go Back": "Geri dön" } From 93b6040291556a0cac2d5804008330001d9d1996 Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 19 Jan 2020 17:14:38 +0000 Subject: [PATCH 102/109] Translated using Weblate (Turkish) Currently translated at 69.3% (1410 of 2035 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 | 58 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 52e837637b..81f0522922 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -1366,5 +1366,61 @@ "Start": "Başlat", "Session verified": "Oturum doğrulandı", "Done": "Bitti", - "Go Back": "Geri dön" + "Go Back": "Geri dön", + "New Session": "Yeni Oturum", + "Gets or sets the room topic": "Oda başlığını getirir yada ayarlar", + "Unbans user with given ID": "Verilen ID ile kullanıcı yasağını kaldırır", + "Ignores a user, hiding their messages from you": "Mesajlarını senden gizleyerek, bir kullanıcıyı yok sayar", + "Ignored user": "Yoksayılan kullanıcı", + "You are now ignoring %(userId)s": "Şimdi %(userId)s yı yoksayıyorsunuz", + "Stops ignoring a user, showing their messages going forward": "Sonraki mesajlarını göstererek, bir kullanıcıyı yoksaymaktan vazgeç", + "Adds a custom widget by URL to the room": "URL ile odaya özel bir görsel bileşen ekle", + "Verifies a user, device, and pubkey tuple": "Bir kullanıcıyı, cihazı ve açık anahtar ikilisini doğrular", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s misafirlerin odaya katılmasına izin verdi.", + "%(senderName)s updated an invalid ban rule": "%(senderName)s bir geçersiz yasaklama kuralını güncelledi", + "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen yasaklama kuralını güncelledi", + "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen kullanıcıları yasaklama kuralı oluşturdu", + "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir oda yasaklama kuralı oluşturdu", + "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir sunucular yasaklama kuralı oluşturdu", + "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir yasak kuralı oluşturdu", + "Ensure you have a stable internet connection, or get in touch with the server admin": "Kararlı bir internet bağlantısına sahip olduğunuzdan emin olun yada sunucu yöneticisi ile iletişime geçin", + "Please contact your service administrator to continue using the service.": "Hizmeti kullanmaya devam etmek için, lütfen hizmet yöneticiniz ile bağlantıya geçin.", + "a few seconds ago": "bir kaç saniye önce", + "about a minute ago": "yaklaşık bir dakika önce", + "%(num)s minutes ago": "%(num)s dakika önce", + "about an hour ago": "yaklaşık bir saat önce", + "%(num)s hours ago": "%(num)s saat önce", + "about a day ago": "yaklaşık bir gün önce", + "%(num)s days ago": "%(num)s gün önce", + "The user's homeserver does not support the version of the room.": "Kullanıcının ana sunucusu odanın sürümünü desteklemiyor.", + "Unknown server error": "Bilinmeyen sunucu hatası", + "Use a few words, avoid common phrases": "Bir kaç kelime kullanın ve genel ifadelerden kaçının", + "No need for symbols, digits, or uppercase letters": "Semboller, sayılar yada büyük harflere gerek yok", + "Avoid repeated words and characters": "Tekrarlanan kelimeler ve karakterlerden kaçının", + "Avoid sequences": "Sekanslardan kaçının", + "Avoid recent years": "Son yıllardan kaçının", + "Avoid years that are associated with you": "Sizle ilişkili yıllardan kaçının", + "Avoid dates and years that are associated with you": "Sizle ilişkili tarihler ve yıllardan kaçının", + "Capitalization doesn't help very much": "Baş harfi büyük yapmak size pek yardımcı olmaz", + "All-uppercase is almost as easy to guess as all-lowercase": "Bütün harflerin büyük olmasıyla tümünün küçük olması tahmin için hemen hemen aynı kolaylıktadır", + "Reversed words aren't much harder to guess": "Ters kelimeler tahmin için çok zor değil", + "Repeats like \"aaa\" are easy to guess": "“aaa” gibi tekrarlar tahmin için oldukça kolay", + "Dates are often easy to guess": "Tarihler sıklıkla tahmin için daha kolaydır", + "This is a top-10 common password": "Bu bir top-10 yaygın parola", + "This is a top-100 common password": "Bu bir top-100 yaygın parola", + "This is a very common password": "Bu oldukça yaygın parola", + "This is similar to a commonly used password": "Bu yaygınca kullanılan bir parolaya benziyor", + "Names and surnames by themselves are easy to guess": "Adlar ve soyadlar kendi kendilerine tahmin için kolaydır", + "There was an error joining the room": "Odaya katılırken bir hata oluştu", + "Custom user status messages": "Özel kullanıcı durum mesajları", + "Group & filter rooms by custom tags (refresh to apply changes)": "Özel etiketler ile odaları grupla & filtrele ( değişiklikleri uygulamak için yenile)", + "Render simple counters in room header": "Oda başlığında basit sayaçları görüntüle", + "Try out new ways to ignore people (experimental)": "Kişileri yoksaymak için yeni yöntemleri dene (deneysel)", + "New invite dialog": "Yeni davet diyalogu", + "Enable local event indexing and E2EE search (requires restart)": "E2EE arama ve yerel olay indeksini aç (yeniden başlatma gerekli)", + "Mirror local video feed": "Yerel video beslemesi yansısı", + "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." } From 15749621a51d797d61960bb83d70f64c1f3c43a8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 20 Jan 2020 12:06:43 +0000 Subject: [PATCH 103/109] Fix rageshake submission after build changes We aren't able to depend on `require` in this context anymore. Fixes https://github.com/vector-im/riot-web/issues/11938 --- .../views/dialogs/BugReportDialog.js | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/views/dialogs/BugReportDialog.js b/src/components/views/dialogs/BugReportDialog.js index ccb332fa60..fe95041373 100644 --- a/src/components/views/dialogs/BugReportDialog.js +++ b/src/components/views/dialogs/BugReportDialog.js @@ -23,6 +23,7 @@ import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; +import sendBugReport from '../../../rageshake/submit-rageshake'; export default class BugReportDialog extends React.Component { constructor(props) { @@ -67,32 +68,30 @@ export default class BugReportDialog extends React.Component { this.setState({ busy: true, progress: null, err: null }); this._sendProgressCallback(_t("Preparing to send logs")); - require(['../../../rageshake/submit-rageshake'], (s) => { - s(SdkConfig.get().bug_report_endpoint_url, { - userText, - sendLogs: true, - progressCallback: this._sendProgressCallback, - label: this.props.label, - }).then(() => { - if (!this._unmounted) { - this.props.onFinished(false); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - // N.B. first param is passed to piwik and so doesn't want i18n - Modal.createTrackedDialog('Bug report sent', '', QuestionDialog, { - title: _t('Logs sent'), - description: _t('Thank you!'), - hasCancelButton: false, - }); - } - }, (err) => { - if (!this._unmounted) { - this.setState({ - busy: false, - progress: null, - err: _t("Failed to send logs: ") + `${err.message}`, - }); - } - }); + sendBugReport(SdkConfig.get().bug_report_endpoint_url, { + userText, + sendLogs: true, + progressCallback: this._sendProgressCallback, + label: this.props.label, + }).then(() => { + if (!this._unmounted) { + this.props.onFinished(false); + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + // N.B. first param is passed to piwik and so doesn't want i18n + Modal.createTrackedDialog('Bug report sent', '', QuestionDialog, { + title: _t('Logs sent'), + description: _t('Thank you!'), + hasCancelButton: false, + }); + } + }, (err) => { + if (!this._unmounted) { + this.setState({ + busy: false, + progress: null, + err: _t("Failed to send logs: ") + `${err.message}`, + }); + } }); } From 2ed5d89c9fca8f4449b1cc32f8aa72dcfe39cb5a Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 12:20:21 +0000 Subject: [PATCH 104/109] Fix arrows keys moving through edit history Different fix that fixes https://github.com/vector-im/riot-web/issues/11817 by setting the flag before the callback rather than having the update method set the flag. Regressed in https://github.com/matrix-org/matrix-react-sdk/pull/3842 Fixes https://github.com/vector-im/riot-web/issues/11917 --- src/components/views/rooms/BasicMessageComposer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 15a7c29e3a..e54396a9ed 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -126,7 +126,6 @@ export default class BasicMessageEditor extends React.Component { } _updateEditorState = (selection, inputType, diff) => { - this._modifiedFlag = true; renderModel(this._editorRef, this.props.model); if (selection) { // set the caret/selection try { @@ -212,6 +211,7 @@ export default class BasicMessageEditor extends React.Component { if (type === "cut") { // Remove the text, updating the model as appropriate replaceRangeAndMoveCaret(range, []); + this._modifiedFlag = true; } event.preventDefault(); } @@ -238,6 +238,7 @@ export default class BasicMessageEditor extends React.Component { const text = event.clipboardData.getData("text/plain"); parts = parsePlainTextMessage(text, partCreator); } + this._modifiedFlag = true; const range = getRangeForSelection(this._editorRef, model, document.getSelection()); replaceRangeAndMoveCaret(range, parts); event.preventDefault(); @@ -248,6 +249,7 @@ export default class BasicMessageEditor extends React.Component { if (this._isIMEComposing) { return; } + this._modifiedFlag = true; const sel = document.getSelection(); const {caret, text} = getCaretOffsetAndText(this._editorRef, sel); this.props.model.update(text, event.inputType, caret); @@ -258,6 +260,7 @@ export default class BasicMessageEditor extends React.Component { const {caret, text} = getCaretOffsetAndText(this._editorRef, sel); const newText = text.substr(0, caret.offset) + textToInsert + text.substr(caret.offset); caret.offset += textToInsert.length; + this._modifiedFlag = true; this.props.model.update(newText, inputType, caret); } From 277f10792481d78881695df9af30f8a9e3ddb265 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 13:03:09 +0000 Subject: [PATCH 105/109] Set flag before mutating on cut too --- src/components/views/rooms/BasicMessageComposer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index e54396a9ed..94904242c3 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -210,8 +210,8 @@ export default class BasicMessageEditor extends React.Component { event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts)); if (type === "cut") { // Remove the text, updating the model as appropriate - replaceRangeAndMoveCaret(range, []); this._modifiedFlag = true; + replaceRangeAndMoveCaret(range, []); } event.preventDefault(); } From 34c69a59b2437e99f3997a20cf2a07f393bfd675 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 15:26:34 +0000 Subject: [PATCH 106/109] 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 d4e58c80e4..aa2cf8bf8b 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 551b2907d841e4f0fcb9c58377e950330f00862f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 09:29:33 -0700 Subject: [PATCH 107/109] Fix variable usage and naming --- src/components/views/dialogs/InviteDialog.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 02a96f7f37..251fb62d2c 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -359,8 +359,8 @@ export default class InviteDialog extends React.PureComponent { _buildSuggestions(excludedTargetIds: string[]): {userId: string, user: RoomMember} { const maxConsideredMembers = 200; - const client = MatrixClientPeg.get(); - const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']]; + const myUserId = MatrixClientPeg.get().getUserId(); + const excludedUserIds = [myUserId, SdkConfig.get()['welcomeUserId']]; const joinedRooms = client.getRooms() .filter(r => r.getMyMembership() === 'join') .filter(r => r.getJoinedMemberCount() <= maxConsideredMembers); @@ -418,7 +418,7 @@ export default class InviteDialog extends React.PureComponent { // which are closer to "continue this conversation" rather than "this person exists". const trueJoinedRooms = client.getRooms().filter(r => r.getMyMembership() === 'join'); const now = (new Date()).getTime(); - const maxAgeConsidered = now - (60 * 60 * 1000); // 1 hour ago + const earliestAgeConsidered = now - (60 * 60 * 1000); // 1 hour ago const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic const lastSpoke = {}; // userId: timestamp const lastSpokeMembers = {}; // userId: room member @@ -432,10 +432,10 @@ export default class InviteDialog extends React.PureComponent { const events = room.getLiveTimeline().getEvents(); // timelines are most recent last for (let i = events.length - 1; i >= Math.max(0, events.length - maxMessagesConsidered); i--) { const ev = events[i]; - if (ev.getSender() === MatrixClientPeg.get().getUserId() || excludedUserIds.includes(ev.getSender())) { + if (ev.getSender() === myUserId || excludedUserIds.includes(ev.getSender())) { continue; } - if (ev.getTs() <= maxAgeConsidered) { + if (ev.getTs() <= earliestAgeConsidered) { break; // give up: all events from here on out are too old } @@ -454,7 +454,7 @@ export default class InviteDialog extends React.PureComponent { // boost we'll try and award at least +1.0 for making the list, with +4.0 being // an approximate maximum for being selected. const distanceFromNow = Math.abs(now - ts); // abs to account for slight future messages - const inverseTime = (now - maxAgeConsidered) - distanceFromNow; + const inverseTime = (now - earliestAgeConsidered) - distanceFromNow; const scoreBoost = Math.max(1, inverseTime / (15 * 60 * 1000)); // 15min segments to keep scores sane let record = memberScores[userId]; From 727ca8ba7792b11c337e0460a05530fd41aef69f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 10:04:14 -0700 Subject: [PATCH 108/109] Don't double check ourselves --- src/components/views/dialogs/InviteDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 251fb62d2c..94d4214fd4 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -432,7 +432,7 @@ export default class InviteDialog extends React.PureComponent { const events = room.getLiveTimeline().getEvents(); // timelines are most recent last for (let i = events.length - 1; i >= Math.max(0, events.length - maxMessagesConsidered); i--) { const ev = events[i]; - if (ev.getSender() === myUserId || excludedUserIds.includes(ev.getSender())) { + if (excludedUserIds.includes(ev.getSender())) { continue; } if (ev.getTs() <= earliestAgeConsidered) { From 7c877fb9c4b50935bb3dd8849c8861ec688d02da Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 10:08:35 -0700 Subject: [PATCH 109/109] Reinstate client variable that is actually used --- src/components/views/dialogs/InviteDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 94d4214fd4..703b0b5121 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -359,8 +359,8 @@ export default class InviteDialog extends React.PureComponent { _buildSuggestions(excludedTargetIds: string[]): {userId: string, user: RoomMember} { const maxConsideredMembers = 200; - const myUserId = MatrixClientPeg.get().getUserId(); - const excludedUserIds = [myUserId, SdkConfig.get()['welcomeUserId']]; + const client = MatrixClientPeg.get(); + const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']]; const joinedRooms = client.getRooms() .filter(r => r.getMyMembership() === 'join') .filter(r => r.getJoinedMemberCount() <= maxConsideredMembers);