diff --git a/src/Invite.js b/src/Invite.js index 6ad929e33b..d1f03fe211 100644 --- a/src/Invite.js +++ b/src/Invite.js @@ -19,9 +19,12 @@ import MultiInviter from './utils/MultiInviter'; const emailRegex = /^\S+@\S+\.\S+$/; +// We allow localhost for mxids to avoid confusion +const mxidRegex = /^@\S+:(?:\S+\.\S+|localhost)$/ + export function getAddressType(inputText) { - const isEmailAddress = /^\S+@\S+\.\S+$/.test(inputText); - const isMatrixId = inputText[0] === '@' && inputText.indexOf(":") > 0; + const isEmailAddress = emailRegex.test(inputText); + const isMatrixId = mxidRegex.test(inputText); // sanity check the input for user IDs if (isEmailAddress) { diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index 2f17445263..61503196e5 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -67,7 +67,14 @@ module.exports = React.createClass({ getInitialState: function() { return { error: false, + + // List of AddressTile.InviteAddressType objects represeting + // the list of addresses we're going to invite inviteList: [], + + // List of AddressTile.InviteAddressType objects represeting + // the set of autocompletion results for the current search + // query. queryList: [], }; }, @@ -156,14 +163,38 @@ module.exports = React.createClass({ }, onQueryChanged: function(ev) { - var query = ev.target.value; - var queryList = []; + const query = ev.target.value; + let queryList = []; // Only do search if there is something to search - if (query.length > 0) { + if (query.length > 0 && query != '@') { + // filter the known users list queryList = this._userList.filter((user) => { return this._matches(query, user); + }).map((user) => { + // Return objects, structure of which is defined + // by InviteAddressType + return { + addressType: 'mx', + address: user.userId, + displayName: user.displayName, + avatarMxc: user.avatarUrl, + isKnown: true, + } }); + + // If the query isn't a user we know about, but is a + // valid address, add an entry for that + if (queryList.length == 0) { + const addrType = Invite.getAddressType(query); + if (addrType !== null) { + queryList.push({ + addressType: addrType, + address: query, + isKnown: false, + }); + } + } } this.setState({ @@ -193,7 +224,7 @@ module.exports = React.createClass({ onSelected: function(index) { var inviteList = this.state.inviteList.slice(); - inviteList.push(this.state.queryList[index].userId); + inviteList.push(this.state.queryList[index]); this.setState({ inviteList: inviteList, queryList: [], @@ -228,10 +259,14 @@ module.exports = React.createClass({ return; } + const addrTexts = addrs.map((addr) => { + return addr.address; + }); + if (this.props.roomId) { // Invite new user to a room var self = this; - Invite.inviteMultipleToRoom(this.props.roomId, addrs) + Invite.inviteMultipleToRoom(this.props.roomId, addrTexts) .then(function(addrs) { var room = MatrixClientPeg.get().getRoom(self.props.roomId); return self._showAnyInviteErrors(addrs, room); @@ -246,9 +281,9 @@ module.exports = React.createClass({ return null; }) .done(); - } else if (this._isDmChat(addrs)) { + } else if (this._isDmChat(addrTexts)) { // Start the DM chat - createRoom({dmUserId: addrs[0]}) + createRoom({dmUserId: addrTexts[0]}) .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -265,7 +300,7 @@ module.exports = React.createClass({ var room; createRoom().then(function(roomId) { room = MatrixClientPeg.get().getRoom(roomId); - return Invite.inviteMultipleToRoom(roomId, addrs); + return Invite.inviteMultipleToRoom(roomId, addrTexts); }) .then(function(addrs) { return self._showAnyInviteErrors(addrs, room); @@ -283,7 +318,7 @@ module.exports = React.createClass({ } // Close - this will happen before the above, as that is async - this.props.onFinished(true, addrs); + this.props.onFinished(true, addrTexts); }, _updateUserList: new rate_limited_func(function() { @@ -334,7 +369,10 @@ module.exports = React.createClass({ _isOnInviteList: function(uid) { for (let i = 0; i < this.state.inviteList.length; i++) { - if (this.state.inviteList[i].toLowerCase() === uid) { + if ( + this.state.inviteList[i].addressType == 'mx' && + this.state.inviteList[i].address.toLowerCase() === uid + ) { return true; } } @@ -369,24 +407,37 @@ module.exports = React.createClass({ }, _addInputToList: function() { - const addrType = Invite.getAddressType(this.refs.textinput.value); - if (addrType !== null) { - const inviteList = this.state.inviteList.slice(); - inviteList.push(this.refs.textinput.value.trim()); - this.setState({ - inviteList: inviteList, - queryList: [], - }); - return inviteList; - } else { + const addressText = this.refs.textinput.value.trim(); + const addrType = Invite.getAddressType(addressText); + const addrObj = { + addressType: addrType, + address: addressText, + isKnown: false, + }; + if (addrType == null) { this.setState({ error: true }); return null; + } else if (addrType == 'mx') { + const user = MatrixClientPeg.get().getUser(addrObj.address); + if (user) { + addrObj.displayName = user.displayName; + addrObj.avatarMxc = user.avatarUrl; + addrObj.isKnown = true; + } } + + const inviteList = this.state.inviteList.slice(); + inviteList.push(addrObj); + this.setState({ + inviteList: inviteList, + queryList: [], + }); + return inviteList; }, render: function() { - var TintableSvg = sdk.getComponent("elements.TintableSvg"); - var AddressSelector = sdk.getComponent("elements.AddressSelector"); + const TintableSvg = sdk.getComponent("elements.TintableSvg"); + const AddressSelector = sdk.getComponent("elements.AddressSelector"); this.scrollElement = null; var query = []; diff --git a/src/components/views/elements/AddressSelector.js b/src/components/views/elements/AddressSelector.js index 050ab84e0a..9f37fa90ff 100644 --- a/src/components/views/elements/AddressSelector.js +++ b/src/components/views/elements/AddressSelector.js @@ -16,16 +16,19 @@ limitations under the License. 'use strict'; -var React = require("react"); -var sdk = require("../../../index"); -var classNames = require('classnames'); +import React from 'react'; +import sdk from '../../../index'; +import classNames from 'classnames'; +import { InviteAddressType } from './AddressTile'; -module.exports = React.createClass({ +export default React.createClass({ displayName: 'AddressSelector', propTypes: { onSelected: React.PropTypes.func.isRequired, - addressList: React.PropTypes.array.isRequired, + + // List of the addresses to display + addressList: React.PropTypes.arrayOf(InviteAddressType).isRequired, truncateAt: React.PropTypes.number.isRequired, selected: React.PropTypes.number, @@ -122,7 +125,7 @@ module.exports = React.createClass({ // method, how far to scroll when using the arrow keys addressList.push(