diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index 3633670f25..2f635fd670 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -26,6 +26,7 @@ import AccessibleButton from '../elements/AccessibleButton'; import q from 'q'; const TRUNCATE_QUERY_LIST = 40; +const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; module.exports = React.createClass({ displayName: "ChatInviteDialog", @@ -40,13 +41,13 @@ module.exports = React.createClass({ roomId: React.PropTypes.string, button: React.PropTypes.string, focus: React.PropTypes.bool, - onFinished: React.PropTypes.func.isRequired + onFinished: React.PropTypes.func.isRequired, }, getDefaultProps: function() { return { value: "", - focus: true + focus: true, }; }, @@ -54,12 +55,20 @@ module.exports = React.createClass({ return { error: false, - // List of AddressTile.InviteAddressType objects represeting + // List of AddressTile.InviteAddressType objects representing // 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 + // Whether a search is ongoing + busy: false, + // An error message generated during the user directory search + searchError: null, + // Whether the server supports the user_directory API + serverSupportsUserDirectory: true, + // The query being searched for + query: "", + // List of AddressTile.InviteAddressType objects representing + // the set of auto-completion results for the current search // query. queryList: [], }; @@ -70,7 +79,6 @@ module.exports = React.createClass({ // Set the cursor at the end of the text input this.refs.textinput.value = this.props.value; } - this._updateUserList(); }, onButtonClick: function() { @@ -137,15 +145,15 @@ module.exports = React.createClass({ } else if (e.keyCode === 38) { // up arrow e.stopPropagation(); e.preventDefault(); - this.addressSelector.moveSelectionUp(); + if (this.addressSelector) this.addressSelector.moveSelectionUp(); } else if (e.keyCode === 40) { // down arrow e.stopPropagation(); e.preventDefault(); - this.addressSelector.moveSelectionDown(); + if (this.addressSelector) this.addressSelector.moveSelectionDown(); } else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab e.stopPropagation(); e.preventDefault(); - this.addressSelector.chooseSelection(); + if (this.addressSelector) this.addressSelector.chooseSelection(); } else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace e.stopPropagation(); e.preventDefault(); @@ -168,74 +176,36 @@ module.exports = React.createClass({ onQueryChanged: function(ev) { const query = ev.target.value.toLowerCase(); - let queryList = []; - - if (query.length < 2) { - return; - } - if (this.queryChangedDebouncer) { clearTimeout(this.queryChangedDebouncer); } - this.queryChangedDebouncer = setTimeout(() => { - // Only do search if there is something to search - if (query.length > 0 && query != '@') { - this._userList.forEach((user) => { - if (user.userId.toLowerCase().indexOf(query) === -1 && - user.displayName.toLowerCase().indexOf(query) === -1 - ) { - return; - } - - // Return objects, structure of which is defined - // by InviteAddressType - queryList.push({ - addressType: 'mx', - address: user.userId, - displayName: user.displayName, - avatarMxc: user.avatarUrl, - isKnown: true, - order: user.getLastActiveTs(), - }); - }); - - queryList = queryList.sort((a,b) => { - return a.order < b.order; - }); - - // If the query is a valid address, add an entry for that - // 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) { - queryList.unshift({ - addressType: addrType, - address: query, - isKnown: false, - }); - if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - if (addrType == 'email') { - this._lookupThreepid(addrType, query).done(); - } + // Only do search if there is something to search + if (query.length > 0 && query != '@' && query.length >= 2) { + this.queryChangedDebouncer = setTimeout(() => { + if (this.state.serverSupportsUserDirectory) { + this._doUserDirectorySearch(query); + } else { + this._doLocalSearch(query); } - } + }, QUERY_USER_DIRECTORY_DEBOUNCE_MS); + } else { this.setState({ - queryList: queryList, - error: false, - }, () => { - this.addressSelector.moveSelectionTop(); + queryList: [], + query: "", + searchError: null, }); - }, 200); + } }, onDismissed: function(index) { var self = this; - return function() { + return () => { var inviteList = self.state.inviteList.slice(); inviteList.splice(index, 1); self.setState({ inviteList: inviteList, queryList: [], + query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); }; @@ -254,10 +224,103 @@ module.exports = React.createClass({ this.setState({ inviteList: inviteList, queryList: [], + query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); }, + _doUserDirectorySearch: function(query) { + this.setState({ + busy: true, + query, + searchError: null, + }); + MatrixClientPeg.get().searchUserDirectory({ + term: query, + }).then((resp) => { + this._processResults(resp.results, query); + }).catch((err) => { + console.error('Error whilst searching user directory: ', err); + this.setState({ + searchError: err.errcode ? err.message : _t('Something went wrong!'), + }); + if (err.errcode === 'M_UNRECOGNIZED') { + this.setState({ + serverSupportsUserDirectory: false, + }); + // Do a local search immediately + this._doLocalSearch(query); + } + }).done(() => { + this.setState({ + busy: false, + }); + }); + }, + + _doLocalSearch: function(query) { + this.setState({ + query, + searchError: null, + }); + const results = []; + MatrixClientPeg.get().getUsers().forEach((user) => { + if (user.userId.toLowerCase().indexOf(query) === -1 && + user.displayName.toLowerCase().indexOf(query) === -1 + ) { + return; + } + + // Put results in the format of the new API + results.push({ + user_id: user.userId, + display_name: user.displayName, + avatar_url: user.avatarUrl, + }); + }); + this._processResults(results, query); + }, + + _processResults: function(results, query) { + const queryList = []; + results.forEach((user) => { + if (user.user_id === MatrixClientPeg.get().credentials.userId) { + return; + } + // Return objects, structure of which is defined + // by InviteAddressType + queryList.push({ + addressType: 'mx', + address: user.user_id, + displayName: user.display_name, + avatarMxc: user.avatar_url, + isKnown: true, + }); + }); + + // If the query is a valid address, add an entry for that + // 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) { + queryList.unshift({ + addressType: addrType, + address: query, + isKnown: false, + }); + if (this._cancelThreepidLookup) this._cancelThreepidLookup(); + if (addrType == 'email') { + this._lookupThreepid(addrType, query).done(); + } + } + this.setState({ + queryList, + error: false, + }, () => { + if (this.addressSelector) this.addressSelector.moveSelectionTop(); + }); + }, + _getDirectMessageRooms: function(addr) { const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); @@ -342,16 +405,6 @@ module.exports = React.createClass({ this.props.onFinished(true, addrTexts); }, - _updateUserList: function() { - // Get all the users - this._userList = MatrixClientPeg.get().getUsers(); - // Remove current user - const meIx = this._userList.findIndex((u) => { - return u.userId === MatrixClientPeg.get().credentials.userId; - }); - this._userList.splice(meIx, 1); - }, - _isOnInviteList: function(uid) { for (let i = 0; i < this.state.inviteList.length; i++) { if ( @@ -419,6 +472,7 @@ module.exports = React.createClass({ this.setState({ inviteList: inviteList, queryList: [], + query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); return inviteList; @@ -454,7 +508,7 @@ module.exports = React.createClass({ displayName: res.displayname, avatarMxc: res.avatar_url, isKnown: true, - }] + }], }); }); }, @@ -486,23 +540,27 @@ module.exports = React.createClass({ placeholder={this.props.placeholder} defaultValue={this.props.value} autoFocus={this.props.focus}> - + , ); - var error; - var addressSelector; + let error; + let addressSelector; if (this.state.error) { error =