summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Fondrie-Teitler <simonft@riseup.net>2017-04-26 20:28:27 -0400
committerKali Kaneko (leap communications) <kali@leap.se>2017-05-04 16:27:23 +0200
commitaa8cf504f876182048e1f0e5b72b234b10a7f472 (patch)
tree2fc444b7b3c622621bb9f594e7b664eb5293d136
parenta6437a3f30a8690b5cb0cf9bad2d0e5253743039 (diff)
[feat] Go to first open line when enter is pressed
When filling out the signup or login pages, and when adding an additional service provider, the enter key will trigger the submit action. When on the choose provider page, enter will take you to the next page. - Resolves: #8841
-rw-r--r--ui/app/components/list_editor/index.js24
-rw-r--r--ui/app/components/login.js145
-rw-r--r--ui/app/components/main_panel/user_password_form.js54
-rw-r--r--ui/app/components/password_field.js28
-rw-r--r--ui/app/components/wizard/provider_select_stage.js32
5 files changed, 189 insertions, 94 deletions
diff --git a/ui/app/components/list_editor/index.js b/ui/app/components/list_editor/index.js
index 58b6ec44..4fff1cab 100644
--- a/ui/app/components/list_editor/index.js
+++ b/ui/app/components/list_editor/index.js
@@ -20,7 +20,8 @@ class ListEdit extends React.Component {
selected: null, // string of the selected item
onRemove: null,
onAdd: null,
- onSelect: null
+ onSelect: null,
+ onKeyDown: null
}}
constructor(props) {
@@ -64,6 +65,18 @@ class ListEdit extends React.Component {
}
}
+ componentDidMount() {
+ if (this.props.onKeyDown) {
+ this.listEditorDiv.addEventListener("keydown", this.props.onKeyDown)
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.props.onKeyDown) {
+ this.listEditorDiv.removeEventListener("keydown", this.props.onKeyDown)
+ }
+ }
+
render() {
let options = null
if (this.props.items) {
@@ -72,11 +85,16 @@ class ListEdit extends React.Component {
}, this)
}
return(
- <div className="list-editor">
+ <div
+ ref={(div) => { this.listEditorDiv = div; }}
+ className="list-editor" >
<FormControl
value={this.row(this.props.selected)}
className="list-select"
- componentClass="select" size="5" onChange={this.click}>
+ componentClass="select"
+ size="5"
+ onChange={this.click}
+ >
{options}
</FormControl>
<ButtonToolbar className="pull-right list-toolbar">
diff --git a/ui/app/components/login.js b/ui/app/components/login.js
index 0a47d58c..2c96e0d5 100644
--- a/ui/app/components/login.js
+++ b/ui/app/components/login.js
@@ -1,5 +1,4 @@
import React from 'react'
-import ReactDOM from 'react-dom'
import { FormGroup, ControlLabel, FormControl, HelpBlock, Button,
Checkbox, Glyphicon, Overlay, Tooltip, Alert } from 'react-bootstrap'
@@ -59,6 +58,7 @@ class Login extends React.Component {
this.onPassword2 = this.onPassword2.bind(this)
this.onInvite = this.onInvite.bind(this)
this.onSubmit = this.onSubmit.bind(this)
+ this.onKeyPress = this.onKeyPress.bind(this)
this.onRemember = this.onRemember.bind(this)
}
@@ -73,6 +73,19 @@ class Login extends React.Component {
}
}
+ componentDidUpdate(prevProps, prevState) {
+ // If the user changes anything in the domain (which follows the @ sign)
+ // it gets replaced reverted to the domain name. This moves the cursor to
+ // before the @ sign when that happens
+ if (this.props.domain && this.state.username){
+ let textarea = this.usernameref
+ let start = this.state.username.indexOf('@')
+ if (textarea.selectionStart > start) {
+ textarea.setSelectionRange(start, start)
+ }
+ }
+ }
+
render () {
let rememberCheck = ""
let submitButton = ""
@@ -147,10 +160,11 @@ class Login extends React.Component {
<FormGroup controlId="loginPassword2" validationState={this.state.password2State}>
<ControlLabel>Repeat Password</ControlLabel>
<FormControl
- type="password"
- ref="password"
- value={this.state.password2 || ""}
- onChange={this.onPassword2} />
+ type="password"
+ inputRef={ref => this.password2ref = ref}
+ value={this.state.password2 || ""}
+ onChange={this.onPassword2}
+ />
{this.state.password2State == 'success' ? null : <FormControl.Feedback/>}
{password2Help}
</FormGroup>
@@ -161,8 +175,10 @@ class Login extends React.Component {
<FormGroup controlId="invite" validationState={this.state.inviteState}>
<ControlLabel>Invite Code</ControlLabel>
<FormControl
- value={this.state.invite || ""}
- onChange={this.onInvite} />
+ value={this.state.invite || ""}
+ onChange={this.onInvite}
+ inputRef={ref => this.inviteref = ref}
+ />
{inviteHelp}
</FormGroup>
)
@@ -175,64 +191,54 @@ class Login extends React.Component {
disabled: !this.maySubmit()
}
if (this.state.loading) {
- submitButton = <Button block {...buttonProps}><Spinner /></Button>
+ submitButton = <Button block {...buttonProps}><Spinner /></Button>
} else {
- submitButton = <Button block {...buttonProps}>{buttonText}</Button>
+ submitButton = <Button block {...buttonProps}>{buttonText}</Button>
}
-
- let usernameref = null
let usernameDisabled = false
let usernameValue = this.state.username || ""
if (this.props.address) {
usernameDisabled = true
usernameValue = this.props.address
- } else if (this.props.domain) {
- usernameref = function(c) {
- if (c != null) {
- let textarea = ReactDOM.findDOMNode(c)
- let start = textarea.value.indexOf('@')
- if (textarea.selectionStart > start) {
- textarea.setSelectionRange(start, start)
- }
- }
- }
}
+ const form = (
+ <form onSubmit={this.onSubmit} onKeyPress={this.onKeyPress}>
+ {message}
+ <FormGroup style={{marginBottom: '10px' }} controlId="loginUsername" validationState={this.state.usernameState}>
+ <ControlLabel>Username</ControlLabel>
+ <FormControl
+ componentClass="textarea"
+ style={{resize: "none"}}
+ rows="1"
+ inputRef={(ref) => this.usernameref = ref}
+ autoFocus
+ value={usernameValue}
+ disabled={usernameDisabled}
+ onChange={this.onUsernameChange}
+ onBlur={this.onUsernameBlur}
+ />
+ {this.state.usernameState == 'success' ? null : <FormControl.Feedback/>}
+ {usernameHelp}
+ </FormGroup>
- let form = <form onSubmit={this.onSubmit}>
- {message}
- <FormGroup style={{marginBottom: '10px' }} controlId="loginUsername" validationState={this.state.usernameState}>
- <ControlLabel>Username</ControlLabel>
- <FormControl
- componentClass="textarea"
- style={{resize: "none"}}
- rows="1"
- ref={usernameref}
- autoFocus
- value={usernameValue}
- disabled={usernameDisabled}
- onChange={this.onUsernameChange}
- onBlur={this.onUsernameBlur} />
- {this.state.usernameState == 'success' ? null : <FormControl.Feedback/>}
- {usernameHelp}
- </FormGroup>
-
- <FormGroup controlId="loginPassword" validationState={this.state.passwordState}>
- <ControlLabel>Password</ControlLabel>
- <FormControl
- type="password"
- ref="password"
- value={this.state.password || ""}
- onChange={this.onPassword} />
- {this.state.passwordState == 'success' ? null : <FormControl.Feedback/>}
- {passwordHelp}
- </FormGroup>
-
- {password2Elem}
- {inviteElem}
- {submitButton}
- {rememberCheck}
- </form>
+ <FormGroup controlId="loginPassword" validationState={this.state.passwordState}>
+ <ControlLabel>Password</ControlLabel>
+ <FormControl
+ type="password"
+ inputRef={ref => this.passwordref = ref}
+ value={this.state.password || ""}
+ onChange={this.onPassword}
+ />
+ {this.state.passwordState == 'success' ? null : <FormControl.Feedback/>}
+ {passwordHelp}
+ </FormGroup>
+ {password2Elem}
+ {inviteElem}
+ {submitButton}
+ {rememberCheck}
+ </form>
+ )
return form
}
@@ -390,6 +396,33 @@ class Login extends React.Component {
}
}
+ /**
+ * Handle key presses
+ */
+ onKeyPress(e) {
+ // On "Enter", move the focus to the first mounted but unfilled field,
+ // or submit the form if there are none.
+ if (e.key === 'Enter') {
+ // Ignore the @ and domain when checking the username
+ if (this.usernameref.value.split('@')[0] === ''){
+ this.usernameref.focus()
+ return
+ }
+
+ const firstUnfilledField = [
+ this.passwordref,
+ this.password2ref,
+ this.inviteref,
+ ].find(ref => ref != null && ref.value == "")
+
+ if (firstUnfilledField) {
+ firstUnfilledField.focus()
+ } else {
+ this.onSubmit(e);
+ }
+ }
+ }
+
doLogin() {
let account = Account.findOrAdd(this.state.username)
account.login(this.state.password, this.props.autoAllowed).then(
@@ -451,4 +484,4 @@ class Login extends React.Component {
}
-export default Login \ No newline at end of file
+export default Login
diff --git a/ui/app/components/main_panel/user_password_form.js b/ui/app/components/main_panel/user_password_form.js
index 5e26b198..33640f0c 100644
--- a/ui/app/components/main_panel/user_password_form.js
+++ b/ui/app/components/main_panel/user_password_form.js
@@ -26,6 +26,7 @@ export default class UserPasswordForm extends React.Component {
repeatPassword: null
}
this.submit = this.submit.bind(this)
+ this.onKeyPress = this.onKeyPress.bind(this)
this.setNew = this.setNew.bind(this)
this.setCurrent = this.setCurrent.bind(this)
this.setRepeat = this.setRepeat.bind(this)
@@ -79,6 +80,22 @@ export default class UserPasswordForm extends React.Component {
)
}
+ onKeyPress(e) {
+ if (e.key === 'Enter') {
+ const firstUnfilledField = [
+ this.currentPasswordRef,
+ this.newPasswordRef,
+ this.repeatPasswordRef,
+ ].find(ref => ref != null && ref.value == "")
+
+ if (firstUnfilledField) {
+ firstUnfilledField.focus()
+ } else {
+ this.submit(e);
+ }
+ }
+ }
+
render () {
let submitButton = null
let message = null
@@ -100,21 +117,30 @@ export default class UserPasswordForm extends React.Component {
submitButton = <Button block disabled={!this.maySubmit()} onClick={this.submit}>Change</Button>
}
return (
- <form onSubmit={this.submit}>
+ <form onSubmit={this.submit} onKeyPress={this.onKeyPress}>
{message}
- <PasswordField id={this.props.account.id + "-current-password"}
- label="Current Password"
- validationMode="none"
- onChange={this.setCurrent} />
- <PasswordField id={this.props.account.id + "-new-password"}
- label="New Password"
- validationMode="crack"
- onChange={this.setNew} />
- <PasswordField id={this.props.account.id + "-repeat-password"}
- label="Repeat Password"
- validationMode="match"
- matchText={this.state.newPassword}
- onChange={this.setRepeat} />
+ <PasswordField
+ id={this.props.account.id + "-current-password"}
+ inputRef={ref => this.currentPasswordRef = ref}
+ label="Current Password"
+ validationMode="none"
+ onChange={this.setCurrent}
+ />
+ <PasswordField
+ id={this.props.account.id + "-new-password"}
+ inputRef={ref => this.newPasswordRef = ref}
+ label="New Password"
+ validationMode="crack"
+ onChange={this.setNew}
+ />
+ <PasswordField
+ id={this.props.account.id + "-repeat-password"}
+ inputRef={ref => this.repeatPasswordRef = ref}
+ label="Repeat Password"
+ validationMode="match"
+ matchText={this.state.newPassword}
+ onChange={this.setRepeat}
+ />
{submitButton}
</form>
)
diff --git a/ui/app/components/password_field.js b/ui/app/components/password_field.js
index 8397967f..654b8a57 100644
--- a/ui/app/components/password_field.js
+++ b/ui/app/components/password_field.js
@@ -8,13 +8,16 @@ import Validate from 'lib/validate'
export default class PasswordField extends React.Component {
- static get defaultProps() {return{
- id: null, // required. controlId of the element
- label: "Password",
- onChange: null, // callback passed current password
- validationMode: "crack", // one of 'none', 'match', 'crack'
- matchText: null, // used if validationMode == 'match'
- }}
+ static get defaultProps() {
+ return {
+ id: null, // required. controlId of the element
+ label: "Password",
+ onChange: null, // callback passed current password
+ validationMode: "crack", // one of 'none', 'match', 'crack'
+ matchText: null, // used if validationMode == 'match'
+ inputRef: null, // a ref to the input. Used to set focus
+ }
+ }
constructor(props) {
super(props)
@@ -43,14 +46,15 @@ export default class PasswordField extends React.Component {
<FormGroup controlId={this.props.id} validationState={this.state.passwordState}>
<ControlLabel>{this.props.label}</ControlLabel>
<FormControl
- type="password"
- ref="password"
- value={this.state.password || ""}
- onChange={this.keypress} />
+ type="password"
+ inputRef={this.props.inputRef}
+ value={this.state.password || ""}
+ onChange={this.keypress}
+ />
{this.state.passwordState == 'success' ? null : <FormControl.Feedback/>}
{passwordHelp}
</FormGroup>
- )
+ )
}
keypress(e) {
diff --git a/ui/app/components/wizard/provider_select_stage.js b/ui/app/components/wizard/provider_select_stage.js
index 2a342d9b..b19fc8ac 100644
--- a/ui/app/components/wizard/provider_select_stage.js
+++ b/ui/app/components/wizard/provider_select_stage.js
@@ -33,12 +33,13 @@ export default class ProviderSelectStage extends React.Component {
provider: null, // Provider object, if selected
error: null // error message
}
- this.add = this.add.bind(this)
- this.remove = this.remove.bind(this)
- this.select = this.select.bind(this)
- this.close = this.close.bind(this)
- this.cancel = this.cancel.bind(this)
- this.next = this.next.bind(this)
+ this.add = this.add.bind(this)
+ this.remove = this.remove.bind(this)
+ this.select = this.select.bind(this)
+ this.close = this.close.bind(this)
+ this.cancel = this.cancel.bind(this)
+ this.next = this.next.bind(this)
+ this.onKeyDown = this.onKeyDown.bind(this)
}
componentWillMount() {
@@ -134,6 +135,13 @@ export default class ProviderSelectStage extends React.Component {
})
}
+ onKeyDown(e) {
+ // Check for enter key
+ if (e.keyCode === 13) {
+ this.next();
+ }
+ }
+
render() {
let modal = null
let info = null
@@ -171,9 +179,15 @@ export default class ProviderSelectStage extends React.Component {
</ButtonToolbar>
</div>
)
- let editlist = <ListEditor ref="list" items={this.state.domains}
- selected={this.state.selected} onRemove={this.remove} onAdd={this.add}
- onSelect={this.select} />
+ let editlist = <ListEditor
+ ref="list"
+ items={this.state.domains}
+ selected={this.state.selected}
+ onRemove={this.remove}
+ onAdd={this.add}
+ onSelect={this.select}
+ onKeyDown={this.onKeyDown}
+ />
return(
<StageLayout title={this.props.title} subtitle={this.props.subtitle} buttons={buttons}>
<HorizontalLayout equalWidths={true}>