From 7c66d1c8673f0a67282d2cfeeb9e98b94b05af4d Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 24 Jan 2017 16:01:39 +0000 Subject: [PATCH 01/12] Sync typing indication with avatar typing indication Follow the same rules for displaying "is typing" as with the typing avatars. --- src/WhoIsTyping.js | 19 ++++++++++++------- src/components/structures/RoomStatusBar.js | 8 ++++++-- src/components/structures/RoomView.js | 1 + 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.js index 8c3838d615..f8e3f1c7fd 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.js @@ -32,17 +32,22 @@ module.exports = { return whoIsTyping; }, - whoIsTypingString: function(room) { - var whoIsTyping = this.usersTypingApartFromMe(room); + whoIsTypingString: function(room, limit) { + const whoIsTyping = this.usersTypingApartFromMe(room); + const othersCount = limit === undefined ? 0 : Math.max(whoIsTyping.length - limit, 0); if (whoIsTyping.length == 0) { - return null; + return ''; } else if (whoIsTyping.length == 1) { return whoIsTyping[0].name + ' is typing'; + } + const names = whoIsTyping.map(function(m) { + return m.name; + }); + if (othersCount) { + const other = ' other' + (othersCount > 1 ? 's' : ''); + return names.slice(0, limit).join(', ') + ' and ' + othersCount + other + ' are typing'; } else { - var names = whoIsTyping.map(function(m) { - return m.name; - }); - var lastPerson = names.shift(); + const lastPerson = names.pop(); return names.join(', ') + ' and ' + lastPerson + ' are typing'; } } diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index c80c4db7cc..a691196219 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -53,6 +53,10 @@ module.exports = React.createClass({ // more interesting) hasActiveCall: React.PropTypes.bool, + // Number of names to display in typing indication. E.g. set to 3, will + // result in "X, Y, Z and 100 others are typing." + whoIsTypingLimit: React.PropTypes.number, + // callback for when the user clicks on the 'resend all' button in the // 'unsent messages' bar onResendAllClick: React.PropTypes.func, @@ -80,7 +84,7 @@ module.exports = React.createClass({ getInitialState: function() { return { syncState: MatrixClientPeg.get().getSyncState(), - whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room), + whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room, this.props.whoIsTypingLimit), }; }, @@ -127,7 +131,7 @@ module.exports = React.createClass({ onRoomMemberTyping: function(ev, member) { this.setState({ - whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room), + whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room, this.props.whoIsTypingLimit), }); }, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8753540e48..b7f05de339 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1531,6 +1531,7 @@ module.exports = React.createClass({ onResize={this.onChildResize} onVisible={this.onStatusBarVisible} onHidden={this.onStatusBarHidden} + whoIsTypingLimit={2} />; } From 9a360a48d21a08df3ee5bea3752a2ba2933d3834 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 24 Jan 2017 16:04:37 +0000 Subject: [PATCH 02/12] Use the same property to limit avatars --- src/components/structures/RoomStatusBar.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index a691196219..59ff3c8f23 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -21,8 +21,6 @@ var WhoIsTyping = require("../../WhoIsTyping"); var MatrixClientPeg = require("../../MatrixClientPeg"); const MemberAvatar = require("../views/avatars/MemberAvatar"); -const TYPING_AVATARS_LIMIT = 2; - const HIDE_DEBOUNCE_MS = 10000; const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; @@ -198,7 +196,7 @@ module.exports = React.createClass({ if (wantPlaceholder) { return (
- {this._renderTypingIndicatorAvatars(TYPING_AVATARS_LIMIT)} + {this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit)}
); } From 4186a769ca46956c7dae96d2324fb66002ce73e8 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 24 Jan 2017 17:16:26 +0000 Subject: [PATCH 03/12] Default prop for whoIsTypingLimit --- src/components/structures/RoomStatusBar.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 59ff3c8f23..212d0d5ee6 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -79,6 +79,12 @@ module.exports = React.createClass({ onVisible: React.PropTypes.func, }, + getDefaultProps: function() { + return { + whoIsTypingLimit: 2, + }; + }, + getInitialState: function() { return { syncState: MatrixClientPeg.get().getSyncState(), From a92fff9da7357df35415bfd7cc6bbabdde88396c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 24 Jan 2017 17:18:56 +0000 Subject: [PATCH 04/12] Fix linting warnings --- src/WhoIsTyping.js | 6 ++++-- src/components/structures/RoomStatusBar.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.js index f8e3f1c7fd..96e76d618b 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.js @@ -34,7 +34,8 @@ module.exports = { whoIsTypingString: function(room, limit) { const whoIsTyping = this.usersTypingApartFromMe(room); - const othersCount = limit === undefined ? 0 : Math.max(whoIsTyping.length - limit, 0); + const othersCount = limit === undefined ? + 0 : Math.max(whoIsTyping.length - limit, 0); if (whoIsTyping.length == 0) { return ''; } else if (whoIsTyping.length == 1) { @@ -45,7 +46,8 @@ module.exports = { }); if (othersCount) { const other = ' other' + (othersCount > 1 ? 's' : ''); - return names.slice(0, limit).join(', ') + ' and ' + othersCount + other + ' are typing'; + return names.slice(0, limit).join(', ') + ' and ' + + othersCount + other + ' are typing'; } else { const lastPerson = names.pop(); return names.join(', ') + ' and ' + lastPerson + ' are typing'; diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 212d0d5ee6..3ba73bb181 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -88,7 +88,10 @@ module.exports = React.createClass({ getInitialState: function() { return { syncState: MatrixClientPeg.get().getSyncState(), - whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room, this.props.whoIsTypingLimit), + whoisTypingString: WhoIsTyping.whoIsTypingString( + this.props.room, + this.props.whoIsTypingLimit + ), }; }, @@ -135,7 +138,10 @@ module.exports = React.createClass({ onRoomMemberTyping: function(ev, member) { this.setState({ - whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room, this.props.whoIsTypingLimit), + whoisTypingString: WhoIsTyping.whoIsTypingString( + this.props.room, + this.props.whoIsTypingLimit + ), }); }, From 56cf7a6af7a9a86ae0cabd535e865ee672e536d7 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 24 Jan 2017 15:54:05 +0000 Subject: [PATCH 05/12] Create a common BaseDialog I'm fed up with copying the boilerplate for modal dialogs the whole time. --- src/KeyCode.js | 1 + src/component-index.js | 2 + src/components/views/dialogs/BaseDialog.js | 72 +++++++++++++++++++ src/components/views/dialogs/ErrorDialog.js | 24 +++---- .../views/dialogs/InteractiveAuthDialog.js | 29 +++----- .../views/dialogs/NeedToRegisterDialog.js | 18 ++--- .../views/dialogs/QuestionDialog.js | 31 +++----- .../views/dialogs/SetDisplayNameDialog.js | 21 +++--- .../views/dialogs/TextInputDialog.js | 35 ++++----- 9 files changed, 136 insertions(+), 97 deletions(-) create mode 100644 src/components/views/dialogs/BaseDialog.js diff --git a/src/KeyCode.js b/src/KeyCode.js index bbe1ddcefa..c9cac01239 100644 --- a/src/KeyCode.js +++ b/src/KeyCode.js @@ -20,6 +20,7 @@ module.exports = { TAB: 9, ENTER: 13, SHIFT: 16, + ESCAPE: 27, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, diff --git a/src/component-index.js b/src/component-index.js index e83de8739d..bdce944e28 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -71,6 +71,8 @@ import views$create_room$Presets from './components/views/create_room/Presets'; views$create_room$Presets && (module.exports.components['views.create_room.Presets'] = views$create_room$Presets); import views$create_room$RoomAlias from './components/views/create_room/RoomAlias'; views$create_room$RoomAlias && (module.exports.components['views.create_room.RoomAlias'] = views$create_room$RoomAlias); +import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog'; +views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog); import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog'; views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog); import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog'; diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js new file mode 100644 index 0000000000..2b3980c536 --- /dev/null +++ b/src/components/views/dialogs/BaseDialog.js @@ -0,0 +1,72 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import * as KeyCode from '../../../KeyCode'; + +/** + * Basic container for modal dialogs. + * + * Includes a div for the title, and a keypress handler which cancels the + * dialog on escape. + */ +export default React.createClass({ + displayName: 'BaseDialog', + + propTypes: { + // onFinished callback to call when Escape is pressed + onFinished: React.PropTypes.func.isRequired, + + // callback to call when Enter is pressed + onEnterPressed: React.PropTypes.func, + + // CSS class to apply to dialog div + className: React.PropTypes.string, + + // Title for the dialog. + // (could probably actually be something more complicated than a string if desired) + title: React.PropTypes.string.isRequired, + + // children should be the content of the dialog + children: React.PropTypes.node, + }, + + _onKeyDown: function(e) { + if (e.keyCode === KeyCode.ESCAPE) { + e.stopPropagation(); + e.preventDefault(); + this.props.onFinished(); + } else if (e.keyCode === KeyCode.ENTER) { + if (this.props.onEnterPressed) { + e.stopPropagation(); + e.preventDefault(); + this.props.onEnterPressed(e); + } + } + }, + + render: function() { + return ( +
+
+ { this.props.title } +
+ { this.props.children } +
+ ); + }, +}); diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index ed48f10fd7..937595dfa8 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -25,9 +25,10 @@ limitations under the License. * }); */ -var React = require("react"); +import React from 'react'; +import sdk from '../../../index'; -module.exports = React.createClass({ +export default React.createClass({ displayName: 'ErrorDialog', propTypes: { title: React.PropTypes.string, @@ -49,20 +50,11 @@ module.exports = React.createClass({ }; }, - onKeyDown: function(e) { - if (e.keyCode === 27) { // escape - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(false); - } - }, - render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
-
- {this.props.title} -
+
{this.props.description}
@@ -71,7 +63,7 @@ module.exports = React.createClass({ {this.props.button}
- + ); - } + }, }); diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js index 301bba0486..a4abbb17d9 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.js +++ b/src/components/views/dialogs/InteractiveAuthDialog.js @@ -111,20 +111,9 @@ export default React.createClass({ }); }, - _onKeyDown: function(e) { - if (e.keyCode === 27) { // escape - e.stopPropagation(); - e.preventDefault(); - if (!this.state.busy) { - this._onCancel(); - } - } - else if (e.keyCode === 13) { // enter - e.stopPropagation(); - e.preventDefault(); - if (this.state.submitButtonEnabled && !this.state.busy) { - this._onSubmit(); - } + _onEnterPressed: function(e) { + if (this.state.submitButtonEnabled && !this.state.busy) { + this._onSubmit(); } }, @@ -171,6 +160,7 @@ export default React.createClass({ render: function() { const Loader = sdk.getComponent("elements.Spinner"); + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); let error = null; if (this.state.errorText) { @@ -200,10 +190,11 @@ export default React.createClass({ ); return ( -
-
- {this.props.title} -
+

This operation requires additional authentication.

{this._renderCurrentStage()} @@ -213,7 +204,7 @@ export default React.createClass({ {submitButton} {cancelButton}
-
+ ); }, }); diff --git a/src/components/views/dialogs/NeedToRegisterDialog.js b/src/components/views/dialogs/NeedToRegisterDialog.js index 0080e0c643..f4df5913d5 100644 --- a/src/components/views/dialogs/NeedToRegisterDialog.js +++ b/src/components/views/dialogs/NeedToRegisterDialog.js @@ -23,8 +23,9 @@ limitations under the License. * }); */ -var React = require("react"); -var dis = require("../../../dispatcher"); +import React from 'react'; +import dis from '../../../dispatcher'; +import sdk from '../../../index'; module.exports = React.createClass({ displayName: 'NeedToRegisterDialog', @@ -54,11 +55,12 @@ module.exports = React.createClass({ }, render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
-
- {this.props.title} -
+
{this.props.description}
@@ -70,7 +72,7 @@ module.exports = React.createClass({ Register
- + ); - } + }, }); diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index 1cd4d047fd..3f7f237c30 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -var React = require("react"); +import React from 'react'; +import sdk from '../../../index'; -module.exports = React.createClass({ +export default React.createClass({ displayName: 'QuestionDialog', propTypes: { title: React.PropTypes.string, @@ -46,25 +47,13 @@ module.exports = React.createClass({ this.props.onFinished(false); }, - onKeyDown: function(e) { - if (e.keyCode === 27) { // escape - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(false); - } - else if (e.keyCode === 13) { // enter - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(true); - } - }, - render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
-
- {this.props.title} -
+
{this.props.description}
@@ -77,7 +66,7 @@ module.exports = React.createClass({ Cancel
- + ); - } + }, }); diff --git a/src/components/views/dialogs/SetDisplayNameDialog.js b/src/components/views/dialogs/SetDisplayNameDialog.js index c1041cc218..9e44671e4a 100644 --- a/src/components/views/dialogs/SetDisplayNameDialog.js +++ b/src/components/views/dialogs/SetDisplayNameDialog.js @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -var React = require("react"); -var sdk = require("../../../index.js"); -var MatrixClientPeg = require("../../../MatrixClientPeg"); +import React from 'react'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; -module.exports = React.createClass({ +export default React.createClass({ displayName: 'SetDisplayNameDialog', propTypes: { onFinished: React.PropTypes.func.isRequired, @@ -59,11 +59,12 @@ module.exports = React.createClass({ }, render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
-
- Set a Display Name -
+
Your display name is how you'll appear to others when you speak in rooms.
What would you like it to be? @@ -79,7 +80,7 @@ module.exports = React.createClass({
-
+ ); - } + }, }); diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js index 6245b5786f..6e40efffd8 100644 --- a/src/components/views/dialogs/TextInputDialog.js +++ b/src/components/views/dialogs/TextInputDialog.js @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -var React = require("react"); +import React from 'react'; +import sdk from '../../../index'; -module.exports = React.createClass({ +export default React.createClass({ displayName: 'TextInputDialog', propTypes: { title: React.PropTypes.string, @@ -27,7 +28,7 @@ module.exports = React.createClass({ value: React.PropTypes.string, button: React.PropTypes.string, focus: React.PropTypes.bool, - onFinished: React.PropTypes.func.isRequired + onFinished: React.PropTypes.func.isRequired, }, getDefaultProps: function() { @@ -36,7 +37,7 @@ module.exports = React.createClass({ value: "", description: "", button: "OK", - focus: true + focus: true, }; }, @@ -55,25 +56,13 @@ module.exports = React.createClass({ this.props.onFinished(false); }, - onKeyDown: function(e) { - if (e.keyCode === 27) { // escape - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(false); - } - else if (e.keyCode === 13) { // enter - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(true, this.refs.textinput.value); - } - }, - render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
-
- {this.props.title} -
+
@@ -90,7 +79,7 @@ module.exports = React.createClass({ {this.props.button}
-
+
); - } + }, }); From 5b61d00533a1b73ad0e2f634a3a13bf98630d9a7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Jan 2017 22:36:55 +0100 Subject: [PATCH 06/12] warn users that changing/resetting password will nuke E2E keys --- .../structures/login/ForgotPassword.js | 24 +++++++-- .../views/settings/ChangePassword.js | 53 ++++++++++++------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 5037136b1d..2c10052b98 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -87,10 +87,26 @@ module.exports = React.createClass({ this.showErrorDialog("New passwords must match each other."); } else { - this.submitPasswordReset( - this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl, - this.state.email, this.state.password - ); + var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createDialog(QuestionDialog, { + title: "Warning", + description: +
+ Resetting password will currently reset any end-to-end encryption keys on all devices, + making encrypted chat history unreadable. + In future this may be improved, + but for now be warned. +
, + button: "Continue", + onFinished: (confirmed) => { + if (confirmed) { + this.submitPasswordReset( + this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl, + this.state.email, this.state.password + ); + } + }, + }); } }, diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index a011d5262e..8a3c46bcfd 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); +var Modal = require("../../../Modal"); var sdk = require("../../../index"); module.exports = React.createClass({ @@ -65,26 +66,42 @@ module.exports = React.createClass({ changePassword: function(old_password, new_password) { var cli = MatrixClientPeg.get(); - var authDict = { - type: 'm.login.password', - user: cli.credentials.userId, - password: old_password - }; + var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createDialog(QuestionDialog, { + title: "Warning", + description: +
+ Changing password will currently reset any end-to-end encryption keys on all devices, + making encrypted chat history unreadable. + This will be improved shortly, + but for now be warned. +
, + button: "Continue", + onFinished: (confirmed) => { + if (confirmed) { + var authDict = { + type: 'm.login.password', + user: cli.credentials.userId, + password: old_password + }; - this.setState({ - phase: this.Phases.Uploading + this.setState({ + phase: this.Phases.Uploading + }); + + var self = this; + cli.setPassword(authDict, new_password).then(function() { + self.props.onFinished(); + }, function(err) { + self.props.onError(err); + }).finally(function() { + self.setState({ + phase: self.Phases.Edit + }); + }).done(); + } + }, }); - - var self = this; - cli.setPassword(authDict, new_password).then(function() { - self.props.onFinished(); - }, function(err) { - self.props.onError(err); - }).finally(function() { - self.setState({ - phase: self.Phases.Edit - }); - }).done(); }, onClickChange: function() { From b148619c527b4ee416b6e886846841d692a816ca Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Jan 2017 22:47:03 +0100 Subject: [PATCH 07/12] warn on logout too --- src/Lifecycle.js | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 493bbf12aa..64d22bd04c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -22,6 +22,7 @@ import Notifier from './Notifier'; import UserActivity from './UserActivity'; import Presence from './Presence'; import dis from './dispatcher'; +import Modal from './Modal'; import DMRoomMap from './utils/DMRoomMap'; /** @@ -289,19 +290,35 @@ export function logout() { return; } - return MatrixClientPeg.get().logout().then(onLoggedOut, - (err) => { - // Just throwing an error here is going to be very unhelpful - // if you're trying to log out because your server's down and - // you want to log into a different server, so just forget the - // access token. It's annoying that this will leave the access - // token still valid, but we should fix this by having access - // tokens expire (and if you really think you've been compromised, - // change your password). - console.log("Failed to call logout API: token will not be invalidated"); - onLoggedOut(); - } - ); + var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createDialog(QuestionDialog, { + title: "Warning", + description: +
+ For security, logging out will delete any end-to-end encryption keys from this browser, + making previous encrypted chat history unreadable if you log back in. + In future this will be improved, + but for now be warned. +
, + button: "Continue", + onFinished: (confirmed) => { + if (confirmed) { + MatrixClientPeg.get().logout().then(onLoggedOut, + (err) => { + // Just throwing an error here is going to be very unhelpful + // if you're trying to log out because your server's down and + // you want to log into a different server, so just forget the + // access token. It's annoying that this will leave the access + // token still valid, but we should fix this by having access + // tokens expire (and if you really think you've been compromised, + // change your password). + console.log("Failed to call logout API: token will not be invalidated"); + onLoggedOut(); + } + ); + } + }, + }); } /** From 6e55bb4956cd0e5d62352970f3f9ba892bd6b7fb Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Jan 2017 23:15:00 +0100 Subject: [PATCH 08/12] actually, move signout warning to UserSettings.js also, kill off the inexplicably useless LogoutPrompt in favour of a normal QuestionDialog. This in turn fixes https://github.com/vector-im/riot-web/issues/2152 --- src/components/views/dialogs/LogoutPrompt.js | 61 -------------------- 1 file changed, 61 deletions(-) delete mode 100644 src/components/views/dialogs/LogoutPrompt.js diff --git a/src/components/views/dialogs/LogoutPrompt.js b/src/components/views/dialogs/LogoutPrompt.js deleted file mode 100644 index c4bd7a0474..0000000000 --- a/src/components/views/dialogs/LogoutPrompt.js +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -var React = require('react'); -var dis = require("../../../dispatcher"); - -module.exports = React.createClass({ - displayName: 'LogoutPrompt', - - propTypes: { - onFinished: React.PropTypes.func, - }, - - logOut: function() { - dis.dispatch({action: 'logout'}); - if (this.props.onFinished) { - this.props.onFinished(); - } - }, - - cancelPrompt: function() { - if (this.props.onFinished) { - this.props.onFinished(); - } - }, - - onKeyDown: function(e) { - if (e.keyCode === 27) { // escape - e.stopPropagation(); - e.preventDefault(); - this.cancelPrompt(); - } - }, - - render: function() { - return ( -
-
- Sign out? -
-
- - -
-
- ); - } -}); - From 6a40abbbf063638e1008090a39e4735c9964a754 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Jan 2017 23:18:25 +0100 Subject: [PATCH 09/12] actually, move signout warning to UserSettings.js also, kill off the inexplicably useless LogoutPrompt in favour of a normal QuestionDialog. This in turn fixes https://github.com/vector-im/riot-web/issues/2152 --- src/Lifecycle.js | 43 +++++++---------------- src/component-index.js | 2 -- src/components/structures/UserSettings.js | 22 ++++++++++-- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 64d22bd04c..493bbf12aa 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -22,7 +22,6 @@ import Notifier from './Notifier'; import UserActivity from './UserActivity'; import Presence from './Presence'; import dis from './dispatcher'; -import Modal from './Modal'; import DMRoomMap from './utils/DMRoomMap'; /** @@ -290,35 +289,19 @@ export function logout() { return; } - var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { - title: "Warning", - description: -
- For security, logging out will delete any end-to-end encryption keys from this browser, - making previous encrypted chat history unreadable if you log back in. - In future this will be improved, - but for now be warned. -
, - button: "Continue", - onFinished: (confirmed) => { - if (confirmed) { - MatrixClientPeg.get().logout().then(onLoggedOut, - (err) => { - // Just throwing an error here is going to be very unhelpful - // if you're trying to log out because your server's down and - // you want to log into a different server, so just forget the - // access token. It's annoying that this will leave the access - // token still valid, but we should fix this by having access - // tokens expire (and if you really think you've been compromised, - // change your password). - console.log("Failed to call logout API: token will not be invalidated"); - onLoggedOut(); - } - ); - } - }, - }); + return MatrixClientPeg.get().logout().then(onLoggedOut, + (err) => { + // Just throwing an error here is going to be very unhelpful + // if you're trying to log out because your server's down and + // you want to log into a different server, so just forget the + // access token. It's annoying that this will leave the access + // token still valid, but we should fix this by having access + // tokens expire (and if you really think you've been compromised, + // change your password). + console.log("Failed to call logout API: token will not be invalidated"); + onLoggedOut(); + } + ); } /** diff --git a/src/component-index.js b/src/component-index.js index e83de8739d..99882e784f 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -79,8 +79,6 @@ import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog'; views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog); import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog'; views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog); -import views$dialogs$LogoutPrompt from './components/views/dialogs/LogoutPrompt'; -views$dialogs$LogoutPrompt && (module.exports.components['views.dialogs.LogoutPrompt'] = views$dialogs$LogoutPrompt); import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog'; views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog); import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog'; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 4a1332be8c..0231bc5038 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -228,8 +228,26 @@ module.exports = React.createClass({ }, onLogoutClicked: function(ev) { - var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt'); - this.logoutModal = Modal.createDialog(LogoutPrompt); + var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createDialog(QuestionDialog, { + title: "Sign out?", + description: +
+ For security, logging out will delete any end-to-end encryption keys from this browser, + making previous encrypted chat history unreadable if you log back in. + In future this will be improved, + but for now be warned. +
, + button: "Sign out", + onFinished: (confirmed) => { + if (confirmed) { + dis.dispatch({action: 'logout'}); + if (this.props.onFinished) { + this.props.onFinished(); + } + } + }, + }); }, onPasswordChangeError: function(err) { From 770820e6faca38a8e2d57c9655a4c4a38273e85f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 24 Jan 2017 22:41:52 +0000 Subject: [PATCH 10/12] Fix a bunch of lint complaints --- .eslintignore | 1 + src/components/structures/UserSettings.js | 2 +- src/components/views/avatars/BaseAvatar.js | 2 +- .../views/dialogs/ChatInviteDialog.js | 5 +-- .../views/elements/AccessibleButton.js | 12 ++++--- src/components/views/rooms/EntityTile.js | 2 +- src/components/views/rooms/MemberInfo.js | 33 ++++++++++++------- src/components/views/rooms/RoomHeader.js | 2 +- src/components/views/rooms/RoomTile.js | 2 +- .../views/rooms/SimpleRoomHeader.js | 2 +- .../views/settings/ChangePassword.js | 5 +-- 11 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..c4c7fe5067 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/component-index.js diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index ba5ab49bbc..ca37a9b179 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -26,7 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore'); var GeminiScrollbar = require('react-gemini-scrollbar'); var Email = require('../../email'); var AddThreepid = require('../../AddThreepid'); -var AccessibleButton = require('../views/elements/AccessibleButton'); +import AccessibleButton from '../views/elements/AccessibleButton'; // if this looks like a release, use the 'version' from package.json; else use // the git sha. diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index a2ad5ee6dc..c9c84aa1bf 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -19,7 +19,7 @@ limitations under the License. var React = require('react'); var AvatarLogic = require("../../../Avatar"); import sdk from '../../../index'; -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; module.exports = React.createClass({ displayName: 'BaseAvatar', diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index 47d343b599..2f17445263 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -24,7 +24,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap'); var rate_limited_func = require("../../../ratelimitedfunc"); var dis = require("../../../dispatcher"); var Modal = require('../../../Modal'); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; const TRUNCATE_QUERY_LIST = 40; @@ -437,7 +437,8 @@ module.exports = React.createClass({
{this.props.title}
- +
diff --git a/src/components/views/elements/AccessibleButton.js b/src/components/views/elements/AccessibleButton.js index 3ff5d7a38a..ffea8e1ba7 100644 --- a/src/components/views/elements/AccessibleButton.js +++ b/src/components/views/elements/AccessibleButton.js @@ -17,8 +17,12 @@ import React from 'react'; /** - * AccessibleButton is a generic wrapper for any element that should be treated as a button. - * Identifies the element as a button, setting proper tab indexing and keyboard activation behavior. + * AccessibleButton is a generic wrapper for any element that should be treated + * as a button. Identifies the element as a button, setting proper tab + * indexing and keyboard activation behavior. + * + * @param {Object} props react element properties + * @returns {Object} rendered react */ export default function AccessibleButton(props) { const {element, onClick, children, ...restProps} = props; @@ -26,7 +30,7 @@ export default function AccessibleButton(props) { restProps.onKeyDown = function(e) { if (e.keyCode == 13 || e.keyCode == 32) return onClick(); }; - restProps.tabIndex = restProps.tabIndex || "0"; + restProps.tabIndex = restProps.tabIndex || "0"; restProps.role = "button"; return React.createElement(element, restProps, children); } @@ -44,7 +48,7 @@ AccessibleButton.propTypes = { }; AccessibleButton.defaultProps = { - element: 'div' + element: 'div', }; AccessibleButton.displayName = "AccessibleButton"; diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index 64de431d9d..71e8fb0be7 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -20,7 +20,7 @@ var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; var PRESENCE_CLASS = { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b616624822..d33b8f3524 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -35,7 +35,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap'); var Unread = require('../../../Unread'); var Receipt = require('../../../utils/Receipt'); var WithMatrixClient = require('../../../wrappers/WithMatrixClient'); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; module.exports = WithMatrixClient(React.createClass({ displayName: 'MemberInfo', @@ -636,20 +636,31 @@ module.exports = WithMatrixClient(React.createClass({ } if (this.state.can.kick) { - kickButton = - { this.props.member.membership === "invite" ? "Disinvite" : "Kick" } - ; + const membership = this.props.member.membership; + const kickLabel = membership === "invite" ? "Disinvite" : "Kick"; + kickButton = ( + + {kickLabel} + + ); } if (this.state.can.ban) { - banButton = - Ban - ; + banButton = ( + + Ban + + ); } if (this.state.can.mute) { - var muteLabel = this.state.muted ? "Unmute" : "Mute"; - muteButton = - {muteLabel} - ; + const muteLabel = this.state.muted ? "Unmute" : "Mute"; + muteButton = ( + + {muteLabel} + + ); } if (this.state.can.toggleMod) { var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator"; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 812dd8c79c..fa0c63dfdd 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -26,7 +26,7 @@ var rate_limited_func = require('../../../ratelimitedfunc'); var linkify = require('linkifyjs'); var linkifyElement = require('linkifyjs/element'); var linkifyMatrix = require('../../../linkify-matrix'); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; linkifyMatrix(linkify); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 4a40cf058f..f6c0f7034e 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -26,7 +26,7 @@ var sdk = require('../../../index'); var ContextualMenu = require('../../structures/ContextualMenu'); var RoomNotifs = require('../../../RoomNotifs'); var FormattingUtils = require('../../../utils/FormattingUtils'); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; var UserSettingsStore = require('../../../UserSettingsStore'); module.exports = React.createClass({ diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js index cabd0f27a4..bc2f4bca69 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.js @@ -19,7 +19,7 @@ limitations under the License. var React = require('react'); var sdk = require('../../../index'); var dis = require("../../../dispatcher"); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; /* * A stripped-down room header used for things like the user settings diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 2bbf5420c0..5cd689ae44 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -19,7 +19,7 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); var sdk = require("../../../index"); -var AccessibleButton = require('../elements/AccessibleButton'); +import AccessibleButton from '../elements/AccessibleButton'; module.exports = React.createClass({ displayName: 'ChangePassword', @@ -137,7 +137,8 @@ module.exports = React.createClass({
- + Change Password From 29b4dde8781d390509716a2cc60347e833ffb028 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 25 Jan 2017 08:01:45 +0000 Subject: [PATCH 11/12] Fix SetDisplayNameDialog SetDisplayNameDialog got broken by the changes to support asynchronous loading of dialogs. Rather than poking into its internals via a ref, make it return its result via onFinished. Fixes https://github.com/vector-im/riot-web/issues/3047 --- src/components/structures/RoomView.js | 8 ++------ src/components/views/dialogs/SetDisplayNameDialog.js | 12 +++++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8753540e48..299d2fa850 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -722,15 +722,11 @@ module.exports = React.createClass({ if (!result.displayname) { var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog'); var dialog_defer = q.defer(); - var dialog_ref; Modal.createDialog(SetDisplayNameDialog, { currentDisplayName: result.displayname, - ref: (r) => { - dialog_ref = r; - }, - onFinished: (submitted) => { + onFinished: (submitted, newDisplayName) => { if (submitted) { - cli.setDisplayName(dialog_ref.getValue()).done(() => { + cli.setDisplayName(newDisplayName).done(() => { dialog_defer.resolve(); }); } diff --git a/src/components/views/dialogs/SetDisplayNameDialog.js b/src/components/views/dialogs/SetDisplayNameDialog.js index c1041cc218..18e6b66bff 100644 --- a/src/components/views/dialogs/SetDisplayNameDialog.js +++ b/src/components/views/dialogs/SetDisplayNameDialog.js @@ -18,6 +18,12 @@ var React = require("react"); var sdk = require("../../../index.js"); var MatrixClientPeg = require("../../../MatrixClientPeg"); + +/** + * Prompt the user to set a display name. + * + * On success, `onFinished(true, newDisplayName)` is called. + */ module.exports = React.createClass({ displayName: 'SetDisplayNameDialog', propTypes: { @@ -42,10 +48,6 @@ module.exports = React.createClass({ this.refs.input_value.select(); }, - getValue: function() { - return this.state.value; - }, - onValueChange: function(ev) { this.setState({ value: ev.target.value @@ -54,7 +56,7 @@ module.exports = React.createClass({ onFormSubmit: function(ev) { ev.preventDefault(); - this.props.onFinished(true); + this.props.onFinished(true, this.state.value); return false; }, From b34f63d3e70fd4f22e226a693f76c86a71abb60c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 25 Jan 2017 14:59:18 +0000 Subject: [PATCH 12/12] Re-add dispatcher as alt-up/down uses it Alt-up/down still doesn't go through rooms in the right order, but it should probably not error. --- src/components/structures/LoggedInView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 57a4d4c721..c00bd2c6db 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -21,6 +21,7 @@ import KeyCode from '../../KeyCode'; import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; import sdk from '../../index'; +import dis from '../../dispatcher'; /** * This is what our MatrixChat shows when we are logged in. The precise view is