Add a11y linter (#130)
This commit is contained in:
committed by
Aleksei Gurianov
parent
9949632cd7
commit
b08e351da0
+10
-2
@@ -1,7 +1,15 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
|
||||
plugins: ['react', 'prettier'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
plugins: [
|
||||
'react',
|
||||
'jsx-a11y',
|
||||
'prettier',
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const handleBtnKeyPress = (event, handler) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handler && handler();
|
||||
}
|
||||
};
|
||||
|
||||
export const getHandleClickProps = handler => ({
|
||||
role: 'button',
|
||||
tabIndex: 0,
|
||||
onClick: handler,
|
||||
onKeyPress: event => handleBtnKeyPress(event, handler),
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import { PROVIDER_NAMES } from 'common/constants';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
|
||||
export default class AuthPanel extends Component {
|
||||
constructor(props) {
|
||||
@@ -41,11 +42,11 @@ export default class AuthPanel extends Component {
|
||||
{loggedIn && (
|
||||
<div className="auth-panel__column">
|
||||
You signed in as{' '}
|
||||
<strong className="auth-panel__username" onClick={this.toggleUserId}>
|
||||
<strong {...getHandleClickProps(this.toggleUserId)} className="auth-panel__username">
|
||||
{user.name}
|
||||
</strong>
|
||||
{isUserIdVisible && <span className="auth-panel__user-id"> ({user.id})</span>}.{' '}
|
||||
<span className="auth-panel__pseudo-link" role="link" tabIndex="0" onClick={props.onSignOut}>
|
||||
<span {...getHandleClickProps(props.onSignOut)} className="auth-panel__pseudo-link" role="link" >
|
||||
Sign out?
|
||||
</span>
|
||||
</div>
|
||||
@@ -62,9 +63,8 @@ export default class AuthPanel extends Component {
|
||||
{comma}
|
||||
<span
|
||||
className="auth-panel__pseudo-link"
|
||||
{...getHandleClickProps(() => props.onSignIn(provider))}
|
||||
role="link"
|
||||
tabIndex="0"
|
||||
onClick={() => props.onSignIn(provider)}
|
||||
>
|
||||
{PROVIDER_NAMES[provider]}
|
||||
</span>
|
||||
@@ -79,9 +79,8 @@ export default class AuthPanel extends Component {
|
||||
{user.admin && (
|
||||
<span
|
||||
className="auth-panel__pseudo-link auth-panel__admin-action"
|
||||
{...getHandleClickProps(this.toggleBlockedVisibility)}
|
||||
role="link"
|
||||
tabIndex="0"
|
||||
onClick={this.toggleBlockedVisibility}
|
||||
>
|
||||
{isBlockedVisible ? 'Hide' : 'Show'} blocked
|
||||
</span>
|
||||
@@ -91,16 +90,16 @@ export default class AuthPanel extends Component {
|
||||
|
||||
<span className="auth-panel__sort">
|
||||
Sort by{' '}
|
||||
<label className="auth-panel__select-label">
|
||||
<span className="auth-panel__select-label">
|
||||
{sortArray.find(x => x.selected).label}
|
||||
<select className="auth-panel__select" onChange={this.onSortChange}>
|
||||
<select className="auth-panel__select" onChange={this.onSortChange} onBlur={this.onSortChange}>
|
||||
{sortArray.map(sort => (
|
||||
<option value={sort.value} selected={sort.selected}>
|
||||
{sort.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import api from 'common/api';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
|
||||
export default class BlockedUsers extends Component {
|
||||
constructor(props) {
|
||||
@@ -51,12 +52,16 @@ export default class BlockedUsers extends Component {
|
||||
<span className="blocked-users__username">{user.name}</span>{' '}
|
||||
<span className="blocked-users__user-id">({user.id})</span>
|
||||
{isUserUnblocked && (
|
||||
<span className="blocked-users__action" onClick={() => this.block(user)}>
|
||||
<span
|
||||
{...getHandleClickProps(() => this.block(user))}
|
||||
className="blocked-users__action">
|
||||
block
|
||||
</span>
|
||||
)}
|
||||
{!isUserUnblocked && (
|
||||
<span className="blocked-users__action" onClick={() => this.unblock(user)}>
|
||||
<span
|
||||
{...getHandleClickProps(() => this.unblock(user))}
|
||||
className="blocked-users__action">
|
||||
unblock
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import api from 'common/api';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants';
|
||||
import { url } from 'common/settings';
|
||||
import store from 'common/store';
|
||||
@@ -416,7 +417,11 @@ export default class Comment extends Component {
|
||||
)}
|
||||
|
||||
{mods.view !== 'user' && (
|
||||
<span className="comment__username" title={o.user.id} onClick={this.toggleUserInfoVisibility}>
|
||||
<span
|
||||
{...getHandleClickProps(this.toggleUserInfoVisibility)}
|
||||
className="comment__username"
|
||||
title={o.user.id}
|
||||
>
|
||||
{o.user.name}
|
||||
</span>
|
||||
)}
|
||||
@@ -424,7 +429,7 @@ export default class Comment extends Component {
|
||||
{isAdmin &&
|
||||
mods.view !== 'user' && (
|
||||
<span
|
||||
onClick={o.user.verified ? this.onUnverifyClick : this.onVerifyClick}
|
||||
{...getHandleClickProps(o.user.verified ? this.onUnverifyClick : this.onVerifyClick)}
|
||||
aria-label="Toggle verification"
|
||||
title={o.user.verified ? 'Verified user' : 'Unverified user'}
|
||||
className={b('comment__verification', {}, { active: o.user.verified, clickable: true })}
|
||||
@@ -449,7 +454,9 @@ export default class Comment extends Component {
|
||||
aria-label="Go to parent comment"
|
||||
title="Go to parent comment"
|
||||
onClick={this.scrollToParent}
|
||||
/>
|
||||
>
|
||||
{' '}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{isAdmin && userBlocked && mods.view !== 'user' && <span className="comment__status">Blocked</span>}
|
||||
@@ -459,9 +466,8 @@ export default class Comment extends Component {
|
||||
{!mods.disabled &&
|
||||
mods.view !== 'user' && (
|
||||
<span
|
||||
{...getHandleClickProps(this.toggleCollapse)}
|
||||
className={b('comment__action', {}, { type: 'collapse', selected: mods.collapsed })}
|
||||
tabIndex="0"
|
||||
onClick={this.toggleCollapse}
|
||||
>
|
||||
{mods.collapsed ? '+' : '−'}
|
||||
</span>
|
||||
@@ -474,10 +480,8 @@ export default class Comment extends Component {
|
||||
{},
|
||||
{ type: 'up', selected: scoreIncreased, disabled: isGuest || isCurrentUser }
|
||||
)}
|
||||
role="button"
|
||||
aria-disabled={isGuest || isCurrentUser}
|
||||
tabIndex="0"
|
||||
onClick={isGuest || isCurrentUser ? null : this.increaseScore}
|
||||
{...getHandleClickProps(isGuest || isCurrentUser ? null : this.increaseScore)}
|
||||
title={
|
||||
isGuest
|
||||
? 'Only authorized users are allowed to vote'
|
||||
@@ -495,15 +499,13 @@ export default class Comment extends Component {
|
||||
</span>
|
||||
|
||||
<span
|
||||
{...getHandleClickProps(isGuest || isCurrentUser ? null : this.decreaseScore)}
|
||||
className={b(
|
||||
'comment__vote',
|
||||
{},
|
||||
{ type: 'down', selected: scoreDecreased, disabled: isGuest || isCurrentUser }
|
||||
)}
|
||||
role="button"
|
||||
aria-disabled={isGuest || isCurrentUser ? 'true' : 'false'}
|
||||
tabIndex="0"
|
||||
onClick={isGuest || isCurrentUser ? null : this.decreaseScore}
|
||||
title={
|
||||
isGuest
|
||||
? 'Only authorized users are allowed to vote'
|
||||
@@ -524,7 +526,7 @@ export default class Comment extends Component {
|
||||
!mods.disabled &&
|
||||
!isGuest &&
|
||||
mods.view !== 'user' && (
|
||||
<span className="comment__action" role="button" tabIndex="0" onClick={this.toggleReplying}>
|
||||
<span {...getHandleClickProps(this.toggleReplying)} className="comment__action">
|
||||
{isReplying ? 'Cancel' : 'Reply'}
|
||||
</span>
|
||||
)}
|
||||
@@ -536,10 +538,8 @@ export default class Comment extends Component {
|
||||
(!!editTimeLeft || isEditing) &&
|
||||
mods.view !== 'user' && (
|
||||
<span
|
||||
{...getHandleClickProps(this.toggleEditing)}
|
||||
className="comment__action comment__action_type_edit"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onClick={this.toggleEditing}
|
||||
>
|
||||
{isEditing ? 'Cancel' : 'Edit'}
|
||||
{editTimeLeft && ` (${editTimeLeft})`}
|
||||
@@ -550,31 +550,31 @@ export default class Comment extends Component {
|
||||
isAdmin && (
|
||||
<span className="comment__controls">
|
||||
{!pinned && (
|
||||
<span className="comment__control" role="button" tabIndex="0" onClick={this.onPinClick}>
|
||||
<span {...getHandleClickProps(this.onPinClick)} className="comment__control">
|
||||
Pin
|
||||
</span>
|
||||
)}
|
||||
|
||||
{pinned && (
|
||||
<span className="comment__control" role="button" tabIndex="0" onClick={this.onUnpinClick}>
|
||||
<span {...getHandleClickProps(this.onUnpinClick)} className="comment__control">
|
||||
Unpin
|
||||
</span>
|
||||
)}
|
||||
|
||||
{userBlocked && (
|
||||
<span className="comment__control" role="button" tabIndex="0" onClick={this.onUnblockClick}>
|
||||
<span {...getHandleClickProps(this.onUnblockClick)} className="comment__control">
|
||||
Unblock
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!userBlocked && (
|
||||
<span className="comment__control" role="button" tabIndex="0" onClick={this.onBlockClick}>
|
||||
<span {...getHandleClickProps(this.onBlockClick)} className="comment__control">
|
||||
Block
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!deleted && (
|
||||
<span className="comment__control" role="button" tabIndex="0" onClick={this.onDeleteClick}>
|
||||
<span {...getHandleClickProps(this.onDeleteClick)} className="comment__control">
|
||||
Delete
|
||||
</span>
|
||||
)}
|
||||
@@ -587,7 +587,7 @@ export default class Comment extends Component {
|
||||
|
||||
{isReplying &&
|
||||
mods.view !== 'user' && (
|
||||
<Input mix="comment__input" onSubmit={this.onReply} onCancel={this.toggleReplying} pid={o.id} autoFocus />
|
||||
<Input mix="comment__input" onSubmit={this.onReply} onCancel={this.toggleReplying} pid={o.id} />
|
||||
)}
|
||||
|
||||
{isEditing &&
|
||||
@@ -600,7 +600,6 @@ export default class Comment extends Component {
|
||||
id={o.id}
|
||||
value={o.orig}
|
||||
errorMessage={!editTimeLeft && 'Editing time has expired.'}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
</article>
|
||||
|
||||
@@ -134,7 +134,7 @@ export default class Input extends Component {
|
||||
const { mods = {}, value = null, errorMessage } = props;
|
||||
|
||||
return (
|
||||
<form className={b('input', props)} onSubmit={this.send} role="form" aria-label="New comment">
|
||||
<form className={b('input', props)} onSubmit={this.send} aria-label="New comment">
|
||||
<div className="input__field-wrapper">
|
||||
<textarea
|
||||
className="input__field"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import api from 'common/api';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
|
||||
import Comment from 'components/comment';
|
||||
import Preloader from 'components/preloader';
|
||||
@@ -45,7 +46,7 @@ export default class UserInfo extends Component {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<span className="user-info__close" onClick={onClose}>
|
||||
<span {...getHandleClickProps(onClose)} className="user-info__close">
|
||||
Close
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Generated
+64
@@ -260,6 +260,16 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
|
||||
"integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types-flow": "0.0.7",
|
||||
"commander": "^2.11.0"
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
|
||||
@@ -374,6 +384,12 @@
|
||||
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
|
||||
"dev": true
|
||||
},
|
||||
"ast-types-flow": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
||||
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
|
||||
"dev": true
|
||||
},
|
||||
"astral-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||
@@ -454,6 +470,15 @@
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz",
|
||||
"integrity": "sha1-Bd+nBa2orZ25k/polvItOVsLCgc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types-flow": "0.0.7"
|
||||
}
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||
@@ -2978,6 +3003,12 @@
|
||||
"es5-ext": "^0.10.9"
|
||||
}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
|
||||
"integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
|
||||
"dev": true
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
@@ -3415,6 +3446,12 @@
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
|
||||
"integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
||||
@@ -3713,6 +3750,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-jsx-a11y": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.0.tgz",
|
||||
"integrity": "sha512-hnhf28u7Z9zlh7Y56tETrwnPeBvXgcqlP7ntHvZsWQs/n/p/vPnfNMNFWTqJAFcbd8PrDEifX1NRGHsjnUmqMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aria-query": "^3.0.0",
|
||||
"array-includes": "^3.0.3",
|
||||
"ast-types-flow": "^0.0.7",
|
||||
"axobject-query": "^2.0.1",
|
||||
"damerau-levenshtein": "^1.0.4",
|
||||
"emoji-regex": "^6.5.1",
|
||||
"has": "^1.0.3",
|
||||
"jsx-ast-utils": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-prettier": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.1.tgz",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-prettier": "^2.6.1",
|
||||
"eslint-plugin-react": "^7.10.0",
|
||||
|
||||
Reference in New Issue
Block a user