From 1973b2bbe78ced0c53d2c1975683f74603a3020e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sat, 5 Aug 2017 00:00:19 +0100 Subject: [PATCH 01/68] Switch app drawer icons --- src/components/views/rooms/MessageComposer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 14f52706ec..7c8723e197 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -289,12 +289,12 @@ export default class MessageComposer extends React.Component { if (this.props.showApps) { hideAppsButton =
- +
; } else { showAppsButton =
- +
; } } From a22e76834336a823a739e4f69c7f2a47a8abd1f6 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 10:01:48 +0100 Subject: [PATCH 02/68] Move room settings button to RoomHeader --- src/components/views/rooms/RoomHeader.js | 83 ++++++++++++++++++++++ src/components/views/rooms/RoomSettings.js | 78 -------------------- 2 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 85aedadf64..d4e390f750 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -30,6 +30,9 @@ import linkifyElement from 'linkifyjs/element'; import linkifyMatrix from '../../../linkify-matrix'; import AccessibleButton from '../elements/AccessibleButton'; import {CancelButton} from './SimpleRoomHeader'; +import SdkConfig from '../../../SdkConfig'; +import ScalarAuthClient from '../../../ScalarAuthClient'; +import ScalarMessaging from '../../../ScalarMessaging'; linkifyMatrix(linkify); @@ -57,6 +60,13 @@ module.exports = React.createClass({ }; }, + getInitialState: function() { + return { + scalar_error: null, + showIntegrationsError: false, + }; + }, + componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); @@ -75,7 +85,23 @@ module.exports = React.createClass({ } }, + componentWillMount: function() { + ScalarMessaging.startListening(); + this.scalarClient = null; + if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + this.scalarClient = new ScalarAuthClient(); + this.scalarClient.connect().done(() => { + this.forceUpdate(); + }, (err) => { + this.setState({ + scalar_error: err, + }); + }); + } + }, + componentWillUnmount: function() { + ScalarMessaging.stopListening(); if (this.props.room) { this.props.room.removeListener("Room.name", this._onRoomNameChange); } @@ -85,6 +111,28 @@ module.exports = React.createClass({ } }, + onManageIntegrations(ev) { + ev.preventDefault(); + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createDialog(IntegrationsManager, { + src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? + this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : + null, + onFinished: ()=>{ + if (this._calcSavePromises().length === 0) { + this.props.onCancelClick(ev); + } + }, + }, "mx_IntegrationsManager"); + }, + + onShowIntegrationsError(ev) { + ev.preventDefault(); + this.setState({ + showIntegrationsError: !this.state.showIntegrationsError, + }); + }, + _onRoomStateEvents: function(event, state) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; @@ -320,10 +368,45 @@ module.exports = React.createClass({ } let rightRow; + let integrationsButton; + let integrationsError; + if (this.scalarClient !== null) { + if (this.state.showIntegrationsError && this.state.scalar_error) { + console.error(this.state.scalar_error); + integrationsError = ( + + { _t('Could not connect to the integration server') } + + ); + } + + if (this.scalarClient.hasCredentials()) { + integrationsButton = ( + + + + ); + } else if (this.state.scalar_error) { + integrationsButton = ( +
+ + { integrationsError } +
+ ); + } else { + integrationsButton = ( + + + + ); + } + } + if (!this.props.editing) { rightRow =
{ settingsButton } + { integrationsButton } { forgetButton } { searchButton } { rightPanelButtons } diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index d6a973f648..1baaf3aaa9 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -24,8 +24,6 @@ import sdk from '../../../index'; import Modal from '../../../Modal'; import ObjectUtils from '../../../ObjectUtils'; import dis from '../../../dispatcher'; -import ScalarAuthClient from '../../../ScalarAuthClient'; -import ScalarMessaging from '../../../ScalarMessaging'; import UserSettingsStore from '../../../UserSettingsStore'; import AccessibleButton from '../elements/AccessibleButton'; @@ -118,14 +116,10 @@ module.exports = React.createClass({ // Default to false if it's undefined, otherwise react complains about changing // components from uncontrolled to controlled isRoomPublished: this._originalIsRoomPublished || false, - scalar_error: null, - showIntegrationsError: false, }; }, componentWillMount: function() { - ScalarMessaging.startListening(); - MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership); MatrixClientPeg.get().getRoomDirectoryVisibility( @@ -137,18 +131,6 @@ module.exports = React.createClass({ console.error("Failed to get room visibility: " + err); }); - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().done(() => { - this.forceUpdate(); - }, (err) => { - this.setState({ - scalar_error: err - }); - }); - } - dis.dispatch({ action: 'ui_opacity', sideOpacity: 0.3, @@ -157,8 +139,6 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - ScalarMessaging.stopListening(); - const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomMember.membership", this._onRoomMemberMembership); @@ -513,28 +493,6 @@ module.exports = React.createClass({ roomState.mayClientSendStateEvent("m.room.guest_access", cli)); }, - onManageIntegrations(ev) { - ev.preventDefault(); - var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createDialog(IntegrationsManager, { - src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : - null, - onFinished: ()=>{ - if (this._calcSavePromises().length === 0) { - this.props.onCancelClick(ev); - } - }, - }, "mx_IntegrationsManager"); - }, - - onShowIntegrationsError(ev) { - ev.preventDefault(); - this.setState({ - showIntegrationsError: !this.state.showIntegrationsError, - }); - }, - onLeaveClick() { dis.dispatch({ action: 'leave_room', @@ -796,46 +754,10 @@ module.exports = React.createClass({
; } - let integrationsButton; - let integrationsError; - - if (this.scalarClient !== null) { - if (this.state.showIntegrationsError && this.state.scalar_error) { - console.error(this.state.scalar_error); - integrationsError = ( - - { _t('Could not connect to the integration server') } - - ); - } - - if (this.scalarClient.hasCredentials()) { - integrationsButton = ( -
- { _t('Manage Integrations') } -
- ); - } else if (this.state.scalar_error) { - integrationsButton = ( -
- Integrations Error - { integrationsError } -
- ); - } else { - integrationsButton = ( -
- { _t('Manage Integrations') } -
- ); - } - } - return (
{ leaveButton } - { integrationsButton } { tagsSection } From 308d932b2f02c6ed2120828af1f22e4401b4bd41 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 10:29:43 +0100 Subject: [PATCH 03/68] CancelClick prop. --- src/components/views/rooms/RoomHeader.js | 6 +++--- src/components/views/rooms/RoomSettings.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index d4e390f750..66b0eab3e7 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -50,6 +50,7 @@ module.exports = React.createClass({ onSaveClick: React.PropTypes.func, onSearchClick: React.PropTypes.func, onLeaveClick: React.PropTypes.func, + onCancelClick: React.PropTypes.func, }, getDefaultProps: function() { @@ -57,6 +58,7 @@ module.exports = React.createClass({ editing: false, inRoom: false, onSaveClick: function() {}, + onCancelClick: function() {}, }; }, @@ -119,9 +121,7 @@ module.exports = React.createClass({ this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : null, onFinished: ()=>{ - if (this._calcSavePromises().length === 0) { - this.props.onCancelClick(ev); - } + this.props.onCancelClick(ev); }, }, "mx_IntegrationsManager"); }, diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 1baaf3aaa9..0c1372f54e 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -90,7 +90,6 @@ module.exports = React.createClass({ propTypes: { room: React.PropTypes.object.isRequired, onSaveClick: React.PropTypes.func, - onCancelClick: React.PropTypes.func, }, getInitialState: function() { From 18ae5fd129ae49bfddeab4be050db73031c2fac7 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 6 Aug 2017 11:01:14 +0100 Subject: [PATCH 04/68] Send messages on widget addition and deletion --- src/ScalarMessaging.js | 6 ++++++ src/components/views/rooms/RoomHeader.js | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index d14d439d66..cb6e79f5e6 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -338,6 +338,12 @@ function setWidget(event, roomId) { sendResponse(event, { success: true, }); + + if (widgetUrl !== null) { + client.sendTextMessage(roomId, `Added ${widgetType} widget - ${widgetUrl}`); + } else { + client.sendTextMessage(roomId, `Removed ${widgetType} widget`); + } }, (err) => { sendError(event, _t('Failed to send request.'), err); }); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 66b0eab3e7..f26aa29d31 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -121,7 +121,9 @@ module.exports = React.createClass({ this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : null, onFinished: ()=>{ - this.props.onCancelClick(ev); + if (this.props.onCancelClick) { + this.props.onCancelClick(ev); + } }, }, "mx_IntegrationsManager"); }, From 9f8e8ae1fd58ccaf75505f5c0350e8b6585d87f9 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 8 Aug 2017 17:34:54 +0100 Subject: [PATCH 05/68] Split timeline updates in to different PR. --- src/ScalarMessaging.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index cb6e79f5e6..d14d439d66 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -338,12 +338,6 @@ function setWidget(event, roomId) { sendResponse(event, { success: true, }); - - if (widgetUrl !== null) { - client.sendTextMessage(roomId, `Added ${widgetType} widget - ${widgetUrl}`); - } else { - client.sendTextMessage(roomId, `Removed ${widgetType} widget`); - } }, (err) => { sendError(event, _t('Failed to send request.'), err); }); From 4bc25f12cb792fccd79d1598346c5c9d57ba2d10 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Wed, 9 Aug 2017 11:44:24 +0100 Subject: [PATCH 06/68] Move manage integrations button in to stand-alone component --- .../views/elements/ManageIntegsButton.js | 127 ++++++++++++++++++ src/components/views/rooms/RoomHeader.js | 87 +----------- 2 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 src/components/views/elements/ManageIntegsButton.js diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js new file mode 100644 index 0000000000..8d4c9d8b85 --- /dev/null +++ b/src/components/views/elements/ManageIntegsButton.js @@ -0,0 +1,127 @@ +/* +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 PropTypes from 'prop-types'; +import sdk from '../../../index'; +import SdkConfig from '../../../SdkConfig'; +import ScalarAuthClient from '../../../ScalarAuthClient'; +import ScalarMessaging from '../../../ScalarMessaging'; +import Modal from "../../../Modal"; +import { _t } from '../../../languageHandler'; +import AccessibleButton from './AccessibleButton'; +import TintableSvg from './TintableSvg'; + +export default class ManageIntegsButton extends React.Component { + constructor(props) { + super(props); + + this.state = { + scalar_error: null, + showIntegrationsError: false, + }; + + this.onManageIntegrations = this.onManageIntegrations.bind(this); + this.onShowIntegrationsError = this.onShowIntegrationsError.bind(this); + } + + componentWillMount() { + ScalarMessaging.startListening(); + this.scalarClient = null; + + if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + this.scalarClient = new ScalarAuthClient(); + this.scalarClient.connect().done(() => { + this.forceUpdate(); + }, (err) => { + this.setState({ scalar_error: err}); + }); + } + } + + componentWillUnmount() { + ScalarMessaging.stopListening(); + } + + onManageIntegrations(ev) { + ev.preventDefault(); + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createDialog(IntegrationsManager, { + src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? + this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : + null, + onFinished: ()=>{ + if (this.props.onCancelClick) { + this.props.onCancelClick(ev); + } + }, + }, "mx_IntegrationsManager"); + } + + onShowIntegrationsError(ev) { + ev.preventDefault(); + this.setState({ + showIntegrationsError: !this.state.showIntegrationsError, + }); + } + + render() { + let integrationsButton; + let integrationsError; + if (this.scalarClient !== null) { + if (this.state.showIntegrationsError && this.state.scalar_error) { + console.error(this.state.scalar_error); + integrationsError = ( + + { _t('Could not connect to the integration server') } + + ); + } + + if (this.scalarClient.hasCredentials()) { + integrationsButton = ( + + + + ); + } else if (this.state.scalar_error) { + integrationsButton = ( +
+ + { integrationsError } +
+ ); + } else { + integrationsButton = ( + + + + ); + } + } + + return integrationsButton; + } +} + +ManageIntegsButton.propTypes = { + room: PropTypes.object.isRequired, + onCancelClick: PropTypes.func, +}; + +ManageIntegsButton.defaultProps = { + onCancelClick: function() {}, +}; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index f26aa29d31..ed354017b2 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -29,10 +29,8 @@ import * as linkify from 'linkifyjs'; import linkifyElement from 'linkifyjs/element'; import linkifyMatrix from '../../../linkify-matrix'; import AccessibleButton from '../elements/AccessibleButton'; +import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; -import SdkConfig from '../../../SdkConfig'; -import ScalarAuthClient from '../../../ScalarAuthClient'; -import ScalarMessaging from '../../../ScalarMessaging'; linkifyMatrix(linkify); @@ -62,13 +60,6 @@ module.exports = React.createClass({ }; }, - getInitialState: function() { - return { - scalar_error: null, - showIntegrationsError: false, - }; - }, - componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); @@ -87,23 +78,7 @@ module.exports = React.createClass({ } }, - componentWillMount: function() { - ScalarMessaging.startListening(); - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().done(() => { - this.forceUpdate(); - }, (err) => { - this.setState({ - scalar_error: err, - }); - }); - } - }, - componentWillUnmount: function() { - ScalarMessaging.stopListening(); if (this.props.room) { this.props.room.removeListener("Room.name", this._onRoomNameChange); } @@ -113,28 +88,6 @@ module.exports = React.createClass({ } }, - onManageIntegrations(ev) { - ev.preventDefault(); - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createDialog(IntegrationsManager, { - src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : - null, - onFinished: ()=>{ - if (this.props.onCancelClick) { - this.props.onCancelClick(ev); - } - }, - }, "mx_IntegrationsManager"); - }, - - onShowIntegrationsError(ev) { - ev.preventDefault(); - this.setState({ - showIntegrationsError: !this.state.showIntegrationsError, - }); - }, - _onRoomStateEvents: function(event, state) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; @@ -370,45 +323,15 @@ module.exports = React.createClass({ } let rightRow; - let integrationsButton; - let integrationsError; - if (this.scalarClient !== null) { - if (this.state.showIntegrationsError && this.state.scalar_error) { - console.error(this.state.scalar_error); - integrationsError = ( - - { _t('Could not connect to the integration server') } - - ); - } - - if (this.scalarClient.hasCredentials()) { - integrationsButton = ( - - - - ); - } else if (this.state.scalar_error) { - integrationsButton = ( -
- - { integrationsError } -
- ); - } else { - integrationsButton = ( - - - - ); - } - } if (!this.props.editing) { rightRow =
{ settingsButton } - { integrationsButton } + { forgetButton } { searchButton } { rightPanelButtons } From 0323151bee584434be0dd7a699c1ffc465aa0c3a Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Thu, 10 Aug 2017 23:53:43 +0100 Subject: [PATCH 07/68] Show a dialog if the maximum number of widgets allowed has been reached. --- src/components/views/rooms/AppsDrawer.js | 33 +++++++++++++++++------- src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 5427d4ec6d..9071a5bcab 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -28,6 +28,8 @@ import ScalarMessaging from '../../../ScalarMessaging'; import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../WidgetUtils'; +// The maximum number of widgets that can be added in a room +const MAX_WIDGETS = 2; module.exports = React.createClass({ displayName: 'AppsDrawer', @@ -162,6 +164,18 @@ module.exports = React.createClass({ e.preventDefault(); } + // Display a warning dialog if the max number of widgets have already been added to the room + if (this.state.apps && this.state.apps.length >= MAX_WIDGETS) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`; + console.error(errorMsg); + Modal.createDialog(ErrorDialog, { + title: _t("Cannot add any more widgets"), + description: _t("The maximum permitted number of widgets have already been added to this room."), + }); + return; + } + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') : @@ -186,21 +200,22 @@ module.exports = React.createClass({ />); }); - const addWidget = this.state.apps && this.state.apps.length < 2 && this._canUserModify() && - (
- [+] {_t('Add a widget')} -
); + const addWidget = ( +
+ [+] {_t('Add a widget')} +
+ ); return (
{apps}
- {addWidget} + {this._canUserModify() && addWidget}
); }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0d5b7d9d96..784ca54bb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -190,6 +190,7 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", "Can't load user settings": "Can't load user settings", + "Cannot add any more widgets": "Cannot add any more widgets", "Change Password": "Change Password", "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.", "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", @@ -546,6 +547,7 @@ "Tagged as: ": "Tagged as: ", "The default role for new room members is": "The default role for new room members is", "The main address for this room is": "The main address for this room is", + "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "The phone number entered looks invalid": "The phone number entered looks invalid", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", "This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.", From 447aa1e5a0fd9e3e4862ff25354f1a83728b63df Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 14 Aug 2017 17:38:59 +0100 Subject: [PATCH 08/68] Refactor ChatInviteDialog to be UserPickerDialog Now it's just a means of choosing users and all the actual inviting functionality is moved out to Invite.js. This will allow us to reuse it for inviting to groups. Adds the ability to restrict what types of addresses may be chosen, although this isn;t used yet, it will be necessary for groups because groups don't support 3pid invites. --- .../views/dialogs/{ChatInviteDialog.js => UserPickerDialog.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/views/dialogs/{ChatInviteDialog.js => UserPickerDialog.js} (100%) diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/UserPickerDialog.js similarity index 100% rename from src/components/views/dialogs/ChatInviteDialog.js rename to src/components/views/dialogs/UserPickerDialog.js From 1b66e88b6ef1affbeaf5c32d347b64be1dd08be7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 14 Aug 2017 17:43:00 +0100 Subject: [PATCH 09/68] ChatInviteDialog -> UserPickerDialog pt 2 The other changes I forgot to add --- src/Invite.js | 129 ++++++++++ src/components/structures/MatrixChat.js | 23 +- .../views/dialogs/UserPickerDialog.js | 221 +++--------------- .../views/elements/AddressSelector.js | 2 +- src/components/views/elements/AddressTile.js | 22 +- 5 files changed, 164 insertions(+), 233 deletions(-) diff --git a/src/Invite.js b/src/Invite.js index 0e8aca2cb5..d825dac6f5 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,11 +17,35 @@ limitations under the License. import MatrixClientPeg from './MatrixClientPeg'; import MultiInviter from './utils/MultiInviter'; +import Modal from "./Modal"; +import createRoom from './createRoom'; +import sdk from "./"; +import { _t } from './languageHandler'; const emailRegex = /^\S+@\S+\.\S+$/; const mxidRegex = /^@\S+:\S+$/ +export const addressTypes = [ + 'mx', 'email', +]; + +// React PropType definition for an object describing +// an address that can be invited to a room (which +// could be a third party identifier or a matrix ID) +// along with some additional information about the +// address / target. +export const InviteAddressType = React.PropTypes.shape({ + addressType: React.PropTypes.oneOf(addressTypes).isRequired, + address: React.PropTypes.string.isRequired, + displayName: React.PropTypes.string, + avatarMxc: React.PropTypes.string, + // true if the address is known to be a valid address (eg. is a real + // user we've seen) or false otherwise (eg. is just an address the + // user has entered) + isKnown: React.PropTypes.bool, +}); + export function getAddressType(inputText) { const isEmailAddress = emailRegex.test(inputText); const isMatrixId = mxidRegex.test(inputText); @@ -61,3 +86,107 @@ export function inviteMultipleToRoom(roomId, addrs) { return inviter.invite(addrs); } +export function showStartChatInviteDialog() { + const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); + Modal.createTrackedDialog('Start a chat', '', UserPickerDialog, { + title: _t('Start a chat'), + description: _t("Who would you like to communicate with?"), + placeholder: _t("Email, name or matrix ID"), + button: _t("Start Chat"), + onFinished: _onStartChatFinished, + }); +} + +export function showRoomInviteDialog(roomId) { + const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog"); + Modal.createTrackedDialog('Chat Invite', '', UserPickerDialog, { + title: _t('Invite new room members'), + description: _t('Who would you like to add to this room?'), + button: _t('Send Invites'), + placeholder: _t("Email, name or matrix ID"), + onFinished: (shouldInvite, addrs) => { + _onRoomInviteFinished(roomId, shouldInvite, addrs); + }, + }); +} + +function _onStartChatFinished(shouldInvite, addrs) { + if (!shouldInvite) return; + + const addrTexts = addrs.map(addr => addr.address); + + if (_isDmChat(addrTexts)) { + // Start a new DM chat + createRoom({dmUserId: addrTexts[0]}).catch((err) => { + console.error(err.stack); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, { + title: _t("Failed to invite user"), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + }); + } else { + // Start multi user chat + let room; + createRoom().then((roomId) => { + room = MatrixClientPeg.get().getRoom(roomId); + return inviteMultipleToRoom(roomId, addrTexts); + }).then((addrs) => { + return _showAnyInviteErrors(addrs, room); + }).catch((err) => { + console.error(err.stack); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { + title: _t("Failed to invite"), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + }); + } +} + +function _onRoomInviteFinished(roomId, shouldInvite, addrs) { + if (!shouldInvite) return; + + const addrTexts = addrs.map(addr => addr.address); + + // Invite new users to a room + inviteMultipleToRoom(roomId, addrTexts).then((addrs) => { + const room = MatrixClientPeg.get().getRoom(roomId); + return _showAnyInviteErrors(addrs, room); + }).catch((err) => { + console.error(err.stack); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { + title: _t("Failed to invite"), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + }); +} + +function _isDmChat(addrTexts) { + if (addrTexts.length === 1 && getAddressType(addrTexts[0])) { + return true; + } else { + return false; + } +} + +function _showAnyInviteErrors(addrs, room) { + // Show user any errors + let errorList = []; + for (let addr of Object.keys(addrs)) { + if (addrs[addr] === "error") { + errorList.push(addr); + } + } + + if (errorList.length > 0) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, { + title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}), + description: errorList.join(", "), + }); + } + return addrs; +} + diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 6fdec80f38..fcd5ac2bd0 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,6 +32,7 @@ import dis from "../../dispatcher"; import Modal from "../../Modal"; import Tinter from "../../Tinter"; import sdk from '../../index'; +import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite'; import * as Rooms from '../../Rooms'; import linkifyMatrix from "../../linkify-matrix"; import * as Lifecycle from '../../Lifecycle'; @@ -512,7 +514,7 @@ module.exports = React.createClass({ this._createChat(); break; case 'view_invite': - this._invite(payload.roomId); + showRoomInviteDialog(payload.roomId); break; case 'notifier_enabled': this.forceUpdate(); @@ -766,13 +768,7 @@ module.exports = React.createClass({ dis.dispatch({action: 'view_set_mxid'}); return; } - const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); - Modal.createTrackedDialog('Start a chat', '', ChatInviteDialog, { - title: _t('Start a chat'), - description: _t("Who would you like to communicate with?"), - placeholder: _t("Email, name or matrix ID"), - button: _t("Start Chat"), - }); + showStartChatInviteDialog(); }, _createRoom: function() { @@ -857,17 +853,6 @@ module.exports = React.createClass({ }).close; }, - _invite: function(roomId) { - const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); - Modal.createTrackedDialog('Chat Invite', '', ChatInviteDialog, { - title: _t('Invite new room members'), - description: _t('Who would you like to add to this room?'), - button: _t('Send Invites'), - placeholder: _t("Email, name or matrix ID"), - roomId: roomId, - }); - }, - _leaveRoom: function(roomId) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 728860edec..5627a814c0 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,9 +16,10 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; -import { getAddressType, inviteMultipleToRoom } from '../../../Invite'; +import { getAddressType } from '../../../Invite'; import createRoom from '../../../createRoom'; import MatrixClientPeg from '../../../MatrixClientPeg'; import DMRoomMap from '../../../utils/DMRoomMap'; @@ -25,30 +27,34 @@ import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; import Promise from 'bluebird'; import dis from '../../../dispatcher'; +import { addressTypes, InviteAddressType } from '../../../Invite.js'; const TRUNCATE_QUERY_LIST = 40; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; module.exports = React.createClass({ - displayName: "ChatInviteDialog", + displayName: "UserPickerDialog", + propTypes: { - title: React.PropTypes.string.isRequired, - description: React.PropTypes.oneOfType([ - React.PropTypes.element, - React.PropTypes.string, + title: PropTypes.string.isRequired, + description: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.string, ]), - value: React.PropTypes.string, - placeholder: React.PropTypes.string, - roomId: React.PropTypes.string, - button: React.PropTypes.string, - focus: React.PropTypes.bool, - onFinished: React.PropTypes.func.isRequired, + value: PropTypes.string, + placeholder: PropTypes.string, + roomId: PropTypes.string, + button: PropTypes.string, + focus: PropTypes.bool, + validAddressTypes: PropTypes.arrayOf(PropTypes.oneOfType(addressTypes)), + onFinished: PropTypes.func.isRequired, }, getDefaultProps: function() { return { value: "", focus: true, + validAddressTypes: addressTypes, }; }, @@ -56,7 +62,7 @@ module.exports = React.createClass({ return { error: false, - // List of AddressTile.InviteAddressType objects representing + // List of InviteAddressType objects representing // the list of addresses we're going to invite inviteList: [], @@ -90,50 +96,7 @@ module.exports = React.createClass({ inviteList = this._addInputToList(); if (inviteList === null) return; } - - const addrTexts = inviteList.map(addr => addr.address); - if (inviteList.length > 0) { - if (this._isDmChat(addrTexts)) { - const userId = inviteList[0].address; - // Direct Message chat - const rooms = this._getDirectMessageRooms(userId); - if (rooms.length > 0) { - // A Direct Message room already exists for this user, so select a - // room from a list that is similar to the one in MemberInfo panel - const ChatCreateOrReuseDialog = sdk.getComponent( - "views.dialogs.ChatCreateOrReuseDialog", - ); - const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, { - userId: userId, - onFinished: (success) => { - this.props.onFinished(success); - }, - onNewDMClick: () => { - dis.dispatch({ - action: 'start_chat', - user_id: userId, - }); - close(true); - }, - onExistingRoomSelected: (roomId) => { - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - close(true); - }, - }).close; - } else { - this._startChat(inviteList); - } - } else { - // Multi invite chat - this._startChat(inviteList); - } - } else { - // No addresses supplied - this.setState({ error: true }); - } + this.props.onFinished(true, inviteList); }, onCancel: function() { @@ -201,11 +164,10 @@ module.exports = React.createClass({ }, onDismissed: function(index) { - var self = this; return () => { - var inviteList = self.state.inviteList.slice(); + const inviteList = this.state.inviteList.slice(); inviteList.splice(index, 1); - self.setState({ + this.setState({ inviteList: inviteList, queryList: [], query: "", @@ -215,14 +177,13 @@ module.exports = React.createClass({ }, onClick: function(index) { - var self = this; - return function() { - self.onSelected(index); + return () => { + this.onSelected(index); }; }, onSelected: function(index) { - var inviteList = this.state.inviteList.slice(); + const inviteList = this.state.inviteList.slice(); inviteList.push(this.state.queryList[index]); this.setState({ inviteList: inviteList, @@ -311,7 +272,7 @@ module.exports = React.createClass({ // This is important, otherwise there's no way to invite // a perfectly valid address if there are close matches. const addrType = getAddressType(query); - if (addrType !== null) { + if (this.props.validAddressTypes.indexOf(addrType) !== -1) { queryList.unshift({ addressType: addrType, address: query, @@ -330,132 +291,6 @@ module.exports = React.createClass({ }); }, - _getDirectMessageRooms: function(addr) { - const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); - const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); - const rooms = []; - dmRooms.forEach(dmRoom => { - let room = MatrixClientPeg.get().getRoom(dmRoom); - if (room) { - const me = room.getMember(MatrixClientPeg.get().credentials.userId); - if (me.membership == 'join') { - rooms.push(room); - } - } - }); - return rooms; - }, - - _startChat: function(addrs) { - if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); - return; - } - - const addrTexts = addrs.map((addr) => { - return addr.address; - }); - - if (this.props.roomId) { - // Invite new user to a room - var self = this; - inviteMultipleToRoom(this.props.roomId, addrTexts) - .then(function(addrs) { - var room = MatrixClientPeg.get().getRoom(self.props.roomId); - return self._showAnyInviteErrors(addrs, room); - }) - .catch(function(err) { - console.error(err.stack); - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t("Failed to invite"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - return null; - }) - .done(); - } else if (this._isDmChat(addrTexts)) { - // Start the DM chat - createRoom({dmUserId: addrTexts[0]}) - .catch(function(err) { - console.error(err.stack); - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, { - title: _t("Failed to invite user"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - return null; - }) - .done(); - } else { - // Start multi user chat - var self = this; - var room; - createRoom().then(function(roomId) { - room = MatrixClientPeg.get().getRoom(roomId); - return inviteMultipleToRoom(roomId, addrTexts); - }) - .then(function(addrs) { - return self._showAnyInviteErrors(addrs, room); - }) - .catch(function(err) { - console.error(err.stack); - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t("Failed to invite"), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - return null; - }) - .done(); - } - - // Close - this will happen before the above, as that is async - this.props.onFinished(true, addrTexts); - }, - - _isOnInviteList: function(uid) { - for (let i = 0; i < this.state.inviteList.length; i++) { - if ( - this.state.inviteList[i].addressType == 'mx' && - this.state.inviteList[i].address.toLowerCase() === uid - ) { - return true; - } - } - return false; - }, - - _isDmChat: function(addrTexts) { - if (addrTexts.length === 1 && - getAddressType(addrTexts[0]) === "mx" && - !this.props.roomId - ) { - return true; - } else { - return false; - } - }, - - _showAnyInviteErrors: function(addrs, room) { - // Show user any errors - var errorList = []; - for (var addr in addrs) { - if (addrs.hasOwnProperty(addr) && addrs[addr] === "error") { - errorList.push(addr); - } - } - - if (errorList.length > 0) { - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, { - title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}), - description: errorList.join(", "), - }); - } - return addrs; - }, - _addInputToList: function() { const addressText = this.refs.textinput.value.trim(); const addrType = getAddressType(addressText); @@ -527,10 +362,10 @@ module.exports = React.createClass({ const AddressSelector = sdk.getComponent("elements.AddressSelector"); this.scrollElement = null; - var query = []; + let query = []; // create the invite list if (this.state.inviteList.length > 0) { - var AddressTile = sdk.getComponent("elements.AddressTile"); + const AddressTile = sdk.getComponent("elements.AddressTile"); for (let i = 0; i < this.state.inviteList.length; i++) { query.push( diff --git a/src/components/views/elements/AddressSelector.js b/src/components/views/elements/AddressSelector.js index 5329994037..003fd534bb 100644 --- a/src/components/views/elements/AddressSelector.js +++ b/src/components/views/elements/AddressSelector.js @@ -20,7 +20,7 @@ limitations under the License. import React from 'react'; import sdk from '../../../index'; import classNames from 'classnames'; -import { InviteAddressType } from './AddressTile'; +import { InviteAddressType } from '../../../Invite'; export default React.createClass({ displayName: 'AddressSelector', diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js index 78fd942a46..fb35dafa5c 100644 --- a/src/components/views/elements/AddressTile.js +++ b/src/components/views/elements/AddressTile.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,31 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; import classNames from 'classnames'; import sdk from "../../../index"; import MatrixClientPeg from "../../../MatrixClientPeg"; import { _t } from '../../../languageHandler'; - -// React PropType definition for an object describing -// an address that can be invited to a room (which -// could be a third party identifier or a matrix ID) -// along with some additional information about the -// address / target. -export const InviteAddressType = React.PropTypes.shape({ - addressType: React.PropTypes.oneOf([ - 'mx', 'email' - ]).isRequired, - address: React.PropTypes.string.isRequired, - displayName: React.PropTypes.string, - avatarMxc: React.PropTypes.string, - // true if the address is known to be a valid address (eg. is a real - // user we've seen) or false otherwise (eg. is just an address the - // user has entered) - isKnown: React.PropTypes.bool, -}); +import { InviteAddressType } from '../../../Invite.js'; export default React.createClass({ From 00d69aa938bd7e0fcd8fc8d861cf7af2a119753b Mon Sep 17 00:00:00 2001 From: krombel Date: Tue, 15 Aug 2017 00:32:38 +0200 Subject: [PATCH 10/68] Update Link to Translation status The translation is now done at translate.riot.im but the link was not updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f5ef73365..144e89c938 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ are currently filed against vector-im/riot-web rather than this project). Translation Status ================== -[![translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget) +[![Translation status](https://translate.riot.im/widgets/riot-web/-/multi-auto.svg)](https://translate.riot.im/engage/riot-web/?utm_source=widget) Developer Guide =============== From d1c54e1224d2c8c46e91fe480527046cac26164d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 08:58:08 +0100 Subject: [PATCH 11/68] Switch to prop-types --- src/Invite.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Invite.js b/src/Invite.js index d825dac6f5..e7e7264d11 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import PropTypes from 'prop-types'; import MatrixClientPeg from './MatrixClientPeg'; import MultiInviter from './utils/MultiInviter'; import Modal from "./Modal"; @@ -30,20 +31,20 @@ export const addressTypes = [ 'mx', 'email', ]; -// React PropType definition for an object describing +// PropType definition for an object describing // an address that can be invited to a room (which // could be a third party identifier or a matrix ID) // along with some additional information about the // address / target. -export const InviteAddressType = React.PropTypes.shape({ - addressType: React.PropTypes.oneOf(addressTypes).isRequired, - address: React.PropTypes.string.isRequired, - displayName: React.PropTypes.string, - avatarMxc: React.PropTypes.string, +export const InviteAddressType = PropTypes.shape({ + addressType: PropTypes.oneOf(addressTypes).isRequired, + address: PropTypes.string.isRequired, + displayName: PropTypes.string, + avatarMxc: PropTypes.string, // true if the address is known to be a valid address (eg. is a real // user we've seen) or false otherwise (eg. is just an address the // user has entered) - isKnown: React.PropTypes.bool, + isKnown: PropTypes.bool, }); export function getAddressType(inputText) { From b7b449434dd5859ac8a66a240683766101044ab6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 09:10:13 +0100 Subject: [PATCH 12/68] Lint --- .eslintignore.errorfiles | 1 - .../views/dialogs/UserPickerDialog.js | 18 ++++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 55eaf75e4b..2018961854 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -33,7 +33,6 @@ src/components/views/create_room/CreateRoomButton.js src/components/views/create_room/Presets.js src/components/views/create_room/RoomAlias.js src/components/views/dialogs/ChatCreateOrReuseDialog.js -src/components/views/dialogs/ChatInviteDialog.js src/components/views/dialogs/DeactivateAccountDialog.js src/components/views/dialogs/InteractiveAuthDialog.js src/components/views/dialogs/SetMxIdDialog.js diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 5627a814c0..7dc1be7d25 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -20,14 +20,10 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; import { getAddressType } from '../../../Invite'; -import createRoom from '../../../createRoom'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; import Promise from 'bluebird'; -import dis from '../../../dispatcher'; -import { addressTypes, InviteAddressType } from '../../../Invite.js'; +import { addressTypes } from '../../../Invite.js'; const TRUNCATE_QUERY_LIST = 40; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; @@ -330,7 +326,7 @@ module.exports = React.createClass({ // not like they leak. this._cancelThreepidLookup = function() { cancelled = true; - } + }; // wait a bit to let the user finish typing return Promise.delay(500).then(() => { @@ -362,13 +358,13 @@ module.exports = React.createClass({ const AddressSelector = sdk.getComponent("elements.AddressSelector"); this.scrollElement = null; - let query = []; + const query = []; // create the invite list if (this.state.inviteList.length > 0) { const AddressTile = sdk.getComponent("elements.AddressTile"); for (let i = 0; i < this.state.inviteList.length; i++) { query.push( - + , ); } } @@ -390,7 +386,9 @@ module.exports = React.createClass({ let error; let addressSelector; if (this.state.error) { - error =
{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}
; + error =
+ {_t("You have entered an invalid contact. Try using their Matrix ID or email address.")} +
; } else if (this.state.searchError) { error =
{this.state.searchError}
; } else if ( @@ -433,5 +431,5 @@ module.exports = React.createClass({
); - } + }, }); From fa660c8211bf834781af5872bae59d2b3387f99d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 10:57:24 +0100 Subject: [PATCH 13/68] PR feedback --- src/Invite.js | 6 +++--- src/components/views/dialogs/UserPickerDialog.js | 15 ++++++--------- src/components/views/elements/AddressSelector.js | 4 ++-- src/components/views/elements/AddressTile.js | 4 ++-- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Invite.js b/src/Invite.js index e7e7264d11..b79a245549 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -18,9 +18,9 @@ limitations under the License. import PropTypes from 'prop-types'; import MatrixClientPeg from './MatrixClientPeg'; import MultiInviter from './utils/MultiInviter'; -import Modal from "./Modal"; +import Modal from './Modal'; import createRoom from './createRoom'; -import sdk from "./"; +import sdk from './'; import { _t } from './languageHandler'; const emailRegex = /^\S+@\S+\.\S+$/; @@ -36,7 +36,7 @@ export const addressTypes = [ // could be a third party identifier or a matrix ID) // along with some additional information about the // address / target. -export const InviteAddressType = PropTypes.shape({ +export const UserAddressType = PropTypes.shape({ addressType: PropTypes.oneOf(addressTypes).isRequired, address: PropTypes.string.isRequired, displayName: PropTypes.string, diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 7dc1be7d25..1405bc754b 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -33,10 +33,7 @@ module.exports = React.createClass({ propTypes: { title: PropTypes.string.isRequired, - description: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.string, - ]), + description: PropTypes.node, value: PropTypes.string, placeholder: PropTypes.string, roomId: PropTypes.string, @@ -58,7 +55,7 @@ module.exports = React.createClass({ return { error: false, - // List of InviteAddressType objects representing + // List of UserAddressType objects representing // the list of addresses we're going to invite inviteList: [], @@ -70,7 +67,7 @@ module.exports = React.createClass({ serverSupportsUserDirectory: true, // The query being searched for query: "", - // List of AddressTile.InviteAddressType objects representing + // List of UserAddressType objects representing // the set of auto-completion results for the current search // query. queryList: [], @@ -254,7 +251,7 @@ module.exports = React.createClass({ return; } // Return objects, structure of which is defined - // by InviteAddressType + // by UserAddressType queryList.push({ addressType: 'mx', address: user.user_id, @@ -268,7 +265,7 @@ module.exports = React.createClass({ // This is important, otherwise there's no way to invite // a perfectly valid address if there are close matches. const addrType = getAddressType(query); - if (this.props.validAddressTypes.indexOf(addrType) !== -1) { + if (this.props.validAddressTypes.includes(addrType)) { queryList.unshift({ addressType: addrType, address: query, @@ -342,7 +339,7 @@ module.exports = React.createClass({ if (cancelled) return null; this.setState({ queryList: [{ - // an InviteAddressType + // a UserAddressType addressType: medium, address: address, displayName: res.displayname, diff --git a/src/components/views/elements/AddressSelector.js b/src/components/views/elements/AddressSelector.js index 003fd534bb..1aae10737e 100644 --- a/src/components/views/elements/AddressSelector.js +++ b/src/components/views/elements/AddressSelector.js @@ -20,7 +20,7 @@ limitations under the License. import React from 'react'; import sdk from '../../../index'; import classNames from 'classnames'; -import { InviteAddressType } from '../../../Invite'; +import { UserAddressType } from '../../../Invite'; export default React.createClass({ displayName: 'AddressSelector', @@ -29,7 +29,7 @@ export default React.createClass({ onSelected: React.PropTypes.func.isRequired, // List of the addresses to display - addressList: React.PropTypes.arrayOf(InviteAddressType).isRequired, + addressList: React.PropTypes.arrayOf(UserAddressType).isRequired, truncateAt: React.PropTypes.number.isRequired, selected: React.PropTypes.number, diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js index fb35dafa5c..ba7d79d987 100644 --- a/src/components/views/elements/AddressTile.js +++ b/src/components/views/elements/AddressTile.js @@ -20,14 +20,14 @@ import classNames from 'classnames'; import sdk from "../../../index"; import MatrixClientPeg from "../../../MatrixClientPeg"; import { _t } from '../../../languageHandler'; -import { InviteAddressType } from '../../../Invite.js'; +import { UserAddressType } from '../../../Invite.js'; export default React.createClass({ displayName: 'AddressTile', propTypes: { - address: InviteAddressType.isRequired, + address: UserAddressType.isRequired, canDismiss: React.PropTypes.bool, onDismissed: React.PropTypes.func, justified: React.PropTypes.bool, From bbcf7e1d9bd6f0de315f4c251510b2de95bb4cbe Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 15 Aug 2017 13:30:13 +0100 Subject: [PATCH 14/68] s/inviteList/userList/ --- .../views/dialogs/UserPickerDialog.js | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/views/dialogs/UserPickerDialog.js b/src/components/views/dialogs/UserPickerDialog.js index 1405bc754b..f4ea0c6a24 100644 --- a/src/components/views/dialogs/UserPickerDialog.js +++ b/src/components/views/dialogs/UserPickerDialog.js @@ -57,7 +57,7 @@ module.exports = React.createClass({ // List of UserAddressType objects representing // the list of addresses we're going to invite - inviteList: [], + userList: [], // Whether a search is ongoing busy: false, @@ -82,14 +82,14 @@ module.exports = React.createClass({ }, onButtonClick: function() { - let inviteList = this.state.inviteList.slice(); + let userList = this.state.userList.slice(); // Check the text input field to see if user has an unconverted address - // If there is and it's valid add it to the local inviteList + // If there is and it's valid add it to the local userList if (this.refs.textinput.value !== '') { - inviteList = this._addInputToList(); - if (inviteList === null) return; + userList = this._addInputToList(); + if (userList === null) return; } - this.props.onFinished(true, inviteList); + this.props.onFinished(true, userList); }, onCancel: function() { @@ -113,10 +113,10 @@ module.exports = React.createClass({ e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.chooseSelection(); - } else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace + } else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace e.stopPropagation(); e.preventDefault(); - this.onDismissed(this.state.inviteList.length - 1)(); + this.onDismissed(this.state.userList.length - 1)(); } else if (e.keyCode === 13) { // enter e.stopPropagation(); e.preventDefault(); @@ -158,10 +158,10 @@ module.exports = React.createClass({ onDismissed: function(index) { return () => { - const inviteList = this.state.inviteList.slice(); - inviteList.splice(index, 1); + const userList = this.state.userList.slice(); + userList.splice(index, 1); this.setState({ - inviteList: inviteList, + userList: userList, queryList: [], query: "", }); @@ -176,10 +176,10 @@ module.exports = React.createClass({ }, onSelected: function(index) { - const inviteList = this.state.inviteList.slice(); - inviteList.push(this.state.queryList[index]); + const userList = this.state.userList.slice(); + userList.push(this.state.queryList[index]); this.setState({ - inviteList: inviteList, + userList: userList, queryList: [], query: "", }); @@ -304,15 +304,15 @@ module.exports = React.createClass({ } } - const inviteList = this.state.inviteList.slice(); - inviteList.push(addrObj); + const userList = this.state.userList.slice(); + userList.push(addrObj); this.setState({ - inviteList: inviteList, + userList: userList, queryList: [], query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - return inviteList; + return userList; }, _lookupThreepid: function(medium, address) { @@ -357,18 +357,18 @@ module.exports = React.createClass({ const query = []; // create the invite list - if (this.state.inviteList.length > 0) { + if (this.state.userList.length > 0) { const AddressTile = sdk.getComponent("elements.AddressTile"); - for (let i = 0; i < this.state.inviteList.length; i++) { + for (let i = 0; i < this.state.userList.length; i++) { query.push( - , + , ); } } // Add the query at the end query.push( -