diff --git a/css/base.css b/css/base.css index 21592095..de6fdaf5 100644 --- a/css/base.css +++ b/css/base.css @@ -207,6 +207,12 @@ textarea::placeholder { color: #bbb; } +input[type="text"].with-visibility, +input[type="password"].with-visibility { + margin-right:-2rem; + padding-right:2rem; +} + /* Scrollbar styling for Chrome. Firefox does not support scrollbar styling. */ ::-webkit-scrollbar { width: 0.6rem; @@ -300,6 +306,10 @@ li.right:hover .menuTrigger { color: #757575; } +.light-gray { + color: #bbb; +} + .small { font-size: 90%; } @@ -415,7 +425,7 @@ li.right:hover .menuTrigger { font-family: inherit; font-size: 1.1rem; height: 2.5rem; - line-height: 1.5; + line-height: 2.0; margin: 0.25rem 0.125rem; width: 100%; vertical-align: baseline; diff --git a/src/webapp.jsx b/src/webapp.jsx index 2c579ebc..cd4e25ea 100644 --- a/src/webapp.jsx +++ b/src/webapp.jsx @@ -743,9 +743,9 @@ class InPlaceEdit extends React.Component { super(props); this.state = { - active: props.state, - initialText: props.text || "", - text: props.text || "" + active: props.active, + initialValue: props.value || "", + value: props.value || "" }; this.handeTextChange = this.handeTextChange.bind(this); @@ -757,23 +757,23 @@ class InPlaceEdit extends React.Component { static getDerivedStateFromProps(nextProps, prevState) { // If text has changed while in read mode, update text and discard changes. // Ignore update if in edit mode. - if (prevState.initialText != nextProps.text && !prevState.active) { + if (prevState.initialValue != nextProps.value && !prevState.active) { return { - initialText: nextProps.text || "", - text: nextProps.text || "" + initialValue: nextProps.value || "", + value: nextProps.value || "" }; } return null; } handeTextChange(e) { - this.setState({text: e.target.value}); + this.setState({value: e.target.value}); } handleKeyDown(e) { if (e.keyCode === 27) { // Escape pressed - this.setState({text: this.props.text, active: false}); + this.setState({value: this.props.value, active: false}); } else if (e.keyCode === 13) { // Enter pressed this.handleEditingFinished(); @@ -789,33 +789,36 @@ class InPlaceEdit extends React.Component { handleEditingFinished() { this.setState({active: false}); - var text = this.state.text.trim(); - if ((text || this.props.text) && (text !== this.props.text)) { - this.props.onFinished(text); + let value = this.state.value.trim(); + if ((value || this.props.value) && (value !== this.props.value)) { + this.props.onFinished(value); } } render() { - var spanText = this.props.type === "password" ? - "••••••••" : this.state.text; - var spanClass = "in-place-edit" + - (this.props.readOnly ? " disabled" : ""); - if (!spanText) { - spanText = this.props.placeholder; - spanClass += " placeholder"; - } - if (spanText.length > 20) { - spanText = spanText.substring(0, 19) + "..."; + if (!this.state.active) { + var spanText = this.props.type == "password" ? "••••••••" : this.state.value; + var spanClass = "in-place-edit" + + (this.props.readOnly ? " disabled" : ""); + if (!spanText) { + spanText = this.props.placeholder; + spanClass += " placeholder"; + } + if (spanText.length > 20) { + spanText = spanText.substring(0, 19) + "..."; + } } return ( this.state.active ? + onBlur={this.handleEditingFinished} /> : {spanText} @@ -823,6 +826,73 @@ class InPlaceEdit extends React.Component { ); } }; + +class VisiblePassword extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + value: this.props.value, + visible: false + }; + + this.handleVisibility = this.handleVisibility.bind(this); + this.handeTextChange = this.handeTextChange.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleEditingFinished = this.handleEditingFinished.bind(this); + } + + handeTextChange(e) { + this.setState({value: e.target.value}); + if (this.props.onChange) { + this.props.onChange(e); + } + } + + handleVisibility(e) { + e.preventDefault(); + this.setState({visible: !this.state.visible}); + } + + handleKeyDown(e) { + if (e.keyCode === 27) { + // Escape pressed + this.setState({value: this.props.value, active: false}); + } else if (e.keyCode === 13) { + // Enter pressed + this.handleEditingFinished(); + } + } + + handleEditingFinished() { + if (this.props.onFinished) { + this.props.onFinished(this.state.value); + } + } + + render() { + return ( +
+ + + + {this.state.visible ? 'visibility' : 'visibility_off'} + + +
+ ); + } +} + /* END InPlaceEdit */ /* BEGIN Combobox for selecting host name */ @@ -1261,12 +1331,12 @@ class LoginView extends React.Component { value={this.state.login} onChange={this.handleLoginChange} required autoFocus /> - + required={true} />
@@ -1290,7 +1360,6 @@ class CreateAccountView extends React.PureComponent { this.state = { login: '', password: '', - password2: '', email: '', fn: '', // full/formatted name imageDataUrl: null, @@ -1300,7 +1369,6 @@ class CreateAccountView extends React.PureComponent { this.handleLoginChange = this.handleLoginChange.bind(this); this.handlePasswordChange = this.handlePasswordChange.bind(this); - this.handlePassword2Change = this.handlePassword2Change.bind(this); this.handleEmailChange = this.handleEmailChange.bind(this); this.handleFnChange = this.handleFnChange.bind(this); this.handleImageChanged = this.handleImageChanged.bind(this); @@ -1312,12 +1380,8 @@ class CreateAccountView extends React.PureComponent { this.setState({login: e.target.value}); } - handlePasswordChange(e) { - this.setState({password: e.target.value}); - } - - handlePassword2Change(e) { - this.setState({password2: e.target.value}); + handlePasswordChange(password) { + this.setState({password: password}); } handleEmailChange(e) { @@ -1339,17 +1403,13 @@ class CreateAccountView extends React.PureComponent { handleSubmit(e) { e.preventDefault(); - if (this.state.password.trim() != this.state.password2.trim()) { - this.props.onError("Passwords don't match", "warn"); - } else { - // TODO: check email for validity - this.setState({errorCleared: false}); - this.props.onCreateAccount( - this.state.login.trim(), - this.state.password.trim(), - vcard(this.state.fn, this.state.imageDataUrl), - {"meth": "email", "val": this.state.email}); - } + // TODO: check email for validity + this.setState({errorCleared: false}); + this.props.onCreateAccount( + this.state.login.trim(), + this.state.password.trim(), + vcard(this.state.fn, this.state.imageDataUrl), + {"meth": "email", "val": this.state.email}); } render() { @@ -1363,23 +1423,22 @@ class CreateAccountView extends React.PureComponent {
- - +
-
- -
+
+ +
@@ -1467,6 +1526,7 @@ class PasswordResetView extends React.PureComponent { this.state = { email: '', + password: '' }; this.handleSubmit = this.handleSubmit.bind(this); @@ -1475,32 +1535,50 @@ class PasswordResetView extends React.PureComponent { handleSubmit(e) { e.preventDefault(); - this.props.onSubmit("email", this.state.email.trim()); + if (this.props.token) { + this.props.onSubmit(null, this.state.password.trim()); + } else { + this.props.onSubmit("email", this.state.email.trim()); + } } handleEmailChange(e) { this.setState({email: e.target.value}); } + handlePasswordChange(e) { + this.setState({password: e.target.value}); + } + render() { + let buttonText = this.props.token ? "Reset" : "Send request"; return (
-

Email with instructions for resetting your password will be sent - to the following email address:

- + {!this.props.token ? + + + + + : + + }
- +
); } } - /* END PasswordResetView */ @@ -1891,7 +1969,7 @@ class EditAccountView extends React.Component {
{ - this.handleError(err.message, "err"); - }); - } else { - this.tinode.connect() - .then(() => { - this.tinode.resetAuthSecret("basic", method, value) - }) - .catch((err) => { - // Socket error - this.handleError(err.message, "err"); - }); - } + // If already connected, connnect() will return a resolved promise. + this.tinode.connect() + .then(() => { + this.tinode.requestResetAuthSecret("basic", method, value) + }) + .catch((err) => { + // Socket error + this.handleError(err.message, "err"); + }); } render() {