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

This commit is contained in:
Weblate 2019-01-31 16:37:18 +00:00
commit 1990a4d39f
12 changed files with 143 additions and 150 deletions

View file

@ -36,7 +36,7 @@ limitations under the License.
color: $primary-fg-color;
}
.mx_Auth_editServerDetails {
.mx_AuthBody_editServerDetails {
padding-left: 1em;
font-size: 12px;
font-weight: normal;
@ -47,21 +47,21 @@ limitations under the License.
box-sizing: border-box;
}
.mx_Auth_fieldRow {
.mx_AuthBody_fieldRow {
display: flex;
margin-bottom: 10px;
}
.mx_Auth_fieldRow > * {
.mx_AuthBody_fieldRow > * {
margin: 0 5px;
flex: 1;
}
.mx_Auth_fieldRow > *:first-child {
.mx_AuthBody_fieldRow > *:first-child {
margin-left: 0;
}
.mx_Auth_fieldRow > *:last-child {
.mx_AuthBody_fieldRow > *:last-child {
margin-right: 0;
}
@ -72,7 +72,7 @@ limitations under the License.
text-decoration: none;
}
.mx_Auth_changeFlow {
.mx_AuthBody_changeFlow {
display: block;
text-align: center;
width: 100%;

View file

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_Auth_language {
.mx_AuthBody_language {
width: 100%;
}
.mx_Auth_language .mx_Dropdown_input {
.mx_AuthBody_language .mx_Dropdown_input {
border: none;
font-size: 14px;
font-weight: 600;
color: $authpage-lang-color;
}
.mx_Auth_language .mx_Dropdown_arrow {
.mx_AuthBody_language .mx_Dropdown_arrow {
background: $authpage-lang-color;
}

View file

@ -1,27 +0,0 @@
/*
Copyright 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.
*/
/**
* Functions for applying common thematic effects to UI elements.
* Ideally this would be themeable.
*/
import Velocity from 'velocity-vector';
import 'velocity-vector/velocity.ui';
export function fieldInputIncorrect(element) {
Velocity(element, "callout.shake", 300);
}

View file

@ -1892,7 +1892,6 @@ export default React.createClass({
idSid={this.state.register_id_sid}
email={this.props.startingFragmentQueryParams.email}
referrer={this.props.startingFragmentQueryParams.referrer}
defaultServerName={this.getDefaultServerName()}
defaultServerDiscoveryError={this.state.defaultServerDiscoveryError}
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
@ -1932,7 +1931,6 @@ export default React.createClass({
<Login
onLoggedIn={Lifecycle.setLoggedIn}
onRegisterClick={this.onRegisterClick}
defaultServerName={this.getDefaultServerName()}
defaultServerDiscoveryError={this.state.defaultServerDiscoveryError}
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}

View file

@ -255,7 +255,7 @@ module.exports = React.createClass({
</form>
{ serverConfigSection }
{ errorText }
<a className="mx_Auth_changeFlow" onClick={this.onLoginClick} href="#">
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{ _t('Sign in instead') }
</a>
</div>

View file

@ -66,10 +66,6 @@ module.exports = React.createClass({
// different home server without confusing users.
fallbackHsUrl: PropTypes.string,
// The default server name to use when the user hasn't specified
// one. This is used when displaying the defaultHsUrl in the UI.
defaultServerName: PropTypes.string,
// An error passed along from higher up explaining that something
// went wrong when finding the defaultHsUrl.
defaultServerDiscoveryError: PropTypes.string,
@ -265,7 +261,10 @@ module.exports = React.createClass({
},
onUsernameBlur: function(username) {
this.setState({ username: username });
this.setState({
username: username,
discoveryError: null,
});
if (username[0] === "@") {
const serverName = username.split(':').slice(1).join(':');
try {
@ -285,16 +284,22 @@ module.exports = React.createClass({
},
onPhoneNumberChanged: function(phoneNumber) {
// Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({ errorText: _t('The phone number entered looks invalid') });
return;
}
this.setState({
phoneNumber: phoneNumber,
});
},
onPhoneNumberBlur: function(phoneNumber) {
this.setState({
errorText: null,
});
// Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({
errorText: _t('The phone number entered looks invalid'),
});
}
},
onServerConfigChange: function(config) {
@ -571,7 +576,7 @@ module.exports = React.createClass({
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}
delayTimeMs={250}
/>;
break;
case ServerType.ADVANCED:
@ -581,7 +586,7 @@ module.exports = React.createClass({
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}
delayTimeMs={250}
/>;
break;
}
@ -649,10 +654,10 @@ module.exports = React.createClass({
onUsernameBlur={this.onUsernameBlur}
onPhoneCountryChanged={this.onPhoneCountryChanged}
onPhoneNumberChanged={this.onPhoneNumberChanged}
onPhoneNumberBlur={this.onPhoneNumberBlur}
onForgotPasswordClick={this.props.onForgotPasswordClick}
loginIncorrect={this.state.loginIncorrect}
hsUrl={this.state.enteredHomeserverUrl}
hsName={this.props.defaultServerName}
disableSubmit={this.state.findingHomeserver}
/>
);
@ -684,7 +689,7 @@ module.exports = React.createClass({
let loginAsGuestJsx;
if (this.props.enableGuest) {
loginAsGuestJsx =
<a className="mx_Auth_changeFlow" onClick={this._onLoginAsGuestClick} href="#">
<a className="mx_AuthBody_changeFlow" onClick={this._onLoginAsGuestClick} href="#">
{ _t('Try the app first') }
</a>;
}
@ -709,7 +714,7 @@ module.exports = React.createClass({
{ errorTextSection }
{ this.renderServerComponentForStep() }
{ this.renderLoginComponentForStep() }
<a className="mx_Auth_changeFlow" onClick={this.onRegisterClick} href="#">
<a className="mx_AuthBody_changeFlow" onClick={this.onRegisterClick} href="#">
{ _t('Create account') }
</a>
{ loginAsGuestJsx }

View file

@ -56,10 +56,6 @@ module.exports = React.createClass({
email: PropTypes.string,
referrer: PropTypes.string,
// The default server name to use when the user hasn't specified
// one. This is used when displaying the defaultHsUrl in the UI.
defaultServerName: PropTypes.string,
// An error passed along from higher up explaining that something
// went wrong when finding the defaultHsUrl.
defaultServerDiscoveryError: PropTypes.string,
@ -151,6 +147,9 @@ module.exports = React.createClass({
},
_replaceClient: async function() {
this.setState({
errorText: null,
});
this._matrixClient = Matrix.createClient({
baseUrl: this.state.hsUrl,
idBaseUrl: this.state.isUrl,
@ -390,7 +389,7 @@ module.exports = React.createClass({
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}
delayTimeMs={250}
/>;
break;
case ServerType.ADVANCED:
@ -400,7 +399,7 @@ module.exports = React.createClass({
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}
delayTimeMs={250}
/>;
break;
}
@ -470,7 +469,6 @@ module.exports = React.createClass({
onEditServerDetailsClick={onEditServerDetailsClick}
flows={this.state.flows}
hsUrl={this.state.hsUrl}
hsName={this.props.defaultServerName}
/>;
}
},
@ -489,7 +487,7 @@ module.exports = React.createClass({
let signIn;
if (!this.state.doingUIAuth) {
signIn = (
<a className="mx_Auth_changeFlow" onClick={this.onLoginClick} href="#">
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{ _t('Sign in instead') }
</a>
);

View file

@ -32,7 +32,7 @@ export default function LanguageSelector() {
if (SdkConfig.get()['disable_login_language_selector']) return <div />;
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
return <LanguageDropdown className="mx_Auth_language"
return <LanguageDropdown className="mx_AuthBody_language"
onOptionChange={onChange}
value={getCurrentLanguage()}
/>;

View file

@ -77,19 +77,20 @@ export default class ModularServerConfig extends React.PureComponent {
});
}
onHomeserverChanged = (ev) => {
this.setState({hsUrl: ev.target.value}, () => {
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
let hsUrl = this.state.hsUrl.trim().replace(/\/$/, "");
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.props.defaultIsUrl,
});
onHomeserverBlur = (ev) => {
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.props.defaultIsUrl,
});
});
}
onHomeserverChange = (ev) => {
const hsUrl = ev.target.value;
this.setState({ hsUrl });
}
_waitThenInvoke(existingTimeoutId, fn) {
if (existingTimeoutId) {
clearTimeout(existingTimeoutId);
@ -117,7 +118,8 @@ export default class ModularServerConfig extends React.PureComponent {
label={_t("Server Name")}
placeholder={this.props.defaultHsUrl}
value={this.state.hsUrl}
onChange={this.onHomeserverChanged}
onBlur={this.onHomeserverBlur}
onChange={this.onHomeserverChange}
/>
</div>
</div>

View file

@ -20,7 +20,6 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {fieldInputIncorrect} from '../../../UiEffects';
import SdkConfig from '../../../SdkConfig';
/**
@ -35,13 +34,13 @@ class PasswordLogin extends React.Component {
onPasswordChanged: function() {},
onPhoneCountryChanged: function() {},
onPhoneNumberChanged: function() {},
onPhoneNumberBlur: function() {},
initialUsername: "",
initialPhoneCountry: "",
initialPhoneNumber: "",
initialPassword: "",
loginIncorrect: false,
hsUrl: "",
hsName: null,
disableSubmit: false,
}
@ -61,6 +60,7 @@ class PasswordLogin extends React.Component {
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
this.onPhoneNumberBlur = this.onPhoneNumberBlur.bind(this);
this.onPasswordChanged = this.onPasswordChanged.bind(this);
this.isLoginEmpty = this.isLoginEmpty.bind(this);
}
@ -70,12 +70,6 @@ class PasswordLogin extends React.Component {
this._loginField = null;
}
componentWillReceiveProps(nextProps) {
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
fieldInputIncorrect(this.isLoginEmpty() ? this._loginField : this._passwordField);
}
}
onSubmitForm(ev) {
ev.preventDefault();
@ -130,7 +124,7 @@ class PasswordLogin extends React.Component {
}
onUsernameBlur(ev) {
this.props.onUsernameBlur(this.state.username);
this.props.onUsernameBlur(ev.target.value);
}
onLoginTypeChange(loginType) {
@ -154,6 +148,10 @@ class PasswordLogin extends React.Component {
this.props.onPhoneNumberChanged(ev.target.value);
}
onPhoneNumberBlur(ev) {
this.props.onPhoneNumberBlur(ev.target.value);
}
onPasswordChanged(ev) {
this.setState({password: ev.target.value});
this.props.onPasswordChanged(ev.target.value);
@ -215,6 +213,7 @@ class PasswordLogin extends React.Component {
type="text"
name="phoneNumber"
onChange={this.onPhoneNumberChanged}
onBlur={this.onPhoneNumberBlur}
placeholder={_t("Mobile phone number")}
value={this.state.phoneNumber}
autoFocus
@ -251,20 +250,18 @@ class PasswordLogin extends React.Component {
}
let yourMatrixAccountText = _t('Your account');
if (this.props.hsName) {
yourMatrixAccountText = _t('Your %(serverName)s account', {serverName: this.props.hsName});
} else {
try {
const parsedHsUrl = new URL(this.props.hsUrl);
yourMatrixAccountText = _t('Your %(serverName)s account', {serverName: parsedHsUrl.hostname});
} catch (e) {
// ignore
}
try {
const parsedHsUrl = new URL(this.props.hsUrl);
yourMatrixAccountText = _t('Your %(serverName)s account', {
serverName: parsedHsUrl.hostname,
});
} catch (e) {
// ignore
}
let editLink = null;
if (this.props.onEditServerDetailsClick) {
editLink = <a className="mx_Auth_editServerDetails"
editLink = <a className="mx_AuthBody_editServerDetails"
href="#" onClick={this.props.onEditServerDetailsClick}
>
{_t('Edit')}
@ -341,7 +338,6 @@ PasswordLogin.propTypes = {
onPhoneNumberChanged: PropTypes.func,
onPasswordChanged: PropTypes.func,
loginIncorrect: PropTypes.bool,
hsName: PropTypes.string,
disableSubmit: PropTypes.bool,
};

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { fieldInputIncorrect } from '../../../UiEffects';
import sdk from '../../../index';
import Email from '../../../email';
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
@ -51,6 +50,7 @@ module.exports = React.createClass({
onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
onEditServerDetailsClick: PropTypes.func,
flows: PropTypes.arrayOf(PropTypes.object).isRequired,
hsUrl: PropTypes.string,
},
getDefaultProps: function() {
@ -78,11 +78,11 @@ module.exports = React.createClass({
// is the one from the first invalid field.
// It's not super ideal that this just calls
// onError once for each invalid field.
this.validateField(FIELD_PASSWORD_CONFIRM);
this.validateField(FIELD_PASSWORD);
this.validateField(FIELD_USERNAME);
this.validateField(FIELD_PHONE_NUMBER);
this.validateField(FIELD_EMAIL);
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
this.validateField(FIELD_PASSWORD, ev.type);
this.validateField(FIELD_USERNAME, ev.type);
this.validateField(FIELD_PHONE_NUMBER, ev.type);
this.validateField(FIELD_EMAIL, ev.type);
const self = this;
if (this.allFieldsValid()) {
@ -139,9 +139,10 @@ module.exports = React.createClass({
return true;
},
validateField: function(fieldID) {
validateField: function(fieldID, eventType) {
const pwd1 = this.refs.password.value.trim();
const pwd2 = this.refs.passwordConfirm.value.trim();
const allowEmpty = eventType === "blur";
switch (fieldID) {
case FIELD_EMAIL: {
@ -162,7 +163,9 @@ module.exports = React.createClass({
}
case FIELD_USERNAME: {
const username = this.refs.username.value.trim();
if (!SAFE_LOCALPART_REGEX.test(username)) {
if (allowEmpty && username === '') {
this.markFieldValid(fieldID, true);
} else if (!SAFE_LOCALPART_REGEX.test(username)) {
this.markFieldValid(
fieldID,
false,
@ -180,7 +183,9 @@ module.exports = React.createClass({
break;
}
case FIELD_PASSWORD:
if (pwd1 == '') {
if (allowEmpty && pwd1 === "") {
this.markFieldValid(fieldID, true);
} else if (pwd1 == '') {
this.markFieldValid(
fieldID,
false,
@ -210,7 +215,6 @@ module.exports = React.createClass({
fieldValid[fieldID] = val;
this.setState({fieldValid: fieldValid});
if (!val) {
fieldInputIncorrect(this.fieldElementById(fieldID));
this.props.onError(errorCode);
}
},
@ -239,13 +243,33 @@ module.exports = React.createClass({
return cls;
},
_onPhoneCountryChange(newVal) {
onEmailBlur(ev) {
this.validateField(FIELD_EMAIL, ev.type);
},
onPasswordBlur(ev) {
this.validateField(FIELD_PASSWORD, ev.type);
},
onPasswordConfirmBlur(ev) {
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
},
onPhoneCountryChange(newVal) {
this.setState({
phoneCountry: newVal.iso2,
phonePrefix: newVal.prefix,
});
},
onPhoneNumberBlur(ev) {
this.validateField(FIELD_PHONE_NUMBER, ev.type);
},
onUsernameBlur(ev) {
this.validateField(FIELD_USERNAME, ev.type);
},
_authStepIsRequired(step) {
// A step is required if no flow exists which does not include that step
// (Notwithstanding setups like either email or msisdn being required)
@ -255,27 +279,19 @@ module.exports = React.createClass({
},
render: function() {
const self = this;
let yourMatrixAccountText = _t('Create your account');
if (this.props.hsName) {
try {
const parsedHsUrl = new URL(this.props.hsUrl);
yourMatrixAccountText = _t('Create your %(serverName)s account', {
serverName: this.props.hsName,
serverName: parsedHsUrl.hostname,
});
} else {
try {
const parsedHsUrl = new URL(this.props.hsUrl);
yourMatrixAccountText = _t('Create your %(serverName)s account', {
serverName: parsedHsUrl.hostname,
});
} catch (e) {
// ignore
}
} catch (e) {
// ignore
}
let editLink = null;
if (this.props.onEditServerDetailsClick) {
editLink = <a className="mx_Auth_editServerDetails"
editLink = <a className="mx_AuthBody_editServerDetails"
href="#" onClick={this.props.onEditServerDetailsClick}
>
{_t('Edit')}
@ -292,8 +308,8 @@ module.exports = React.createClass({
autoFocus={true} placeholder={emailPlaceholder}
defaultValue={this.props.defaultEmail}
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_EMAIL);}}
value={self.state.email} />
onBlur={this.onEmailBlur}
value={this.state.email} />
</div>
);
@ -305,11 +321,12 @@ module.exports = React.createClass({
_t("Phone (optional)");
phoneSection = (
<div className="mx_Login_phoneSection">
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
<CountryDropdown ref="phone_country"
className="mx_Login_phoneCountry mx_Login_field_prefix"
value={this.state.phoneCountry}
isSmall={true}
showPrefix={true}
onOptionChange={this.onPhoneCountryChange}
/>
<input type="text" ref="phoneNumber"
placeholder={phonePlaceholder}
@ -320,8 +337,8 @@ module.exports = React.createClass({
'mx_Login_field',
'mx_Login_field_has_prefix',
)}
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
value={self.state.phoneNumber}
onBlur={this.onPhoneNumberBlur}
value={this.state.phoneNumber}
/>
</div>
);
@ -340,24 +357,24 @@ module.exports = React.createClass({
{editLink}
</h3>
<form onSubmit={this.onSubmit}>
<div className="mx_Auth_fieldRow">
<div className="mx_AuthBody_fieldRow">
<input type="text" ref="username"
placeholder={placeholderUsername} defaultValue={this.props.defaultUsername}
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
onBlur={this.onUsernameBlur} />
</div>
<div className="mx_Auth_fieldRow">
<div className="mx_AuthBody_fieldRow">
<input type="password" ref="password"
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
onBlur={this.onPasswordBlur}
placeholder={_t("Password")} defaultValue={this.props.defaultPassword} />
<input type="password" ref="passwordConfirm"
placeholder={_t("Confirm")}
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
onBlur={this.onPasswordConfirmBlur}
defaultValue={this.props.defaultPassword} />
</div>
<div className="mx_Auth_fieldRow">
<div className="mx_AuthBody_fieldRow">
{ emailSection }
{ phoneSection }
</div>

View file

@ -76,32 +76,34 @@ export default class ServerConfig extends React.PureComponent {
});
}
onHomeserverChanged = (ev) => {
this.setState({hsUrl: ev.target.value}, () => {
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
let hsUrl = this.state.hsUrl.trim().replace(/\/$/, "");
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.state.isUrl,
});
onHomeserverBlur = (ev) => {
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.state.isUrl,
});
});
}
onIdentityServerChanged = (ev) => {
this.setState({isUrl: ev.target.value}, () => {
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, () => {
let isUrl = this.state.isUrl.trim().replace(/\/$/, "");
if (isUrl === "") isUrl = this.props.defaultIsUrl;
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.state.isUrl,
});
onHomeserverChange = (ev) => {
const hsUrl = ev.target.value;
this.setState({ hsUrl });
}
onIdentityServerBlur = (ev) => {
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, () => {
this.props.onServerConfigChange({
hsUrl: this.state.hsUrl,
isUrl: this.state.isUrl,
});
});
}
onIdentityServerChange = (ev) => {
const isUrl = ev.target.value;
this.setState({ isUrl });
}
_waitThenInvoke(existingTimeoutId, fn) {
if (existingTimeoutId) {
clearTimeout(existingTimeoutId);
@ -130,13 +132,15 @@ export default class ServerConfig extends React.PureComponent {
label={_t("Homeserver URL")}
placeholder={this.props.defaultHsUrl}
value={this.state.hsUrl}
onChange={this.onHomeserverChanged}
onBlur={this.onHomeserverBlur}
onChange={this.onHomeserverChange}
/>
<Field id="mx_ServerConfig_isUrl"
label={_t("Identity Server URL")}
placeholder={this.props.defaultIsUrl}
value={this.state.isUrl}
onChange={this.onIdentityServerChanged}
onBlur={this.onIdentityServerBlur}
onChange={this.onIdentityServerChange}
/>
</div>
</div>