Merge pull request #2780 from jryans/auth-validation
Initial portions of support for Field validation
This commit is contained in:
commit
45063cad4a
18 changed files with 262 additions and 100 deletions
|
@ -95,6 +95,7 @@
|
||||||
@import "./views/elements/_SyntaxHighlight.scss";
|
@import "./views/elements/_SyntaxHighlight.scss";
|
||||||
@import "./views/elements/_ToggleSwitch.scss";
|
@import "./views/elements/_ToggleSwitch.scss";
|
||||||
@import "./views/elements/_ToolTipButton.scss";
|
@import "./views/elements/_ToolTipButton.scss";
|
||||||
|
@import "./views/elements/_Tooltip.scss";
|
||||||
@import "./views/globals/_MatrixToolbar.scss";
|
@import "./views/globals/_MatrixToolbar.scss";
|
||||||
@import "./views/groups/_GroupPublicityToggle.scss";
|
@import "./views/groups/_GroupPublicityToggle.scss";
|
||||||
@import "./views/groups/_GroupRoomList.scss";
|
@import "./views/groups/_GroupRoomList.scss";
|
||||||
|
@ -137,7 +138,6 @@
|
||||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomTooltip.scss";
|
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
@import "./views/rooms/_SearchBar.scss";
|
@import "./views/rooms/_SearchBar.scss";
|
||||||
@import "./views/rooms/_SearchableEntityList.scss";
|
@import "./views/rooms/_SearchableEntityList.scss";
|
||||||
|
|
|
@ -141,6 +141,39 @@ limitations under the License.
|
||||||
color: $greyed-fg-color;
|
color: $greyed-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Field_valid {
|
||||||
|
&.mx_Field,
|
||||||
|
&.mx_Field:focus-within {
|
||||||
|
border-color: $input-valid-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_Field label,
|
||||||
|
&.mx_Field:focus-within label {
|
||||||
|
color: $input-valid-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field_invalid {
|
||||||
|
&.mx_Field,
|
||||||
|
&.mx_Field:focus-within {
|
||||||
|
border-color: $input-invalid-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_Field label,
|
||||||
|
&.mx_Field:focus-within label {
|
||||||
|
color: $input-invalid-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field_tooltip {
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field_tooltip.mx_Field_valid {
|
||||||
|
animation: mx_fadeout 1s 2s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
// Customise other components when placed inside a Field
|
// Customise other components when placed inside a Field
|
||||||
|
|
||||||
.mx_Field .mx_Dropdown_input {
|
.mx_Field .mx_Dropdown_input {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2019 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.
|
||||||
|
@ -14,41 +15,55 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomTooltip_chevron {
|
@keyframes mx_fadein {
|
||||||
position: absolute;
|
from { opacity: 0; }
|
||||||
left: -8px;
|
to { opacity: 1; }
|
||||||
top: 4px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 8px solid transparent;
|
|
||||||
border-right: 8px solid $menu-bg-color;
|
|
||||||
border-bottom: 8px solid transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTooltip_chevron:after {
|
@keyframes mx_fadeout {
|
||||||
content:'';
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Tooltip_chevron {
|
||||||
|
position: absolute;
|
||||||
|
left: -7px;
|
||||||
|
top: 10px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-top: 7px solid transparent;
|
border-top: 7px solid transparent;
|
||||||
border-right: 7px solid $primary-bg-color;
|
border-right: 7px solid $menu-border-color;
|
||||||
border-bottom: 7px solid transparent;
|
border-bottom: 7px solid transparent;
|
||||||
position:absolute;
|
}
|
||||||
top: -7px;
|
|
||||||
|
.mx_Tooltip_chevron:after {
|
||||||
|
content:'';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 6px solid transparent;
|
||||||
|
border-right: 6px solid $menu-bg-color;
|
||||||
|
border-bottom: 6px solid transparent;
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTooltip {
|
.mx_Tooltip {
|
||||||
display: none;
|
display: none;
|
||||||
|
animation: mx_fadein 0.2s;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
border-radius: 5px;
|
border: 1px solid $menu-border-color;
|
||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.21);
|
border-radius: 4px;
|
||||||
background-color: $primary-bg-color;
|
box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);
|
||||||
|
background-color: $menu-bg-color;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
padding: 5px;
|
padding: 10px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
max-width: 600px;
|
max-width: 200px;
|
||||||
|
word-break: break-word;
|
||||||
margin-right: 50px;
|
margin-right: 50px;
|
||||||
}
|
}
|
|
@ -53,6 +53,7 @@ $input-lighter-bg-color: #f2f5f8;
|
||||||
$input-lighter-fg-color: $input-darker-fg-color;
|
$input-lighter-fg-color: $input-darker-fg-color;
|
||||||
$input-focused-border-color: #238cf5;
|
$input-focused-border-color: #238cf5;
|
||||||
$input-valid-border-color: $accent-color;
|
$input-valid-border-color: $accent-color;
|
||||||
|
$input-invalid-border-color: $warning-color;
|
||||||
|
|
||||||
$field-focused-label-bg-color: $bg-color;
|
$field-focused-label-bg-color: $bg-color;
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ $input-lighter-bg-color: #f2f5f8;
|
||||||
$input-lighter-fg-color: $input-darker-fg-color;
|
$input-lighter-fg-color: $input-darker-fg-color;
|
||||||
$input-focused-border-color: #238cf5;
|
$input-focused-border-color: #238cf5;
|
||||||
$input-valid-border-color: $accent-color;
|
$input-valid-border-color: $accent-color;
|
||||||
|
$input-invalid-border-color: $warning-color;
|
||||||
|
|
||||||
$field-focused-label-bg-color: #ffffff;
|
$field-focused-label-bg-color: #ffffff;
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ $input-fg-color: rgba(74, 74, 74, 0.9);
|
||||||
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
|
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
|
||||||
$scrollbar-track-color: transparent;
|
$scrollbar-track-color: transparent;
|
||||||
// context menus
|
// context menus
|
||||||
$menu-border-color: #ebedf8;
|
$menu-border-color: #e7e7e7;
|
||||||
$menu-bg-color: #fff;
|
$menu-bg-color: #fff;
|
||||||
$menu-box-shadow-color: rgba(118, 131, 156, 0.6);
|
$menu-box-shadow-color: rgba(118, 131, 156, 0.6);
|
||||||
$menu-selected-color: #f5f8fa;
|
$menu-selected-color: #f5f8fa;
|
||||||
|
|
|
@ -145,8 +145,8 @@ module.exports = React.createClass({
|
||||||
// Get the label/tooltip to show
|
// Get the label/tooltip to show
|
||||||
getLabel: function(label, show) {
|
getLabel: function(label, show) {
|
||||||
if (show) {
|
if (show) {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
return <Tooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -70,11 +70,6 @@ class PasswordLogin extends React.Component {
|
||||||
this.isLoginEmpty = this.isLoginEmpty.bind(this);
|
this.isLoginEmpty = this.isLoginEmpty.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this._passwordField = null;
|
|
||||||
this._loginField = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
onForgotPasswordClick(ev) {
|
onForgotPasswordClick(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
@ -180,7 +175,6 @@ class PasswordLogin extends React.Component {
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_email"
|
id="mx_PasswordLogin_email"
|
||||||
ref={(e) => { this._loginField = e; }}
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
name="username" // make it a little easier for browser's remember-password
|
||||||
key="email_input"
|
key="email_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -196,7 +190,6 @@ class PasswordLogin extends React.Component {
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_username"
|
id="mx_PasswordLogin_username"
|
||||||
ref={(e) => { this._loginField = e; }}
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
name="username" // make it a little easier for browser's remember-password
|
||||||
key="username_input"
|
key="username_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -223,7 +216,6 @@ class PasswordLogin extends React.Component {
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_phoneNumber"
|
id="mx_PasswordLogin_phoneNumber"
|
||||||
ref={(e) => { this._loginField = e; }}
|
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
key="phone_input"
|
key="phone_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -344,7 +336,6 @@ class PasswordLogin extends React.Component {
|
||||||
<Field
|
<Field
|
||||||
className={pwFieldClass}
|
className={pwFieldClass}
|
||||||
id="mx_PasswordLogin_password"
|
id="mx_PasswordLogin_password"
|
||||||
ref={(e) => { this._passwordField = e; }}
|
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
label={_t('Password')}
|
label={_t('Password')}
|
||||||
|
|
|
@ -69,8 +69,8 @@ export default React.createClass({
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (this.state.showTooltip) {
|
if (this.state.showTooltip) {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
|
tooltip = <Tooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = this.props.iconPath ?
|
const icon = this.props.iconPath ?
|
||||||
|
|
|
@ -17,6 +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 classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
|
||||||
export default class Field extends React.PureComponent {
|
export default class Field extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -31,25 +32,43 @@ export default class Field extends React.PureComponent {
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
// The field's placeholder string. Defaults to the label.
|
// The field's placeholder string. Defaults to the label.
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
|
// The field's value.
|
||||||
|
// This is a controlled component, so the value is required.
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
// Optional component to include inside the field before the input.
|
// Optional component to include inside the field before the input.
|
||||||
prefix: PropTypes.node,
|
prefix: PropTypes.node,
|
||||||
|
// The callback called whenever the contents of the field
|
||||||
|
// changes. Returns an object with `valid` boolean field
|
||||||
|
// and a `feedback` react component field to provide feedback
|
||||||
|
// to the user.
|
||||||
|
onValidate: PropTypes.func,
|
||||||
// All other props pass through to the <input>.
|
// All other props pass through to the <input>.
|
||||||
};
|
};
|
||||||
|
|
||||||
get value() {
|
constructor() {
|
||||||
if (!this.refs.fieldInput) return null;
|
super();
|
||||||
return this.refs.fieldInput.value;
|
this.state = {
|
||||||
|
valid: undefined,
|
||||||
|
feedback: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(newValue) {
|
onChange = (ev) => {
|
||||||
if (!this.refs.fieldInput) {
|
if (this.props.onValidate) {
|
||||||
throw new Error("No field input reference");
|
const result = this.props.onValidate(ev.target.value);
|
||||||
|
this.setState({
|
||||||
|
valid: result.valid,
|
||||||
|
feedback: result.feedback,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.refs.fieldInput.value = newValue;
|
// Parent component may have supplied its own `onChange` as well
|
||||||
}
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { element, prefix, children, ...inputProps } = this.props;
|
const { element, prefix, onValidate, children, ...inputProps } = this.props;
|
||||||
|
|
||||||
const inputElement = element || "input";
|
const inputElement = element || "input";
|
||||||
|
|
||||||
|
@ -58,6 +77,8 @@ export default class Field extends React.PureComponent {
|
||||||
inputProps.ref = "fieldInput";
|
inputProps.ref = "fieldInput";
|
||||||
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
||||||
|
|
||||||
|
inputProps.onChange = this.onChange;
|
||||||
|
|
||||||
const fieldInput = React.createElement(inputElement, inputProps, children);
|
const fieldInput = React.createElement(inputElement, inputProps, children);
|
||||||
|
|
||||||
let prefixContainer = null;
|
let prefixContainer = null;
|
||||||
|
@ -65,17 +86,37 @@ export default class Field extends React.PureComponent {
|
||||||
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
|
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = classNames("mx_Field", `mx_Field_${inputElement}`, {
|
let validClass;
|
||||||
|
if (onValidate) {
|
||||||
|
validClass = classNames({
|
||||||
|
mx_Field_valid: this.state.valid === true,
|
||||||
|
mx_Field_invalid: this.state.valid === false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
|
||||||
// If we have a prefix element, leave the label always at the top left and
|
// If we have a prefix element, leave the label always at the top left and
|
||||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||||
// properly.
|
// properly.
|
||||||
mx_Field_labelAlwaysTopLeft: prefix,
|
mx_Field_labelAlwaysTopLeft: prefix,
|
||||||
|
[validClass]: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div className={classes}>
|
// handle displaying feedback on validity
|
||||||
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
|
let feedback;
|
||||||
|
if (this.state.feedback) {
|
||||||
|
feedback = <Tooltip
|
||||||
|
tooltipClassName="mx_Field_tooltip"
|
||||||
|
label={this.state.feedback}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={fieldClasses}>
|
||||||
{prefixContainer}
|
{prefixContainer}
|
||||||
{fieldInput}
|
{fieldInput}
|
||||||
<label htmlFor={this.props.id}>{this.props.label}</label>
|
<label htmlFor={this.props.id}>{this.props.label}</label>
|
||||||
|
{feedback}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ export default React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||||
const profile = this.state.profile || {};
|
const profile = this.state.profile || {};
|
||||||
const name = profile.name || this.props.tag;
|
const name = profile.name || this.props.tag;
|
||||||
const avatarHeight = 40;
|
const avatarHeight = 40;
|
||||||
|
@ -181,7 +181,7 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
const tip = this.state.hover ?
|
const tip = this.state.hover ?
|
||||||
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||||
<div />;
|
<div />;
|
||||||
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
||||||
<div className="mx_TagTile_context_button" onClick={this.onContextButtonClick}>
|
<div className="mx_TagTile_context_button" onClick={this.onContextButtonClick}>
|
||||||
|
|
|
@ -39,8 +39,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
const tip = this.state.hover ? <RoomTooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_ToolTipButton_container"
|
className="mx_ToolTipButton_container"
|
||||||
tooltipClassName="mx_ToolTipButton_helpText"
|
tooltipClassName="mx_ToolTipButton_helpText"
|
||||||
label={this.props.helpText}
|
label={this.props.helpText}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2019 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.
|
||||||
|
@ -23,22 +24,21 @@ import classNames from 'classnames';
|
||||||
const MIN_TOOLTIP_HEIGHT = 25;
|
const MIN_TOOLTIP_HEIGHT = 25;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomTooltip',
|
displayName: 'Tooltip',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// Class applied to the element used to position the tooltip
|
// Class applied to the element used to position the tooltip
|
||||||
className: React.PropTypes.string.isRequired,
|
className: React.PropTypes.string,
|
||||||
// Class applied to the tooltip itself
|
// Class applied to the tooltip itself
|
||||||
tooltipClassName: React.PropTypes.string,
|
tooltipClassName: React.PropTypes.string,
|
||||||
// The tooltip is derived from either the room name or a label
|
// the react element to put into the tooltip
|
||||||
room: React.PropTypes.object,
|
|
||||||
label: React.PropTypes.node,
|
label: React.PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this.tooltipContainer = document.createElement("div");
|
this.tooltipContainer = document.createElement("div");
|
||||||
this.tooltipContainer.className = "mx_RoomTileTooltip_wrapper";
|
this.tooltipContainer.className = "mx_Tooltip_wrapper";
|
||||||
document.body.appendChild(this.tooltipContainer);
|
document.body.appendChild(this.tooltipContainer);
|
||||||
window.addEventListener('scroll', this._renderTooltip, true);
|
window.addEventListener('scroll', this._renderTooltip, true);
|
||||||
|
|
||||||
|
@ -85,11 +85,11 @@ module.exports = React.createClass({
|
||||||
style = this._updatePosition(style);
|
style = this._updatePosition(style);
|
||||||
style.display = "block";
|
style.display = "block";
|
||||||
|
|
||||||
const tooltipClasses = classNames("mx_RoomTooltip", this.props.tooltipClassName);
|
const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName);
|
||||||
|
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
<div className={tooltipClasses} style={style}>
|
<div className={tooltipClasses} style={style}>
|
||||||
<div className="mx_RoomTooltip_chevron" />
|
<div className="mx_Tooltip_chevron" />
|
||||||
{ this.props.label }
|
{ this.props.label }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -143,8 +143,8 @@ export default React.createClass({
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (this.props.collapsed && this.state.hover) {
|
if (this.props.collapsed && this.state.hover) {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={groupName} dir="auto" />;
|
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={groupName} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
|
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
|
||||||
|
|
|
@ -44,9 +44,9 @@ export default class MStickerBody extends MImageBody {
|
||||||
|
|
||||||
if (!content || !content.body || !content.info || !content.info.w) return null;
|
if (!content || !content.body || !content.info || !content.info.w) return null;
|
||||||
|
|
||||||
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||||
return <div style={{left: content.info.w + 'px'}} className="mx_MStickerBody_tooltip">
|
return <div style={{left: content.info.w + 'px'}} className="mx_MStickerBody_tooltip">
|
||||||
<RoomTooltip label={content.body} />
|
<Tooltip label={content.body} />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -364,8 +364,8 @@ module.exports = React.createClass({
|
||||||
label = <EmojiText element="div" title={name} className={nameClasses} dir="auto">{ name }</EmojiText>;
|
label = <EmojiText element="div" title={name} className={nameClasses} dir="auto">{ name }</EmojiText>;
|
||||||
}
|
}
|
||||||
} else if (this.state.hover) {
|
} else if (this.state.hover) {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
|
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
//var incomingCallBox;
|
//var incomingCallBox;
|
||||||
|
|
|
@ -32,6 +32,7 @@ import sessionStore from '../../../stores/SessionStore';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ChangePassword',
|
displayName: 'ChangePassword',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func,
|
||||||
onError: PropTypes.func,
|
onError: PropTypes.func,
|
||||||
|
@ -73,6 +74,9 @@ module.exports = React.createClass({
|
||||||
return {
|
return {
|
||||||
phase: this.Phases.Edit,
|
phase: this.Phases.Edit,
|
||||||
cachedPassword: null,
|
cachedPassword: null,
|
||||||
|
oldPassword: "",
|
||||||
|
newPassword: "",
|
||||||
|
newPasswordConfirm: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -165,6 +169,9 @@ module.exports = React.createClass({
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: this.Phases.Edit,
|
phase: this.Phases.Edit,
|
||||||
|
oldPassword: "",
|
||||||
|
newPassword: "",
|
||||||
|
newPasswordConfirm: "",
|
||||||
});
|
});
|
||||||
}).done();
|
}).done();
|
||||||
},
|
},
|
||||||
|
@ -192,11 +199,29 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onChangeOldPassword(ev) {
|
||||||
|
this.setState({
|
||||||
|
oldPassword: ev.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeNewPassword(ev) {
|
||||||
|
this.setState({
|
||||||
|
newPassword: ev.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeNewPasswordConfirm(ev) {
|
||||||
|
this.setState({
|
||||||
|
newPasswordConfirm: ev.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onClickChange: function(ev) {
|
onClickChange: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
|
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
|
||||||
const newPassword = this.refs.new_input.value;
|
const newPassword = this.state.newPassword;
|
||||||
const confirmPassword = this.refs.confirm_input.value;
|
const confirmPassword = this.state.newPasswordConfirm;
|
||||||
const err = this.props.onCheckPassword(
|
const err = this.props.onCheckPassword(
|
||||||
oldPassword, newPassword, confirmPassword,
|
oldPassword, newPassword, confirmPassword,
|
||||||
);
|
);
|
||||||
|
@ -217,7 +242,12 @@ module.exports = React.createClass({
|
||||||
if (!this.state.cachedPassword) {
|
if (!this.state.cachedPassword) {
|
||||||
currentPassword = (
|
currentPassword = (
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field id="passwordold" type="password" ref="old_input" label={_t('Current password')} />
|
<Field id="mx_ChangePassword_oldPassword"
|
||||||
|
type="password"
|
||||||
|
label={_t('Current password')}
|
||||||
|
value={this.state.oldPassword}
|
||||||
|
onChange={this.onChangeOldPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -230,11 +260,21 @@ module.exports = React.createClass({
|
||||||
<form className={this.props.className} onSubmit={this.onClickChange}>
|
<form className={this.props.className} onSubmit={this.onClickChange}>
|
||||||
{ currentPassword }
|
{ currentPassword }
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field id="password1" type="password" ref="new_input" label={passwordLabel}
|
<Field id="mx_ChangePassword_newPassword"
|
||||||
autoFocus={this.props.autoFocusNewPasswordInput} />
|
type="password"
|
||||||
|
label={passwordLabel}
|
||||||
|
value={this.state.newPassword}
|
||||||
|
autoFocus={this.props.autoFocusNewPasswordInput}
|
||||||
|
onChange={this.onChangeNewPassword}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field id="password2" type="password" ref="confirm_input" label={_t("Confirm password")} />
|
<Field id="mx_ChangePassword_newPasswordConfirm"
|
||||||
|
type="password"
|
||||||
|
label={_t("Confirm password")}
|
||||||
|
value={this.state.newPasswordConfirm}
|
||||||
|
onChange={this.onChangeNewPasswordConfirm}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className={buttonClassName} kind={this.props.buttonKind} onClick={this.onClickChange}>
|
<AccessibleButton className={buttonClassName} kind={this.props.buttonKind} onClick={this.onClickChange}>
|
||||||
{ _t('Change Password') }
|
{ _t('Change Password') }
|
||||||
|
|
|
@ -119,6 +119,7 @@ export default class EmailAddresses extends React.Component {
|
||||||
verifying: false,
|
verifying: false,
|
||||||
addTask: null,
|
addTask: null,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
|
newEmailAddress: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,14 +135,20 @@ export default class EmailAddresses extends React.Component {
|
||||||
this.setState({emails: this.state.emails.filter((e) => e !== address)});
|
this.setState({emails: this.state.emails.filter((e) => e !== address)});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onChangeNewEmailAddress = (e) => {
|
||||||
|
this.setState({
|
||||||
|
newEmailAddress: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
_onAddClick = (e) => {
|
_onAddClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.refs.newEmailAddress) return;
|
if (!this.state.newEmailAddress) return;
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const email = this.refs.newEmailAddress.value;
|
const email = this.state.newEmailAddress;
|
||||||
|
|
||||||
// TODO: Inline field validation
|
// TODO: Inline field validation
|
||||||
if (!Email.looksValid(email)) {
|
if (!Email.looksValid(email)) {
|
||||||
|
@ -173,14 +180,14 @@ export default class EmailAddresses extends React.Component {
|
||||||
|
|
||||||
this.setState({continueDisabled: true});
|
this.setState({continueDisabled: true});
|
||||||
this.state.addTask.checkEmailLinkClicked().then(() => {
|
this.state.addTask.checkEmailLinkClicked().then(() => {
|
||||||
const email = this.refs.newEmailAddress.value;
|
const email = this.state.newEmailAddress;
|
||||||
this.setState({
|
this.setState({
|
||||||
emails: [...this.state.emails, {address: email, medium: "email"}],
|
emails: [...this.state.emails, {address: email, medium: "email"}],
|
||||||
addTask: null,
|
addTask: null,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
verifying: false,
|
verifying: false,
|
||||||
|
newEmailAddress: "",
|
||||||
});
|
});
|
||||||
this.refs.newEmailAddress.value = "";
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.setState({continueDisabled: false});
|
this.setState({continueDisabled: false});
|
||||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||||
|
@ -221,8 +228,14 @@ export default class EmailAddresses extends React.Component {
|
||||||
{existingEmailElements}
|
{existingEmailElements}
|
||||||
<form onSubmit={this._onAddClick} autoComplete={false}
|
<form onSubmit={this._onAddClick} autoComplete={false}
|
||||||
noValidate={true} className="mx_EmailAddresses_new">
|
noValidate={true} className="mx_EmailAddresses_new">
|
||||||
<Field id="newEmailAddress" ref="newEmailAddress" label={_t("Email Address")}
|
<Field id="mx_EmailAddressses_newEmailAddress"
|
||||||
type="text" autoComplete="off" disabled={this.state.verifying} />
|
type="text"
|
||||||
|
label={_t("Email Address")}
|
||||||
|
autoComplete="off"
|
||||||
|
disabled={this.state.verifying}
|
||||||
|
value={this.state.newEmailAddress}
|
||||||
|
onChange={this._onChangeNewEmailAddress}
|
||||||
|
/>
|
||||||
{addButton}
|
{addButton}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -117,6 +117,8 @@ export default class PhoneNumbers extends React.Component {
|
||||||
addTask: null,
|
addTask: null,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
phoneCountry: "",
|
phoneCountry: "",
|
||||||
|
newPhoneNumber: "",
|
||||||
|
newPhoneNumberCode: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,14 +134,26 @@ export default class PhoneNumbers extends React.Component {
|
||||||
this.setState({msisdns: this.state.msisdns.filter((e) => e !== address)});
|
this.setState({msisdns: this.state.msisdns.filter((e) => e !== address)});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onChangeNewPhoneNumber = (e) => {
|
||||||
|
this.setState({
|
||||||
|
newPhoneNumber: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onChangeNewPhoneNumberCode = (e) => {
|
||||||
|
this.setState({
|
||||||
|
newPhoneNumberCode: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
_onAddClick = (e) => {
|
_onAddClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.refs.newPhoneNumber) return;
|
if (!this.state.newPhoneNumber) return;
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const phoneNumber = this.refs.newPhoneNumber.value;
|
const phoneNumber = this.state.newPhoneNumber;
|
||||||
const phoneCountry = this.state.phoneCountry;
|
const phoneCountry = this.state.phoneCountry;
|
||||||
|
|
||||||
const task = new AddThreepid();
|
const task = new AddThreepid();
|
||||||
|
@ -162,7 +176,7 @@ export default class PhoneNumbers extends React.Component {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.setState({continueDisabled: true});
|
this.setState({continueDisabled: true});
|
||||||
const token = this.refs.newPhoneNumberCode.value;
|
const token = this.state.newPhoneNumberCode;
|
||||||
this.state.addTask.haveMsisdnToken(token).then(() => {
|
this.state.addTask.haveMsisdnToken(token).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
msisdns: [...this.state.msisdns, {address: this.state.verifyMsisdn, medium: "msisdn"}],
|
msisdns: [...this.state.msisdns, {address: this.state.verifyMsisdn, medium: "msisdn"}],
|
||||||
|
@ -171,8 +185,9 @@ export default class PhoneNumbers extends React.Component {
|
||||||
verifying: false,
|
verifying: false,
|
||||||
verifyMsisdn: "",
|
verifyMsisdn: "",
|
||||||
verifyError: null,
|
verifyError: null,
|
||||||
|
newPhoneNumber: "",
|
||||||
|
newPhoneNumberCode: "",
|
||||||
});
|
});
|
||||||
this.refs.newPhoneNumber.value = "";
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.setState({continueDisabled: false});
|
this.setState({continueDisabled: false});
|
||||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||||
|
@ -205,22 +220,28 @@ export default class PhoneNumbers extends React.Component {
|
||||||
if (this.state.verifying) {
|
if (this.state.verifying) {
|
||||||
const msisdn = this.state.verifyMsisdn;
|
const msisdn = this.state.verifyMsisdn;
|
||||||
addVerifySection = (
|
addVerifySection = (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
{_t("A text message has been sent to +%(msisdn)s. " +
|
{_t("A text message has been sent to +%(msisdn)s. " +
|
||||||
"Please enter the verification code it contains", {msisdn: msisdn})}
|
"Please enter the verification code it contains", { msisdn: msisdn })}
|
||||||
<br />
|
<br />
|
||||||
{this.state.verifyError}
|
{this.state.verifyError}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this._onContinueClick} autoComplete={false} noValidate={true}>
|
<form onSubmit={this._onContinueClick} autoComplete={false} noValidate={true}>
|
||||||
<Field id="newPhoneNumberCode" ref="newPhoneNumberCode" label={_t("Verification code")}
|
<Field id="mx_PhoneNumbers_newPhoneNumberCode"
|
||||||
type="text" autoComplete="off" disabled={this.state.continueDisabled} />
|
type="text"
|
||||||
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
label={_t("Verification code")}
|
||||||
disabled={this.state.continueDisabled}>
|
autoComplete="off"
|
||||||
{_t("Continue")}
|
disabled={this.state.continueDisabled}
|
||||||
</AccessibleButton>
|
value={this.state.newPhoneNumberCode}
|
||||||
</form>
|
onChange={this._onChangeNewPhoneNumberCode}
|
||||||
</div>
|
/>
|
||||||
|
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
||||||
|
disabled={this.state.continueDisabled}>
|
||||||
|
{_t("Continue")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,9 +259,15 @@ export default class PhoneNumbers extends React.Component {
|
||||||
<form onSubmit={this._onAddClick} autoComplete={false}
|
<form onSubmit={this._onAddClick} autoComplete={false}
|
||||||
noValidate={true} className="mx_PhoneNumbers_new">
|
noValidate={true} className="mx_PhoneNumbers_new">
|
||||||
<div className="mx_PhoneNumbers_input">
|
<div className="mx_PhoneNumbers_input">
|
||||||
<Field id="newPhoneNumber" ref="newPhoneNumber" label={_t("Phone Number")}
|
<Field id="mx_PhoneNumbers_newPhoneNumber"
|
||||||
type="text" autoComplete="off" disabled={this.state.verifying}
|
type="text"
|
||||||
prefix={phoneCountry} />
|
label={_t("Phone Number")}
|
||||||
|
autoComplete="off"
|
||||||
|
disabled={this.state.verifying}
|
||||||
|
prefix={phoneCountry}
|
||||||
|
value={this.state.newPhoneNumber}
|
||||||
|
onChange={this._onChangeNewPhoneNumber}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{addVerifySection}
|
{addVerifySection}
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in a new issue