commit
						580b68a1b6
					
				|  | @ -65,6 +65,7 @@ | |||
|     "file-saver": "^1.3.3", | ||||
|     "filesize": "3.5.6", | ||||
|     "flux": "2.1.1", | ||||
|     "focus-trap-react": "^3.0.5", | ||||
|     "fuse.js": "^2.2.0", | ||||
|     "glob": "^5.0.14", | ||||
|     "highlight.js": "^8.9.1", | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/Modal.js
								
								
								
								
							
							
						
						
									
										13
									
								
								src/Modal.js
								
								
								
								
							|  | @ -22,6 +22,7 @@ const ReactDOM = require('react-dom'); | |||
| import PropTypes from 'prop-types'; | ||||
| import Analytics from './Analytics'; | ||||
| import sdk from './index'; | ||||
| import dis from './dispatcher'; | ||||
| 
 | ||||
| const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; | ||||
| 
 | ||||
|  | @ -188,10 +189,22 @@ class ModalManager { | |||
| 
 | ||||
|     _reRender() { | ||||
|         if (this._modals.length == 0) { | ||||
|             // If there is no modal to render, make all of Riot available
 | ||||
|             // to screen reader users again
 | ||||
|             dis.dispatch({ | ||||
|                 action: 'aria_unhide_main_app', | ||||
|             }); | ||||
|             ReactDOM.unmountComponentAtNode(this.getOrCreateContainer()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Hide the content outside the modal to screen reader users
 | ||||
|         // so they won't be able to navigate into it and act on it using
 | ||||
|         // screen reader specific features
 | ||||
|         dis.dispatch({ | ||||
|             action: 'aria_hide_main_app', | ||||
|         }); | ||||
| 
 | ||||
|         const modal = this._modals[0]; | ||||
|         const dialog = ( | ||||
|             <div className={"mx_Dialog_wrapper " + (modal.className ? modal.className : '')}> | ||||
|  |  | |||
|  | @ -374,7 +374,7 @@ const LoggedInView = React.createClass({ | |||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div className='mx_MatrixChat_wrapper'> | ||||
|             <div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers}> | ||||
|                 { topBar } | ||||
|                 <DragDropContext onDragEnd={this._onDragEnd}> | ||||
|                     <div className={bodyClasses}> | ||||
|  |  | |||
|  | @ -171,6 +171,10 @@ export default React.createClass({ | |||
|             register_hs_url: null, | ||||
|             register_is_url: null, | ||||
|             register_id_sid: null, | ||||
| 
 | ||||
|             // When showing Modal dialogs we need to set aria-hidden on the root app element
 | ||||
|             // and disable it when there are no dialogs
 | ||||
|             hideToSRUsers: false, | ||||
|         }; | ||||
|         return s; | ||||
|     }, | ||||
|  | @ -608,6 +612,16 @@ export default React.createClass({ | |||
|             case 'send_event': | ||||
|                 this.onSendEvent(payload.room_id, payload.event); | ||||
|                 break; | ||||
|             case 'aria_hide_main_app': | ||||
|                 this.setState({ | ||||
|                     hideToSRUsers: true, | ||||
|                 }); | ||||
|                 break; | ||||
|             case 'aria_unhide_main_app': | ||||
|                 this.setState({ | ||||
|                     hideToSRUsers: false, | ||||
|                 }); | ||||
|                 break; | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import FocusTrap from 'focus-trap-react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { MatrixClient } from 'matrix-js-sdk'; | ||||
|  | @ -37,9 +38,6 @@ export default React.createClass({ | |||
|         // onFinished callback to call when Escape is pressed
 | ||||
|         onFinished: PropTypes.func.isRequired, | ||||
| 
 | ||||
|         // callback to call when Enter is pressed
 | ||||
|         onEnterPressed: PropTypes.func, | ||||
| 
 | ||||
|         // called when a key is pressed
 | ||||
|         onKeyDown: PropTypes.func, | ||||
| 
 | ||||
|  | @ -52,6 +50,10 @@ export default React.createClass({ | |||
| 
 | ||||
|         // children should be the content of the dialog
 | ||||
|         children: PropTypes.node, | ||||
| 
 | ||||
|         // Id of content element
 | ||||
|         // If provided, this is used to add a aria-describedby attribute
 | ||||
|         contentId: React.PropTypes.string, | ||||
|     }, | ||||
| 
 | ||||
|     childContextTypes: { | ||||
|  | @ -76,12 +78,6 @@ export default React.createClass({ | |||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|             this.props.onFinished(); | ||||
|         } else if (e.keyCode === KeyCode.ENTER) { | ||||
|             if (this.props.onEnterPressed) { | ||||
|                 e.stopPropagation(); | ||||
|                 e.preventDefault(); | ||||
|                 this.props.onEnterPressed(e); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|  | @ -93,17 +89,28 @@ export default React.createClass({ | |||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
| 
 | ||||
|         return ( | ||||
|             <div onKeyDown={this._onKeyDown} className={this.props.className}> | ||||
|             <FocusTrap onKeyDown={this._onKeyDown} | ||||
|                 className={this.props.className} | ||||
|                 role="dialog" | ||||
|                 aria-labelledby='mx_BaseDialog_title' | ||||
|                 // This should point to a node describing the dialog.
 | ||||
|                 // If we were about to completelly follow this recommendation we'd need to
 | ||||
|                 // make all the components relying on BaseDialog to be aware of it.
 | ||||
|                 // So instead we will use the whole content as the description.
 | ||||
|                 // Description comes first and if the content contains more text,
 | ||||
|                 // AT users can skip its presentation.
 | ||||
|                 aria-describedby={this.props.contentId} | ||||
|             > | ||||
|                 <AccessibleButton onClick={this._onCancelClick} | ||||
|                     className="mx_Dialog_cancelButton" | ||||
|                 > | ||||
|                     <TintableSvg src="img/icons-close-button.svg" width="35" height="35" /> | ||||
|                 </AccessibleButton> | ||||
|                 <div className={'mx_Dialog_title ' + this.props.titleClass}> | ||||
|                 <div className={'mx_Dialog_title ' + this.props.titleClass} id='mx_BaseDialog_title'> | ||||
|                     { this.props.title } | ||||
|                 </div> | ||||
|                 { this.props.children } | ||||
|             </div> | ||||
|             </FocusTrap> | ||||
|         ); | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -129,7 +129,7 @@ export default class ChatCreateOrReuseDialog extends React.Component { | |||
|                 </div> | ||||
|                 <div className={labelClasses}><i>{ _t("Start new chat") }</i></div> | ||||
|             </AccessibleButton>; | ||||
|             content = <div className="mx_Dialog_content"> | ||||
|             content = <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                 { _t('You already have existing direct chats with this user:') } | ||||
|                 <div className="mx_ChatCreateOrReuseDialog_tiles"> | ||||
|                     { this.state.tiles } | ||||
|  | @ -147,7 +147,7 @@ export default class ChatCreateOrReuseDialog extends React.Component { | |||
|             if (this.state.busyProfile) { | ||||
|                 profile = <Spinner />; | ||||
|             } else if (this.state.profileError) { | ||||
|                 profile = <div className="error"> | ||||
|                 profile = <div className="error" role="alert"> | ||||
|                     Unable to load profile information for { this.props.userId } | ||||
|                 </div>; | ||||
|             } else { | ||||
|  | @ -163,14 +163,14 @@ export default class ChatCreateOrReuseDialog extends React.Component { | |||
|                 </div>; | ||||
|             } | ||||
|             content = <div> | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                 <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     <p> | ||||
|                         { _t('Click on the button below to start chatting!') } | ||||
|                     </p> | ||||
|                     { profile } | ||||
|                 </div> | ||||
|                 <DialogButtons primaryButton={_t('Start Chatting')} | ||||
|                     onPrimaryButtonClick={this.props.onNewDMClick} /> | ||||
|                     onPrimaryButtonClick={this.props.onNewDMClick} focus="true" /> | ||||
|             </div>; | ||||
|         } | ||||
| 
 | ||||
|  | @ -179,6 +179,7 @@ export default class ChatCreateOrReuseDialog extends React.Component { | |||
|             <BaseDialog className='mx_ChatCreateOrReuseDialog' | ||||
|                 onFinished={this.props.onFinished.bind(false)} | ||||
|                 title={title} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 { content } | ||||
|             </BaseDialog> | ||||
|  |  | |||
|  | @ -114,10 +114,10 @@ export default React.createClass({ | |||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={this.onOk} | ||||
|                 title={this.props.title} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                 <div id="mx_Dialog_content" className="mx_Dialog_content"> | ||||
|                     <div className="mx_ConfirmUserActionDialog_avatar"> | ||||
|                         { avatar } | ||||
|                     </div> | ||||
|  |  | |||
|  | @ -112,7 +112,7 @@ export default React.createClass({ | |||
|             // XXX: We should catch errcodes and give sensible i18ned messages for them,
 | ||||
|             // rather than displaying what the server gives us, but synapse doesn't give
 | ||||
|             // any yet.
 | ||||
|             createErrorNode = <div className="error"> | ||||
|             createErrorNode = <div className="error" role="alert"> | ||||
|                 <div>{ _t('Something went wrong whilst creating your community') }</div> | ||||
|                 <div>{ this.state.createError.message }</div> | ||||
|             </div>; | ||||
|  | @ -120,7 +120,6 @@ export default React.createClass({ | |||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={this._onFormSubmit} | ||||
|                 title={_t('Create Community')} | ||||
|             > | ||||
|                 <form onSubmit={this._onFormSubmit}> | ||||
|  |  | |||
|  | @ -45,30 +45,31 @@ export default React.createClass({ | |||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         return ( | ||||
|             <BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={this.onOk} | ||||
|                 title={_t('Create Room')} | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                     <div className="mx_CreateRoomDialog_label"> | ||||
|                         <label htmlFor="textinput"> { _t('Room name (optional)') } </label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <input id="textinput" ref="textinput" className="mx_CreateRoomDialog_input" autoFocus={true} size="64" /> | ||||
|                     </div> | ||||
|                     <br /> | ||||
| 
 | ||||
|                     <details className="mx_CreateRoomDialog_details"> | ||||
|                         <summary className="mx_CreateRoomDialog_details_summary">{ _t('Advanced options') }</summary> | ||||
|                         <div> | ||||
|                             <input type="checkbox" id="checkbox" ref="checkbox" defaultChecked={this.defaultNoFederate} /> | ||||
|                             <label htmlFor="checkbox"> | ||||
|                                 { _t('Block users on other matrix homeservers from joining this room') } | ||||
|                                 <br /> | ||||
|                                 ({ _t('This setting cannot be changed later!') }) | ||||
|                             </label> | ||||
|                 <form onSubmit={this.onOk}> | ||||
|                     <div className="mx_Dialog_content"> | ||||
|                         <div className="mx_CreateRoomDialog_label"> | ||||
|                             <label htmlFor="textinput"> { _t('Room name (optional)') } </label> | ||||
|                         </div> | ||||
|                     </details> | ||||
|                 </div> | ||||
|                         <div> | ||||
|                             <input id="textinput" ref="textinput" className="mx_CreateRoomDialog_input" autoFocus={true} size="64" /> | ||||
|                         </div> | ||||
|                         <br /> | ||||
| 
 | ||||
|                         <details className="mx_CreateRoomDialog_details"> | ||||
|                             <summary className="mx_CreateRoomDialog_details_summary">{ _t('Advanced options') }</summary> | ||||
|                             <div> | ||||
|                                 <input type="checkbox" id="checkbox" ref="checkbox" defaultChecked={this.defaultNoFederate} /> | ||||
|                                 <label htmlFor="checkbox"> | ||||
|                                 { _t('Block users on other matrix homeservers from joining this room') } | ||||
|                                     <br /> | ||||
|                                     ({ _t('This setting cannot be changed later!') }) | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </details> | ||||
|                     </div> | ||||
|                 </form> | ||||
|                 <DialogButtons primaryButton={_t('Create Room')} | ||||
|                     onPrimaryButtonClick={this.onOk} | ||||
|                     onCancel={this.onCancel} /> | ||||
|  |  | |||
|  | @ -52,22 +52,18 @@ export default React.createClass({ | |||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         if (this.props.focus) { | ||||
|             this.refs.button.focus(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         return ( | ||||
|             <BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished} | ||||
|                     title={this.props.title || _t('Error')}> | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                     title={this.props.title || _t('Error')} | ||||
|                     contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     { this.props.description || _t('An error has occurred.') } | ||||
|                 </div> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <button ref="button" className="mx_Dialog_primary" onClick={this.props.onFinished}> | ||||
|                     <button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={this.props.focus}> | ||||
|                         { this.props.button || _t('OK') } | ||||
|                     </button> | ||||
|                 </div> | ||||
|  |  | |||
|  | @ -73,11 +73,12 @@ export default React.createClass({ | |||
|         let content; | ||||
|         if (this.state.authError) { | ||||
|             content = ( | ||||
|                 <div> | ||||
|                     <div>{ this.state.authError.message || this.state.authError.toString() }</div> | ||||
|                 <div id='mx_Dialog_content'> | ||||
|                     <div role="alert">{ this.state.authError.message || this.state.authError.toString() }</div> | ||||
|                     <br /> | ||||
|                     <AccessibleButton onClick={this._onDismissClick} | ||||
|                         className="mx_UserSettings_button" | ||||
|                         autoFocus="true" | ||||
|                     > | ||||
|                         { _t("Dismiss") } | ||||
|                     </AccessibleButton> | ||||
|  | @ -85,7 +86,7 @@ export default React.createClass({ | |||
|             ); | ||||
|         } else { | ||||
|             content = ( | ||||
|                 <div> | ||||
|                 <div id='mx_Dialog_content'> | ||||
|                     <InteractiveAuth ref={this._collectInteractiveAuth} | ||||
|                         matrixClient={this.props.matrixClient} | ||||
|                         authData={this.props.authData} | ||||
|  | @ -100,6 +101,7 @@ export default React.createClass({ | |||
|             <BaseDialog className="mx_InteractiveAuthDialog" | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 { content } | ||||
|             </BaseDialog> | ||||
|  |  | |||
|  | @ -126,11 +126,11 @@ export default React.createClass({ | |||
|         text = _t(text, {displayName: displayName}); | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
|             <div id='mx_Dialog_content'> | ||||
|                 <p>{ text }</p> | ||||
| 
 | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <button onClick={this._onVerifyClicked}> | ||||
|                     <button onClick={this._onVerifyClicked} autoFocus="true"> | ||||
|                         { _t('Start verification') } | ||||
|                     </button> | ||||
|                     <button onClick={this._onShareClicked}> | ||||
|  | @ -154,7 +154,7 @@ export default React.createClass({ | |||
|             content = this._renderContent(); | ||||
|         } else { | ||||
|             content = ( | ||||
|                 <div> | ||||
|                 <div id='mx_Dialog_content'> | ||||
|                     <p>{ _t('Loading device info...') }</p> | ||||
|                     <Spinner /> | ||||
|                 </div> | ||||
|  | @ -165,6 +165,7 @@ export default React.createClass({ | |||
|             <BaseDialog className='mx_KeyShareRequestDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={_t('Encryption key request')} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 { content } | ||||
|             </BaseDialog> | ||||
|  |  | |||
|  | @ -60,10 +60,10 @@ export default React.createClass({ | |||
|         } | ||||
|         return ( | ||||
|             <BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={this.onOk} | ||||
|                 title={this.props.title} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                 <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     { this.props.description } | ||||
|                 </div> | ||||
|                 <DialogButtons primaryButton={this.props.button || _t('OK')} | ||||
|  |  | |||
|  | @ -30,6 +30,12 @@ export default React.createClass({ | |||
|         onFinished: PropTypes.func.isRequired, | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         if (this.refs.bugreportLink) { | ||||
|             this.refs.bugreportLink.focus(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     _sendBugReport: function() { | ||||
|         const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); | ||||
|         Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); | ||||
|  | @ -50,16 +56,20 @@ export default React.createClass({ | |||
|                 { _t( | ||||
|                     "Otherwise, <a>click here</a> to send a bug report.", | ||||
|                     {}, | ||||
|                     { 'a': (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{ sub }</a> }, | ||||
|                     { 'a': (sub) => <a ref="bugreportLink" onClick={this._sendBugReport} | ||||
|                     key="bugreport" href='#'>{ sub }</a> }, | ||||
|                 ) } | ||||
|                 </p> | ||||
|             ); | ||||
|         } | ||||
|         const shouldFocusContinueButton =!(bugreport==true); | ||||
| 
 | ||||
|         return ( | ||||
|             <BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished} | ||||
|                     title={_t('Unable to restore session')}> | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                     title={_t('Unable to restore session')} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     <p>{ _t("We encountered an error trying to restore your previous session. If " + | ||||
|                     "you continue, you will need to log in again, and encrypted chat " + | ||||
|                     "history will be unreadable.") }</p> | ||||
|  | @ -71,7 +81,7 @@ export default React.createClass({ | |||
|                     { bugreport } | ||||
|                 </div> | ||||
|                 <DialogButtons primaryButton={_t("Continue anyway")} | ||||
|                     onPrimaryButtonClick={this._continueClicked} | ||||
|                     onPrimaryButtonClick={this._continueClicked} focus={shouldFocusContinueButton} | ||||
|                     onCancel={this.props.onFinished} /> | ||||
|             </BaseDialog> | ||||
|         ); | ||||
|  |  | |||
|  | @ -41,9 +41,6 @@ export default React.createClass({ | |||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|     }, | ||||
| 
 | ||||
|     onEmailAddressChanged: function(value) { | ||||
|         this.setState({ | ||||
|             emailAddress: value, | ||||
|  | @ -131,6 +128,7 @@ export default React.createClass({ | |||
| 
 | ||||
|         const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText | ||||
|             className="mx_SetEmailDialog_email_input" | ||||
|             autoFocus="true" | ||||
|             placeholder={_t("Email address")} | ||||
|             placeholderClassName="mx_SetEmailDialog_email_input_placeholder" | ||||
|             blurToCancel={false} | ||||
|  | @ -140,9 +138,10 @@ export default React.createClass({ | |||
|             <BaseDialog className="mx_SetEmailDialog" | ||||
|                 onFinished={this.onCancelled} | ||||
|                 title={this.props.title} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                     <p> | ||||
|                     <p id='mx_Dialog_content'> | ||||
|                         { _t('This will allow you to reset your password and receive notifications.') } | ||||
|                     </p> | ||||
|                     { emailInput } | ||||
|  |  | |||
|  | @ -235,14 +235,14 @@ export default React.createClass({ | |||
|                 "error": Boolean(this.state.usernameError), | ||||
|                 "success": usernameAvailable, | ||||
|             }); | ||||
|             usernameIndicator = <div className={usernameIndicatorClasses}> | ||||
|             usernameIndicator = <div className={usernameIndicatorClasses} role="alert"> | ||||
|                 { usernameAvailable ? _t('Username available') : this.state.usernameError } | ||||
|             </div>; | ||||
|         } | ||||
| 
 | ||||
|         let authErrorIndicator = null; | ||||
|         if (this.state.authError) { | ||||
|             authErrorIndicator = <div className="error"> | ||||
|             authErrorIndicator = <div className="error" role="alert"> | ||||
|                 { this.state.authError } | ||||
|             </div>; | ||||
|         } | ||||
|  | @ -254,8 +254,9 @@ export default React.createClass({ | |||
|             <BaseDialog className="mx_SetMxIdDialog" | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={_t('To get started, please pick a username!')} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                 <div className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     <div className="mx_SetMxIdDialog_input_group"> | ||||
|                         <input type="text" ref="input_value" value={this.state.username} | ||||
|                             autoFocus={true} | ||||
|  |  | |||
|  | @ -61,17 +61,18 @@ export default React.createClass({ | |||
|         const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); | ||||
|         return ( | ||||
|             <BaseDialog className="mx_TextInputDialog" onFinished={this.props.onFinished} | ||||
|                 onEnterPressed={this.onOk} | ||||
|                 title={this.props.title} | ||||
|             > | ||||
|                 <div className="mx_Dialog_content"> | ||||
|                     <div className="mx_TextInputDialog_label"> | ||||
|                         <label htmlFor="textinput"> { this.props.description } </label> | ||||
|                 <form onSubmit={this.onOk}> | ||||
|                     <div className="mx_Dialog_content"> | ||||
|                         <div className="mx_TextInputDialog_label"> | ||||
|                             <label htmlFor="textinput"> { this.props.description } </label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" /> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 </form> | ||||
|                 <DialogButtons primaryButton={this.props.button} | ||||
|                     onPrimaryButtonClick={this.onOk} | ||||
|                     onCancel={this.onCancel} /> | ||||
|  |  | |||
|  | @ -189,8 +189,9 @@ export default React.createClass({ | |||
|             <BaseDialog className='mx_UnknownDeviceDialog' | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 title={_t('Room contains unknown devices')} | ||||
|                 contentId='mx_Dialog_content' | ||||
|             > | ||||
|                 <GeminiScrollbar autoshow={false} className="mx_Dialog_content"> | ||||
|                 <GeminiScrollbar autoshow={false} className="mx_Dialog_content" id='mx_Dialog_content'> | ||||
|                     <h4> | ||||
|                         { _t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name}) } | ||||
|                     </h4> | ||||
|  |  | |||
|  | @ -17,6 +17,8 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { KeyCode } from '../../../Keyboard'; | ||||
| 
 | ||||
| /** | ||||
|  * AccessibleButton is a generic wrapper for any element that should be treated | ||||
|  * as a button.  Identifies the element as a button, setting proper tab | ||||
|  | @ -28,8 +30,34 @@ import PropTypes from 'prop-types'; | |||
| export default function AccessibleButton(props) { | ||||
|     const {element, onClick, children, ...restProps} = props; | ||||
|     restProps.onClick = onClick; | ||||
|     // We need to consume enter onKeyDown and space onKeyUp
 | ||||
|     // otherwise we are risking also activating other keyboard focusable elements
 | ||||
|     // that might receive focus as a result of the AccessibleButtonClick action
 | ||||
|     // It's because we are using html buttons at a few places e.g. inside dialogs
 | ||||
|     // And divs which we report as role button to assistive technologies.
 | ||||
|     // Browsers handle space and enter keypresses differently and we are only adjusting to the
 | ||||
|     // inconsistencies here
 | ||||
|     restProps.onKeyDown = function(e) { | ||||
|         if (e.keyCode === KeyCode.ENTER) { | ||||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|             return onClick(e); | ||||
|         } | ||||
|         if (e.keyCode === KeyCode.SPACE) { | ||||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|         } | ||||
|     }; | ||||
|     restProps.onKeyUp = function(e) { | ||||
|         if (e.keyCode == 13 || e.keyCode == 32) return onClick(e); | ||||
|         if (e.keyCode === KeyCode.SPACE) { | ||||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|             return onClick(e); | ||||
|         } | ||||
|         if (e.keyCode === KeyCode.ENTER) { | ||||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|         } | ||||
|     }; | ||||
|     restProps.tabIndex = restProps.tabIndex || "0"; | ||||
|     restProps.role = "button"; | ||||
|  |  | |||
|  | @ -128,12 +128,22 @@ export const PasswordAuthEntry = React.createClass({ | |||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         let errorSection; | ||||
|         if (this.props.errorText) { | ||||
|             errorSection = ( | ||||
|                 <div className="error" role="alert"> | ||||
|                     { this.props.errorText } | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
|                 <p>{ _t("To continue, please enter your password.") }</p> | ||||
|                 <p>{ _t("Password:") }</p> | ||||
|                 <form onSubmit={this._onSubmit}> | ||||
|                     <label htmlFor="passwordField">{ _t("Password:") }</label> | ||||
|                     <input | ||||
|                         name="passwordField" | ||||
|                         ref="passwordField" | ||||
|                         className={passwordBoxClass} | ||||
|                         onChange={this._onPasswordFieldChange} | ||||
|  | @ -143,9 +153,7 @@ export const PasswordAuthEntry = React.createClass({ | |||
|                         { submitButtonOrSpinner } | ||||
|                     </div> | ||||
|                 </form> | ||||
|                 <div className="error"> | ||||
|                     { this.props.errorText } | ||||
|                 </div> | ||||
|             { errorSection } | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
|  | @ -180,14 +188,22 @@ export const RecaptchaAuthEntry = React.createClass({ | |||
| 
 | ||||
|         const CaptchaForm = sdk.getComponent("views.login.CaptchaForm"); | ||||
|         const sitePublicKey = this.props.stageParams.public_key; | ||||
| 
 | ||||
|         let errorSection; | ||||
|         if (this.props.errorText) { | ||||
|             errorSection = ( | ||||
|                 <div className="error" role="alert"> | ||||
|                     { this.props.errorText } | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div> | ||||
|                 <CaptchaForm sitePublicKey={sitePublicKey} | ||||
|                     onCaptchaResponse={this._onCaptchaResponse} | ||||
|                 /> | ||||
|                 <div className="error"> | ||||
|                     { this.props.errorText } | ||||
|                 </div> | ||||
|                 { errorSection } | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
|  | @ -372,6 +388,14 @@ export const MsisdnAuthEntry = React.createClass({ | |||
|                 mx_InteractiveAuthEntryComponents_msisdnSubmit: true, | ||||
|                 mx_UserSettings_button: true, // XXX button classes
 | ||||
|             }); | ||||
|             let errorSection; | ||||
|             if (this.state.errorText) { | ||||
|                 errorSection = ( | ||||
|                     <div className="error" role="alert"> | ||||
|                         { this.state.errorText } | ||||
|                     </div> | ||||
|                 ); | ||||
|             } | ||||
|             return ( | ||||
|                 <div> | ||||
|                     <p>{ _t("A text message has been sent to %(msisdn)s", | ||||
|  | @ -385,6 +409,7 @@ export const MsisdnAuthEntry = React.createClass({ | |||
|                                 className="mx_InteractiveAuthEntryComponents_msisdnEntry" | ||||
|                                 value={this.state.token} | ||||
|                                 onChange={this._onTokenChange} | ||||
|                                 aria-label={ _t("Code")} | ||||
|                             /> | ||||
|                             <br /> | ||||
|                             <input type="submit" value={_t("Submit")} | ||||
|  | @ -392,9 +417,7 @@ export const MsisdnAuthEntry = React.createClass({ | |||
|                                 disabled={!enableSubmit} | ||||
|                             /> | ||||
|                         </form> | ||||
|                         <div className="error"> | ||||
|                             { this.state.errorText } | ||||
|                         </div> | ||||
|                         {errorSection} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             ); | ||||
|  | @ -427,6 +450,12 @@ export const FallbackAuthEntry = React.createClass({ | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     focus: function() { | ||||
|         if (this.refs.fallbackButton) { | ||||
|             this.refs.fallbackButton.focus(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     _onShowFallbackClick: function() { | ||||
|         const url = this.props.matrixClient.getFallbackAuthUrl( | ||||
|             this.props.loginType, | ||||
|  | @ -445,12 +474,18 @@ export const FallbackAuthEntry = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         return ( | ||||
|             <div> | ||||
|                 <a onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a> | ||||
|                 <div className="error"> | ||||
|         let errorSection; | ||||
|         if (this.props.errorText) { | ||||
|             errorSection = ( | ||||
|                 <div className="error" role="alert"> | ||||
|                     { this.props.errorText } | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
|         return ( | ||||
|             <div> | ||||
|                 <a ref="fallbackButton" onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a> | ||||
|                 {errorSection} | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
|  |  | |||
|  | @ -531,6 +531,7 @@ | |||
|     "Token incorrect": "Token incorrect", | ||||
|     "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", | ||||
|     "Please enter the code it contains:": "Please enter the code it contains:", | ||||
|     "Code": "Code", | ||||
|     "Start authentication": "Start authentication", | ||||
|     "powered by Matrix": "powered by Matrix", | ||||
|     "Username on %(hs)s": "Username on %(hs)s", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker