Add eslint-plugin-jsx-a11y (#1651)
* Add eslint-plugin-jsx-a11y. * Fix npm script. * Adjust npm scripts so test also runs lint. * Fix existing lint errors. * Don't break on a11y issues. * Add role and tabIndex. * Add vim and Mac files to .gitignore and .dockerignore. * Handle htmlFor (partially), a that's actually a button. * Fix missing tabIndex. * Add cursor:pointer to load-more * Revert change to load_more. * Fixes based on review. * Update yarn.lock. * Don't try to install fsevents on Linux (hides warning noise).
This commit is contained in:
		| @@ -6,3 +6,6 @@ node_modules | ||||
| storybook | ||||
| neo4j | ||||
| vendor/bundle | ||||
| .DS_Store | ||||
| *.swp | ||||
| *~ | ||||
|   | ||||
							
								
								
									
										34
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								.eslintrc
									
									
									
									
									
								
							| @@ -8,7 +8,8 @@ | ||||
|   "parser": "babel-eslint", | ||||
|  | ||||
|   "plugins": [ | ||||
|     "react" | ||||
|     "react", | ||||
|     "jsx-a11y" | ||||
|   ], | ||||
|  | ||||
|   "parserOptions": { | ||||
| @@ -43,9 +44,36 @@ | ||||
|     "no-mixed-spaces-and-tabs": 1, | ||||
|     "no-nested-ternary": 1, | ||||
|     "no-trailing-spaces": 1, | ||||
|     "react/wrap-multilines": 2, | ||||
|  | ||||
|     "react/jsx-wrap-multilines": 2, | ||||
|     "react/self-closing-comp": 2, | ||||
|     "react/prop-types": 2, | ||||
|     "react/no-multi-comp": 0 | ||||
|     "react/no-multi-comp": 0, | ||||
|  | ||||
|     "jsx-a11y/accessible-emoji": 1, | ||||
|     "jsx-a11y/anchor-has-content": 1, | ||||
|     "jsx-a11y/aria-activedescendant-has-tabindex": 1, | ||||
|     "jsx-a11y/aria-props": 1, | ||||
|     "jsx-a11y/aria-proptypes": 1, | ||||
|     "jsx-a11y/aria-role": 1, | ||||
|     "jsx-a11y/aria-unsupported-elements": 1, | ||||
|     "jsx-a11y/heading-has-content": 1, | ||||
|     "jsx-a11y/href-no-hash": 1, | ||||
|     "jsx-a11y/html-has-lang": 1, | ||||
|     "jsx-a11y/iframe-has-title": 1, | ||||
|     "jsx-a11y/img-has-alt": 1, | ||||
|     "jsx-a11y/img-redundant-alt": 1, | ||||
|     "jsx-a11y/label-has-for": 1, | ||||
|     "jsx-a11y/mouse-events-have-key-events": 1, | ||||
|     "jsx-a11y/no-access-key": 1, | ||||
|     "jsx-a11y/no-distracting-elements": 1, | ||||
|     "jsx-a11y/no-onchange": 1, | ||||
|     "jsx-a11y/no-redundant-roles": 1, | ||||
|     "jsx-a11y/onclick-has-focus": 1, | ||||
|     "jsx-a11y/onclick-has-role": 1, | ||||
|     "jsx-a11y/role-has-required-aria-props": 1, | ||||
|     "jsx-a11y/role-supports-aria-props": 1, | ||||
|     "jsx-a11y/scope": 1, | ||||
|     "jsx-a11y/tabindex-no-positive": 1 | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -29,10 +29,16 @@ neo4j/ | ||||
| # Ignore Capistrano customizations | ||||
| config/deploy/* | ||||
|  | ||||
|  | ||||
| # Ignore IDE files | ||||
| .vscode/ | ||||
|  | ||||
| # Ignore postgres + redis volume optionally created by docker-compose | ||||
| postgres | ||||
| redis | ||||
|  | ||||
| # Ignore Apple files | ||||
| .DS_Store | ||||
|  | ||||
| # Ignore vim files | ||||
| *~ | ||||
| *.swp | ||||
|   | ||||
| @@ -28,7 +28,7 @@ RUN BUILD_DEPS=" \ | ||||
|     imagemagick \ | ||||
|  && npm install -g npm@3 && npm install -g yarn \ | ||||
|  && bundle install --deployment --without test development \ | ||||
|  && yarn \ | ||||
|  && yarn --ignore-optional \ | ||||
|  && yarn cache clean \ | ||||
|  && npm -g cache clean \ | ||||
|  && apk del $BUILD_DEPS \ | ||||
|   | ||||
| @@ -178,7 +178,12 @@ const AutosuggestTextarea = React.createClass({ | ||||
|  | ||||
|         <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'> | ||||
|           {suggestions.map((suggestion, i) => ( | ||||
|             <div key={suggestion} className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`} onClick={this.onSuggestionClick.bind(this, suggestion)}> | ||||
|             <div | ||||
|               role='button' | ||||
|               tabIndex='0' | ||||
|               key={suggestion} | ||||
|               className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`} | ||||
|               onClick={this.onSuggestionClick.bind(this, suggestion)}> | ||||
|               <AutosuggestAccountContainer id={suggestion} /> | ||||
|             </div> | ||||
|           ))} | ||||
|   | ||||
| @@ -9,6 +9,7 @@ const Button = React.createClass({ | ||||
|     block: React.PropTypes.bool, | ||||
|     secondary: React.PropTypes.bool, | ||||
|     size: React.PropTypes.number, | ||||
|     style: React.PropTypes.object, | ||||
|     children: React.PropTypes.node | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -15,13 +15,13 @@ const ColumnBackButton = React.createClass({ | ||||
|   mixins: [PureRenderMixin], | ||||
|  | ||||
|   handleClick () { | ||||
|     if (window.history && window.history.length == 1) this.context.router.push("/"); | ||||
|     if (window.history && window.history.length === 1) this.context.router.push("/"); | ||||
|     else this.context.router.goBack(); | ||||
|   }, | ||||
|  | ||||
|   render () { | ||||
|     return ( | ||||
|       <div onClick={this.handleClick} className='column-back-button'> | ||||
|       <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'> | ||||
|         <i className='fa fa-fw fa-chevron-left' style={iconStyle} /> | ||||
|         <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> | ||||
|       </div> | ||||
|   | ||||
| @@ -31,7 +31,7 @@ const ColumnBackButtonSlim = React.createClass({ | ||||
|   render () { | ||||
|     return ( | ||||
|       <div style={{ position: 'relative' }}> | ||||
|         <div style={outerStyle} onClick={this.handleClick} className='column-back-button'> | ||||
|         <div role='button' tabIndex='0' style={outerStyle} onClick={this.handleClick} className='column-back-button'> | ||||
|           <i className='fa fa-fw fa-chevron-left' style={iconStyle} /> | ||||
|           <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> | ||||
|         </div> | ||||
|   | ||||
| @@ -46,7 +46,9 @@ const ColumnCollapsable = React.createClass({ | ||||
|  | ||||
|     return ( | ||||
|       <div style={{ position: 'relative' }}> | ||||
|         <div title={`${title}`} style={{...iconStyle }} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div> | ||||
|         <div role='button' tabIndex='0' title={`${title}`} style={{...iconStyle }} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}> | ||||
|           <i className={`fa fa-${icon}`} /> | ||||
|         </div> | ||||
|  | ||||
|         <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}> | ||||
|           {({ opacity, height }) => | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
|  | ||||
| const LoadMore = ({ onClick }) => ( | ||||
|   <a href='#' className='load-more' onClick={onClick}> | ||||
|   <a href="#" className='load-more' role='button' onClick={onClick}> | ||||
|     <FormattedMessage id='status.load_more' defaultMessage='Load more' /> | ||||
|   </a> | ||||
| ); | ||||
|   | ||||
| @@ -220,7 +220,7 @@ const MediaGallery = React.createClass({ | ||||
|       } | ||||
|  | ||||
|       children = ( | ||||
|         <div style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}> | ||||
|         <div role='button' tabIndex='0' style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}> | ||||
|           <span style={spoilerSpanStyle}>{warning}</span> | ||||
|           <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> | ||||
|         </div> | ||||
|   | ||||
| @@ -6,7 +6,8 @@ const Permalink = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     href: React.PropTypes.string.isRequired, | ||||
|     to: React.PropTypes.string.isRequired | ||||
|     to: React.PropTypes.string.isRequired, | ||||
|     children: React.PropTypes.node.isRequired | ||||
|   }, | ||||
|  | ||||
|   handleClick (e) { | ||||
|   | ||||
| @@ -119,7 +119,7 @@ const StatusContent = React.createClass({ | ||||
|       return ( | ||||
|         <div className='status__content' style={{ cursor: 'pointer' }} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> | ||||
|           <p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} > | ||||
|             <span dangerouslySetInnerHTML={spoilerContent} />  <a className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>{toggleText}</a> | ||||
|             <span dangerouslySetInnerHTML={spoilerContent} />  <a tabIndex='0' className='status__content__spoiler-link' role='button' onClick={this.handleSpoilerClick}>{toggleText}</a> | ||||
|           </p> | ||||
|  | ||||
|           {mentionsPlaceholder} | ||||
|   | ||||
| @@ -194,7 +194,7 @@ const VideoPlayer = React.createClass({ | ||||
|     if (!this.state.visible) { | ||||
|       if (sensitive) { | ||||
|         return ( | ||||
|           <div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> | ||||
|           <div role='button' tabIndex='0' style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> | ||||
|             {spoilerButton} | ||||
|             <span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> | ||||
|             <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> | ||||
| @@ -202,7 +202,7 @@ const VideoPlayer = React.createClass({ | ||||
|         ); | ||||
|       } else { | ||||
|         return ( | ||||
|           <div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> | ||||
|           <div role='button' tabIndex='0' style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> | ||||
|             {spoilerButton} | ||||
|             <span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> | ||||
|             <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> | ||||
| @@ -213,7 +213,7 @@ const VideoPlayer = React.createClass({ | ||||
|  | ||||
|     if (this.state.preview && !autoplay) { | ||||
|       return ( | ||||
|         <div style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}> | ||||
|         <div role='button' tabIndex='0' style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}> | ||||
|           {spoilerButton} | ||||
|           <div style={{ position: 'absolute', top: '50%', left: '50%', fontSize: '36px', transform: 'translate(-50%, -50%)', padding: '5px', borderRadius: '100px', color: 'rgba(255, 255, 255, 0.8)' }}><i className='fa fa-play' /></div> | ||||
|         </div> | ||||
| @@ -225,7 +225,7 @@ const VideoPlayer = React.createClass({ | ||||
|         {spoilerButton} | ||||
|         {muteButton} | ||||
|         {expandButton} | ||||
|         <video ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} /> | ||||
|         <video role='button' tabIndex='0' ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -43,7 +43,16 @@ const Avatar = React.createClass({ | ||||
|     return ( | ||||
|       <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}> | ||||
|         {({ radius }) => | ||||
|           <a href={account.get('url')} className='account__header__avatar' target='_blank' rel='noopener' style={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden' }} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}> | ||||
|           <a | ||||
|             href={account.get('url')} | ||||
|             className='account__header__avatar' | ||||
|             target='_blank' | ||||
|             rel='noopener' | ||||
|             style={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden' }} | ||||
|             onMouseOver={this.handleMouseOver} | ||||
|             onMouseOut={this.handleMouseOut} | ||||
|             onFocus={this.handleMouseOver} | ||||
|             onBlur={this.handleMouseOut}> | ||||
|             <img src={account.get('avatar')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} /> | ||||
|           </a> | ||||
|         } | ||||
|   | ||||
| @@ -83,7 +83,7 @@ const PrivacyDropdown = React.createClass({ | ||||
|         <div className='privacy-dropdown__value'><IconButton icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle} /></div> | ||||
|         <div className='privacy-dropdown__dropdown'> | ||||
|           {options.map(item => | ||||
|             <div key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}> | ||||
|             <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}> | ||||
|               <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div> | ||||
|               <div className='privacy-dropdown__option__content'> | ||||
|                 <strong>{item.shortText}</strong> | ||||
|   | ||||
| @@ -36,6 +36,10 @@ const Search = React.createClass({ | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   noop () { | ||||
|  | ||||
|   }, | ||||
|  | ||||
|   handleFocus () { | ||||
|     this.props.onShow(); | ||||
|   }, | ||||
| @@ -56,9 +60,9 @@ const Search = React.createClass({ | ||||
|           onFocus={this.handleFocus} | ||||
|         /> | ||||
|  | ||||
|         <div className='search__icon'> | ||||
|         <div role='button' tabIndex='0' className='search__icon' onClick={hasValue ? this.handleClear : this.noop}> | ||||
|           <i className={`fa fa-search ${hasValue ? '' : 'active'}`} /> | ||||
|           <i className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} onClick={this.handleClear} /> | ||||
|           <i aria-label="Clear search" className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} /> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   | ||||
| @@ -15,7 +15,7 @@ const ClearColumnButton = React.createClass({ | ||||
|     const { intl } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <div title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}> | ||||
|       <div role='button' title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}> | ||||
|         <i className='fa fa-eraser' /> | ||||
|       </div> | ||||
|     ); | ||||
|   | ||||
| @@ -27,9 +27,11 @@ const ColumnSettings = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     settings: ImmutablePropTypes.map.isRequired, | ||||
|     intl: React.PropTypes.object.isRequired, | ||||
|     onChange: React.PropTypes.func.isRequired, | ||||
|     onSave: React.PropTypes.func.isRequired, | ||||
|     intl: React.PropTypes.shape({ | ||||
|       formatMessage: React.PropTypes.func.isRequired | ||||
|     }).isRequired | ||||
|   }, | ||||
|  | ||||
|   mixins: [PureRenderMixin], | ||||
|   | ||||
| @@ -71,7 +71,7 @@ const Notification = React.createClass({ | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
|   render () { | ||||
|   render () { // eslint-disable-line consistent-return | ||||
|     const { notification } = this.props; | ||||
|     const account          = notification.get('account'); | ||||
|     const displayName      = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); | ||||
|   | ||||
| @@ -14,8 +14,8 @@ const labelSpanStyle = { | ||||
|   marginLeft: '8px' | ||||
| }; | ||||
|  | ||||
| const SettingToggle = ({ settings, settingKey, label, onChange }) => ( | ||||
|   <label style={labelStyle}> | ||||
| const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => ( | ||||
|   <label htmlFor={htmlFor} style={labelStyle}> | ||||
|     <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} /> | ||||
|     <span className='setting-toggle' style={labelSpanStyle}>{label}</span> | ||||
|   </label> | ||||
| @@ -25,7 +25,8 @@ SettingToggle.propTypes = { | ||||
|   settings: ImmutablePropTypes.map.isRequired, | ||||
|   settingKey: React.PropTypes.array.isRequired, | ||||
|   label: React.PropTypes.node.isRequired, | ||||
|   onChange: React.PropTypes.func.isRequired | ||||
|   onChange: React.PropTypes.func.isRequired, | ||||
|   htmlFor: React.PropTypes.string | ||||
| }; | ||||
|  | ||||
| export default SettingToggle; | ||||
|   | ||||
| @@ -25,7 +25,7 @@ const ColumnHeader = React.createClass({ | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <div aria-label={type} className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick}> | ||||
|       <div role='button' tabIndex='0' aria-label={type} className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick}> | ||||
|         {icon} | ||||
|         {type} | ||||
|       </div> | ||||
|   | ||||
| @@ -34,7 +34,8 @@ ColumnLink.propTypes = { | ||||
|   icon: React.PropTypes.string.isRequired, | ||||
|   text: React.PropTypes.string.isRequired, | ||||
|   to: React.PropTypes.string, | ||||
|   href: React.PropTypes.string | ||||
|   href: React.PropTypes.string, | ||||
|   method: React.PropTypes.string | ||||
| }; | ||||
|  | ||||
| export default ColumnLink; | ||||
|   | ||||
| @@ -104,8 +104,8 @@ const MediaModal = React.createClass({ | ||||
|     leftNav = rightNav = content = ''; | ||||
|  | ||||
|     if (media.size > 1) { | ||||
|       leftNav  = <div style={leftNavStyle} className='modal-container__nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; | ||||
|       rightNav = <div style={rightNavStyle} className='modal-container__nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; | ||||
|       leftNav  = <div role='button' tabIndex='0' style={leftNavStyle} className='modal-container__nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; | ||||
|       rightNav = <div role='button' tabIndex='0' style={rightNavStyle} className='modal-container__nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; | ||||
|     } | ||||
|  | ||||
|     if (attachment.get('type') === 'image') { | ||||
|   | ||||
| @@ -66,7 +66,7 @@ const ModalRoot = React.createClass({ | ||||
|  | ||||
|               return ( | ||||
|                 <div key={key}> | ||||
|                   <div className='modal-root__overlay' style={{ opacity: style.opacity, transform: `translateZ(0px)` }} onClick={onClose} /> | ||||
|                   <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity, transform: `translateZ(0px)` }} onClick={onClose} /> | ||||
|                   <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}> | ||||
|                     <SpecificComponent {...props} onClose={onClose} /> | ||||
|                   </div> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ Link.parseAttrs = (link, parts) => { | ||||
|     link  = Link.parseParams(link, uriAttrs[1]) | ||||
|   } | ||||
|  | ||||
|   while(match = Link.attrPattern.exec(attrs)) { | ||||
|   while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign | ||||
|     attr  = match[1].toLowerCase() | ||||
|     value = match[4] || match[3] || match[2] | ||||
|  | ||||
|   | ||||
| @@ -73,7 +73,7 @@ const es = { | ||||
|   "notifications.column_settings.mention": "Menciones:", | ||||
|   "notifications.column_settings.reblog": "Retoots:", | ||||
|   "emoji_button.label": "Insertar emoji", | ||||
|   "privacy.public.short": "Público",  | ||||
|   "privacy.public.short": "Público", | ||||
|   "privacy.public.long": "Mostrar en la historia federada", | ||||
|   "privacy.unlisted.short": "Sin federar", | ||||
|   "privacy.unlisted.long": "No mostrar en la historia federada", | ||||
|   | ||||
| @@ -16,7 +16,7 @@ const ru = { | ||||
|   "status.show_less": "Свернуть", | ||||
|   "status.open": "Развернуть статус", | ||||
|   "status.report": "Пожаловаться", | ||||
|   "status.load_more": "Показать еще",  | ||||
|   "status.load_more": "Показать еще", | ||||
|   "video_player.toggle_sound": "Вкл./выкл. звук", | ||||
|   "video_player.toggle_visible": "Показать/скрыть", | ||||
|   "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.", | ||||
|   | ||||
| @@ -22,7 +22,7 @@ export default function errorsMiddleware() { | ||||
|  | ||||
|           dispatch(showAlert(title, message)); | ||||
|         } else { | ||||
|           console.error(action.error); | ||||
|           console.error(action.error); // eslint-disable-line no-console | ||||
|           dispatch(showAlert('Oops!', 'An unexpected error occurred.')); | ||||
|         } | ||||
|       } | ||||
|   | ||||
| @@ -9,17 +9,17 @@ const initialState = Immutable.List([]); | ||||
|  | ||||
| export default function alerts(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|     case ALERT_SHOW: | ||||
|       return state.push(Immutable.Map({ | ||||
|         key: state.size > 0 ? state.last().get('key') + 1 : 0, | ||||
|         title: action.title, | ||||
|         message: action.message | ||||
|       })); | ||||
|     case ALERT_DISMISS: | ||||
|       return state.filterNot(item => item.get('key') === action.alert.key); | ||||
|     case ALERT_CLEAR: | ||||
|       return state.clear(); | ||||
|     default: | ||||
|       return state; | ||||
|   case ALERT_SHOW: | ||||
|     return state.push(Immutable.Map({ | ||||
|       key: state.size > 0 ? state.last().get('key') + 1 : 0, | ||||
|       title: action.title, | ||||
|       message: action.message | ||||
|     })); | ||||
|   case ALERT_DISMISS: | ||||
|     return state.filterNot(item => item.get('key') === action.alert.key); | ||||
|   case ALERT_CLEAR: | ||||
|     return state.clear(); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
| }; | ||||
|   | ||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,9 +1,11 @@ | ||||
| { | ||||
|   "name": "mastodon", | ||||
|   "scripts": { | ||||
|     "test": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx", | ||||
|     "start": "babel-node ./streaming/index.js --presets es2015,stage-2", | ||||
|     "storybook": "start-storybook -p 9001 -c storybook", | ||||
|     "start": "babel-node ./streaming/index.js --presets es2015,stage-2" | ||||
|     "test": "npm run test:lint && npm run test:mocha", | ||||
|     "test:lint": "eslint -c .eslintrc --ext=js --ext=jsx app/assets/javascripts/", | ||||
|     "test:mocha": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@kadira/storybook": "^2.35.3", | ||||
| @@ -74,8 +76,12 @@ | ||||
|     "ws": "^2.1.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "babel-eslint": "^7.2.1", | ||||
|     "babel-eslint": "^7.2.2", | ||||
|     "eslint": "^3.19.0", | ||||
|     "eslint-plugin-jsx-a11y": "^4.0.0", | ||||
|     "eslint-plugin-react": "^6.10.3" | ||||
|   }, | ||||
|   "optionalDependencies": { | ||||
|     "fsevents": "*" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -250,6 +250,12 @@ argparse@^1.0.7: | ||||
|   dependencies: | ||||
|     sprintf-js "~1.0.2" | ||||
|  | ||||
| aria-query@^0.3.0: | ||||
|   version "0.3.0" | ||||
|   resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.3.0.tgz#cb8a9984e2862711c83c80ade5b8f5ca0de2b467" | ||||
|   dependencies: | ||||
|     ast-types-flow "0.0.7" | ||||
|  | ||||
| arr-diff@^2.0.0: | ||||
|   version "2.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" | ||||
| @@ -357,6 +363,10 @@ assertion-error@^1.0.1: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" | ||||
|  | ||||
| ast-types-flow@0.0.7: | ||||
|   version "0.0.7" | ||||
|   resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" | ||||
|  | ||||
| ast-types@0.9.5: | ||||
|   version "0.9.5" | ||||
|   resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.5.tgz#1a660a09945dbceb1f9c9cbb715002617424e04a" | ||||
| @@ -511,9 +521,9 @@ babel-core@^6.11.4: | ||||
|     slash "^1.0.0" | ||||
|     source-map "^0.5.0" | ||||
|  | ||||
| babel-eslint@^7.2.1: | ||||
|   version "7.2.1" | ||||
|   resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f" | ||||
| babel-eslint@^7.2.2: | ||||
|   version "7.2.2" | ||||
|   resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.2.tgz#0da2cbe6554fd0fb069f19674f2db2f9c59270ff" | ||||
|   dependencies: | ||||
|     babel-code-frame "^6.22.0" | ||||
|     babel-traverse "^6.23.1" | ||||
| @@ -2128,6 +2138,10 @@ d@1: | ||||
|   dependencies: | ||||
|     es5-ext "^0.10.9" | ||||
|  | ||||
| damerau-levenshtein@^1.0.0: | ||||
|   version "1.0.4" | ||||
|   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" | ||||
|  | ||||
| dashdash@^1.12.0: | ||||
|   version "1.14.0" | ||||
|   resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141" | ||||
| @@ -2343,6 +2357,10 @@ elliptic@^6.0.0: | ||||
|     hash.js "^1.0.0" | ||||
|     inherits "^2.0.1" | ||||
|  | ||||
| emoji-regex@^6.1.0: | ||||
|   version "6.4.2" | ||||
|   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.2.tgz#a30b6fee353d406d96cfb9fa765bdc82897eff6e" | ||||
|  | ||||
| emojione-picker@^2.0.1: | ||||
|   version "2.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/emojione-picker/-/emojione-picker-2.0.1.tgz#62e58db67d37a400a883c82d39abb1cc1c8ed65a" | ||||
| @@ -2542,6 +2560,17 @@ escope@^3.6.0: | ||||
|     esrecurse "^4.1.0" | ||||
|     estraverse "^4.1.1" | ||||
|  | ||||
| eslint-plugin-jsx-a11y@^4.0.0: | ||||
|   version "4.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-4.0.0.tgz#779bb0fe7b08da564a422624911de10061e048ee" | ||||
|   dependencies: | ||||
|     aria-query "^0.3.0" | ||||
|     ast-types-flow "0.0.7" | ||||
|     damerau-levenshtein "^1.0.0" | ||||
|     emoji-regex "^6.1.0" | ||||
|     jsx-ast-utils "^1.0.0" | ||||
|     object-assign "^4.0.1" | ||||
|  | ||||
| eslint-plugin-react@^6.10.3: | ||||
|   version "6.10.3" | ||||
|   resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78" | ||||
| @@ -2886,7 +2915,7 @@ fs.realpath@^1.0.0: | ||||
|   version "1.0.0" | ||||
|   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" | ||||
|  | ||||
| fsevents@^1.0.0: | ||||
| fsevents@*, fsevents@^1.0.0: | ||||
|   version "1.0.14" | ||||
|   resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.0.14.tgz#558e8cc38643d8ef40fe45158486d0d25758eee4" | ||||
|   dependencies: | ||||
| @@ -3672,7 +3701,7 @@ jsprim@^1.2.2: | ||||
|     json-schema "0.2.3" | ||||
|     verror "1.3.6" | ||||
|  | ||||
| jsx-ast-utils@^1.3.4: | ||||
| jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4: | ||||
|   version "1.4.0" | ||||
|   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.0.tgz#5afe38868f56bc8cc7aeaef0100ba8c75bd12591" | ||||
|   dependencies: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user