Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Weblate 2018-06-21 13:07:59 +00:00
commit 6642292d1e

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd Copyright 2017, 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import Promise from 'bluebird'; import Promise from 'bluebird';
@ -27,6 +27,13 @@ import GroupStore from '../../../stores/GroupStore';
const TRUNCATE_QUERY_LIST = 40; const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
const addressTypeName = {
'mx-user-id': _td("Matrix ID"),
'mx-room-id': _td("Matrix Room ID"),
'email': _td("email address"),
};
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "AddressPickerDialog", displayName: "AddressPickerDialog",
@ -66,7 +73,7 @@ module.exports = React.createClass({
// List of UserAddressType objects representing // List of UserAddressType objects representing
// the list of addresses we're going to invite // the list of addresses we're going to invite
userList: [], selectedList: [],
// Whether a search is ongoing // Whether a search is ongoing
busy: false, busy: false,
@ -76,10 +83,9 @@ module.exports = React.createClass({
serverSupportsUserDirectory: true, serverSupportsUserDirectory: true,
// The query being searched for // The query being searched for
query: "", query: "",
// List of UserAddressType objects representing // List of UserAddressType objects representing the set of
// the set of auto-completion results for the current search // auto-completion results for the current search query.
// query. suggestedList: [],
queryList: [],
}; };
}, },
@ -91,14 +97,14 @@ module.exports = React.createClass({
}, },
onButtonClick: function() { onButtonClick: function() {
let userList = this.state.userList.slice(); let selectedList = this.state.selectedList.slice();
// Check the text input field to see if user has an unconverted address // 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 userList // If there is and it's valid add it to the local selectedList
if (this.refs.textinput.value !== '') { if (this.refs.textinput.value !== '') {
userList = this._addInputToList(); selectedList = this._addInputToList();
if (userList === null) return; if (selectedList === null) return;
} }
this.props.onFinished(true, userList); this.props.onFinished(true, selectedList);
}, },
onCancel: function() { onCancel: function() {
@ -118,18 +124,18 @@ module.exports = React.createClass({
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (this.addressSelector) 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 } else if (this.state.suggestedList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (this.addressSelector) this.addressSelector.chooseSelection(); if (this.addressSelector) this.addressSelector.chooseSelection();
} else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace } else if (this.refs.textinput.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.onDismissed(this.state.userList.length - 1)(); this.onDismissed(this.state.selectedList.length - 1)();
} else if (e.keyCode === 13) { // enter } else if (e.keyCode === 13) { // enter
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (this.refs.textinput.value == '') { if (this.refs.textinput.value === '') {
// if there's nothing in the input box, submit the form // if there's nothing in the input box, submit the form
this.onButtonClick(); this.onButtonClick();
} else { } else {
@ -148,7 +154,7 @@ module.exports = React.createClass({
clearTimeout(this.queryChangedDebouncer); clearTimeout(this.queryChangedDebouncer);
} }
// Only do search if there is something to search // Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) { if (query.length > 0 && query !== '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => { this.queryChangedDebouncer = setTimeout(() => {
if (this.props.pickerType === 'user') { if (this.props.pickerType === 'user') {
if (this.props.groupId) { if (this.props.groupId) {
@ -170,7 +176,7 @@ module.exports = React.createClass({
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS); }, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else { } else {
this.setState({ this.setState({
queryList: [], suggestedList: [],
query: "", query: "",
searchError: null, searchError: null,
}); });
@ -179,11 +185,11 @@ module.exports = React.createClass({
onDismissed: function(index) { onDismissed: function(index) {
return () => { return () => {
const userList = this.state.userList.slice(); const selectedList = this.state.selectedList.slice();
userList.splice(index, 1); selectedList.splice(index, 1);
this.setState({ this.setState({
userList: userList, selectedList,
queryList: [], suggestedList: [],
query: "", query: "",
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
@ -197,11 +203,11 @@ module.exports = React.createClass({
}, },
onSelected: function(index) { onSelected: function(index) {
const userList = this.state.userList.slice(); const selectedList = this.state.selectedList.slice();
userList.push(this.state.queryList[index]); selectedList.push(this.state.suggestedList[index]);
this.setState({ this.setState({
userList: userList, selectedList,
queryList: [], suggestedList: [],
query: "", query: "",
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
@ -379,10 +385,10 @@ module.exports = React.createClass({
}, },
_processResults: function(results, query) { _processResults: function(results, query) {
const queryList = []; const suggestedList = [];
results.forEach((result) => { results.forEach((result) => {
if (result.room_id) { if (result.room_id) {
queryList.push({ suggestedList.push({
addressType: 'mx-room-id', addressType: 'mx-room-id',
address: result.room_id, address: result.room_id,
displayName: result.name, displayName: result.name,
@ -399,7 +405,7 @@ module.exports = React.createClass({
// Return objects, structure of which is defined // Return objects, structure of which is defined
// by UserAddressType // by UserAddressType
queryList.push({ suggestedList.push({
addressType: 'mx-user-id', addressType: 'mx-user-id',
address: result.user_id, address: result.user_id,
displayName: result.display_name, displayName: result.display_name,
@ -413,18 +419,18 @@ module.exports = React.createClass({
// a perfectly valid address if there are close matches. // a perfectly valid address if there are close matches.
const addrType = getAddressType(query); const addrType = getAddressType(query);
if (this.props.validAddressTypes.includes(addrType)) { if (this.props.validAddressTypes.includes(addrType)) {
queryList.unshift({ suggestedList.unshift({
addressType: addrType, addressType: addrType,
address: query, address: query,
isKnown: false, isKnown: false,
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (addrType == 'email') { if (addrType === 'email') {
this._lookupThreepid(addrType, query).done(); this._lookupThreepid(addrType, query).done();
} }
} }
this.setState({ this.setState({
queryList, suggestedList,
error: false, error: false,
}, () => { }, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop(); if (this.addressSelector) this.addressSelector.moveSelectionTop();
@ -442,14 +448,14 @@ module.exports = React.createClass({
if (!this.props.validAddressTypes.includes(addrType)) { if (!this.props.validAddressTypes.includes(addrType)) {
this.setState({ error: true }); this.setState({ error: true });
return null; return null;
} else if (addrType == 'mx-user-id') { } else if (addrType === 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address); const user = MatrixClientPeg.get().getUser(addrObj.address);
if (user) { if (user) {
addrObj.displayName = user.displayName; addrObj.displayName = user.displayName;
addrObj.avatarMxc = user.avatarUrl; addrObj.avatarMxc = user.avatarUrl;
addrObj.isKnown = true; addrObj.isKnown = true;
} }
} else if (addrType == 'mx-room-id') { } else if (addrType === 'mx-room-id') {
const room = MatrixClientPeg.get().getRoom(addrObj.address); const room = MatrixClientPeg.get().getRoom(addrObj.address);
if (room) { if (room) {
addrObj.displayName = room.name; addrObj.displayName = room.name;
@ -458,15 +464,15 @@ module.exports = React.createClass({
} }
} }
const userList = this.state.userList.slice(); const selectedList = this.state.selectedList.slice();
userList.push(addrObj); selectedList.push(addrObj);
this.setState({ this.setState({
userList: userList, selectedList,
queryList: [], suggestedList: [],
query: "", query: "",
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return userList; return selectedList;
}, },
_lookupThreepid: function(medium, address) { _lookupThreepid: function(medium, address) {
@ -492,7 +498,7 @@ module.exports = React.createClass({
if (res === null) return null; if (res === null) return null;
if (cancelled) return null; if (cancelled) return null;
this.setState({ this.setState({
queryList: [{ suggestedList: [{
// a UserAddressType // a UserAddressType
addressType: medium, addressType: medium,
address: address, address: address,
@ -510,15 +516,27 @@ module.exports = React.createClass({
const AddressSelector = sdk.getComponent("elements.AddressSelector"); const AddressSelector = sdk.getComponent("elements.AddressSelector");
this.scrollElement = null; this.scrollElement = null;
// map addressType => set of addresses to avoid O(n*m) operation
const selectedAddresses = {};
this.state.selectedList.forEach(({address, addressType}) => {
if (!selectedAddresses[addressType]) selectedAddresses[addressType] = new Set();
selectedAddresses[addressType].add(address);
});
// Filter out any addresses in the above already selected addresses (matching both type and address)
const filteredSuggestedList = this.state.suggestedList.filter(({address, addressType}) => {
return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address));
});
const query = []; const query = [];
// create the invite list // create the invite list
if (this.state.userList.length > 0) { if (this.state.selectedList.length > 0) {
const AddressTile = sdk.getComponent("elements.AddressTile"); const AddressTile = sdk.getComponent("elements.AddressTile");
for (let i = 0; i < this.state.userList.length; i++) { for (let i = 0; i < this.state.selectedList.length; i++) {
query.push( query.push(
<AddressTile <AddressTile
key={i} key={i}
address={this.state.userList[i]} address={this.state.selectedList[i]}
canDismiss={true} canDismiss={true}
onDismissed={this.onDismissed(i)} onDismissed={this.onDismissed(i)}
showAddress={this.props.pickerType === 'user'} />, showAddress={this.props.pickerType === 'user'} />,
@ -528,7 +546,7 @@ module.exports = React.createClass({
// Add the query at the end // Add the query at the end
query.push( query.push(
<textarea key={this.state.userList.length} <textarea key={this.state.selectedList.length}
rows="1" rows="1"
id="textinput" id="textinput"
ref="textinput" ref="textinput"
@ -543,34 +561,22 @@ module.exports = React.createClass({
let error; let error;
let addressSelector; let addressSelector;
if (this.state.error) { if (this.state.error) {
let tryUsing = ''; const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t]));
const validTypeDescriptions = this.props.validAddressTypes.map((t) => {
return {
'mx-user-id': _t("Matrix ID"),
'mx-room-id': _t("Matrix Room ID"),
'email': _t("email address"),
}[t];
});
tryUsing = _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
});
error = <div className="mx_ChatInviteDialog_error"> error = <div className="mx_ChatInviteDialog_error">
{ _t("You have entered an invalid address.") } { _t("You have entered an invalid address.") }
<br /> <br />
{ tryUsing } { _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
}) }
</div>; </div>;
} else if (this.state.searchError) { } else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>; error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>;
} else if ( } else if (this.state.query.length > 0 && filteredSuggestedList.length === 0 && !this.state.busy) {
this.state.query.length > 0 &&
this.state.queryList.length === 0 &&
!this.state.busy
) {
error = <div className="mx_ChatInviteDialog_error">{ _t("No results") }</div>; error = <div className="mx_ChatInviteDialog_error">{ _t("No results") }</div>;
} else { } else {
addressSelector = ( addressSelector = (
<AddressSelector ref={(ref) => {this.addressSelector = ref;}} <AddressSelector ref={(ref) => {this.addressSelector = ref;}}
addressList={this.state.queryList} addressList={filteredSuggestedList}
showAddress={this.props.pickerType === 'user'} showAddress={this.props.pickerType === 'user'}
onSelected={this.onSelected} onSelected={this.onSelected}
truncateAt={TRUNCATE_QUERY_LIST} truncateAt={TRUNCATE_QUERY_LIST}