Add aria attributes where possible (#156)

* Add aria attributes where possible

* Bump submodule to get aria translations
This commit is contained in:
Mitch Lillie 2021-02-06 12:20:41 -08:00 committed by GitHub
parent ef19b9f6b8
commit 04955cc45e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 137 additions and 19 deletions

@ -1 +1 @@
Subproject commit a47ae3f825f74ad7a6106b92e21d3c66b10d3fe8 Subproject commit 084ce539fff5253317d6460598b10a6867999c20

View file

@ -14,12 +14,13 @@ export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
render() { render() {
return ( return (
<div class="position-relative mb-2"> <div class="position-relative mb-2">
{this.props.banner && <PictrsImage src={this.props.banner} />} {this.props.banner && <PictrsImage src={this.props.banner} alt="" />}
{this.props.icon && ( {this.props.icon && (
<PictrsImage <PictrsImage
src={this.props.icon} src={this.props.icon}
iconOverlay iconOverlay
pushup={!!this.props.banner} pushup={!!this.props.banner}
alt=""
/> />
)} )}
</div> </div>

View file

@ -198,6 +198,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
onClick={linkEvent(this, this.handleCommentCollapse)} onClick={linkEvent(this, this.handleCommentCollapse)}
aria-label={
this.state.collapsed ? i18n.t('expand') : i18n.t('collapse')
}
> >
{this.state.collapsed ? '+' : '—'} {this.state.collapsed ? '+' : '—'}
</button> </button>
@ -248,6 +251,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
? i18n.t('mark_as_unread') ? i18n.t('mark_as_unread')
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
aria-label={
this.commentOrMentionRead
? i18n.t('mark_as_unread')
: i18n.t('mark_as_read')
}
> >
{this.state.readLoading ? ( {this.state.readLoading ? (
this.loadingIcon this.loadingIcon
@ -270,6 +278,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}`} }`}
onClick={linkEvent(node, this.handleCommentUpvote)} onClick={linkEvent(node, this.handleCommentUpvote)}
data-tippy-content={i18n.t('upvote')} data-tippy-content={i18n.t('upvote')}
aria-label={i18n.t('upvote')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-arrow-up1"></use> <use xlinkHref="#icon-arrow-up1"></use>
@ -287,6 +296,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}`} }`}
onClick={linkEvent(node, this.handleCommentDownvote)} onClick={linkEvent(node, this.handleCommentDownvote)}
data-tippy-content={i18n.t('downvote')} data-tippy-content={i18n.t('downvote')}
aria-label={i18n.t('downvote')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-arrow-down1"></use> <use xlinkHref="#icon-arrow-down1"></use>
@ -300,6 +310,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t('reply')} data-tippy-content={i18n.t('reply')}
aria-label={i18n.t('reply')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-reply1"></use> <use xlinkHref="#icon-reply1"></use>
@ -310,6 +321,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleShowAdvanced)} onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
aria-label={i18n.t('more')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-more-vertical"></use> <use xlinkHref="#icon-more-vertical"></use>
@ -340,6 +352,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
data-tippy-content={ data-tippy-content={
cv.saved ? i18n.t('unsave') : i18n.t('save') cv.saved ? i18n.t('unsave') : i18n.t('save')
} }
aria-label={
cv.saved ? i18n.t('unsave') : i18n.t('save')
}
> >
{this.state.saveLoading ? ( {this.state.saveLoading ? (
this.loadingIcon this.loadingIcon
@ -357,6 +372,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
aria-label={i18n.t('view_source')}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -372,6 +388,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-edit"></use> <use xlinkHref="#icon-edit"></use>
@ -388,6 +405,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
? i18n.t('delete') ? i18n.t('delete')
: i18n.t('restore') : i18n.t('restore')
} }
aria-label={
!cv.comment.deleted
? i18n.t('delete')
: i18n.t('restore')
}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -667,9 +689,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.state.showBanDialog && ( {this.state.showBanDialog && (
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-form-label">{i18n.t('reason')}</label> <label
class="col-form-label"
htmlFor={`mod-ban-reason-${cv.comment.id}`}
>
{i18n.t('reason')}
</label>
<input <input
type="text" type="text"
id={`mod-ban-reason-${cv.comment.id}`}
class="form-control mr-2" class="form-control mr-2"
placeholder={i18n.t('reason')} placeholder={i18n.t('reason')}
value={this.state.banReason} value={this.state.banReason}

View file

@ -158,6 +158,7 @@ export class Communities extends Component<any, CommunitiesState> {
{cv.subscribed ? ( {cv.subscribed ? (
<span <span
class="pointer btn-link" class="pointer btn-link"
role="button"
onClick={linkEvent( onClick={linkEvent(
cv.community.id, cv.community.id,
this.handleUnsubscribe this.handleUnsubscribe
@ -168,6 +169,7 @@ export class Communities extends Component<any, CommunitiesState> {
) : ( ) : (
<span <span
class="pointer btn-link" class="pointer btn-link"
role="button"
onClick={linkEvent( onClick={linkEvent(
cv.community.id, cv.community.id,
this.handleSubscribe this.handleSubscribe

View file

@ -2,6 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { pictrsUri } from '../env'; import { pictrsUri } from '../env';
import { UserService } from '../services'; import { UserService } from '../services';
import { toast, randomStr } from '../utils'; import { toast, randomStr } from '../utils';
import { i18n } from '../i18next';
interface ImageUploadFormProps { interface ImageUploadFormProps {
uploadTitle: string; uploadTitle: string;
@ -48,7 +49,10 @@ export class ImageUploadForm extends Component<
this.props.rounded ? 'rounded-circle' : '' this.props.rounded ? 'rounded-circle' : ''
}`} }`}
/> />
<a onClick={linkEvent(this, this.handleRemoveImage)}> <a
onClick={linkEvent(this, this.handleRemoveImage)}
aria-label={i18n.t('remove')}
>
<svg class="icon mini-overlay"> <svg class="icon mini-overlay">
<use xlinkHref="#icon-x"></use> <use xlinkHref="#icon-x"></use>
</svg> </svg>

View file

@ -172,6 +172,7 @@ export class Inbox extends Component<any, InboxState> {
<li className="list-inline-item"> <li className="list-inline-item">
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent(this, this.markAllAsRead)} onClick={linkEvent(this, this.markAllAsRead)}
> >
{i18n.t('mark_all_as_read')} {i18n.t('mark_all_as_read')}

View file

@ -326,6 +326,7 @@ export class Login extends Component<any, State> {
class="rounded-top img-fluid" class="rounded-top img-fluid"
src={this.captchaPngSrc()} src={this.captchaPngSrc()}
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
alt={i18n.t('captcha')}
/> />
{this.state.captcha.ok.wav && ( {this.state.captcha.ok.wav && (
<button <button

View file

@ -508,7 +508,9 @@ export class Main extends Component<any, MainState> {
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
aria-label={i18n.t('edit')}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">

View file

@ -233,6 +233,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('header')} data-tippy-content={i18n.t('header')}
aria-label={i18n.t('header')}
onClick={linkEvent(this, this.handleInsertHeader)} onClick={linkEvent(this, this.handleInsertHeader)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -242,6 +243,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('strikethrough')} data-tippy-content={i18n.t('strikethrough')}
aria-label={i18n.t('strikethrough')}
onClick={linkEvent(this, this.handleInsertStrikethrough)} onClick={linkEvent(this, this.handleInsertStrikethrough)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -251,6 +253,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('quote')} data-tippy-content={i18n.t('quote')}
aria-label={i18n.t('quote')}
onClick={linkEvent(this, this.handleInsertQuote)} onClick={linkEvent(this, this.handleInsertQuote)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -260,6 +263,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('list')} data-tippy-content={i18n.t('list')}
aria-label={i18n.t('list')}
onClick={linkEvent(this, this.handleInsertList)} onClick={linkEvent(this, this.handleInsertList)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -269,6 +273,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('code')} data-tippy-content={i18n.t('code')}
aria-label={i18n.t('code')}
onClick={linkEvent(this, this.handleInsertCode)} onClick={linkEvent(this, this.handleInsertCode)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -278,6 +283,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('subscript')} data-tippy-content={i18n.t('subscript')}
aria-label={i18n.t('subscript')}
onClick={linkEvent(this, this.handleInsertSubscript)} onClick={linkEvent(this, this.handleInsertSubscript)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -287,6 +293,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('superscript')} data-tippy-content={i18n.t('superscript')}
aria-label={i18n.t('superscript')}
onClick={linkEvent(this, this.handleInsertSuperscript)} onClick={linkEvent(this, this.handleInsertSuperscript)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -296,6 +303,7 @@ export class MarkdownTextArea extends Component<
<button <button
class="btn btn-sm text-muted" class="btn btn-sm text-muted"
data-tippy-content={i18n.t('spoiler')} data-tippy-content={i18n.t('spoiler')}
aria-label={i18n.t('spoiler')}
onClick={linkEvent(this, this.handleInsertSpoiler)} onClick={linkEvent(this, this.handleInsertSpoiler)}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">

View file

@ -80,11 +80,12 @@ export class PasswordChange extends Component<any, State> {
return ( return (
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}> <form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="new-password">
{i18n.t('new_password')} {i18n.t('new_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
id="new-password"
type="password" type="password"
value={this.state.passwordChangeForm.password} value={this.state.passwordChangeForm.password}
onInput={linkEvent(this, this.handlePasswordChange)} onInput={linkEvent(this, this.handlePasswordChange)}
@ -94,11 +95,12 @@ export class PasswordChange extends Component<any, State> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="verify-password">
{i18n.t('verify_password')} {i18n.t('verify_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
id="verify-password"
type="password" type="password"
value={this.state.passwordChangeForm.password_verify} value={this.state.passwordChangeForm.password_verify}
onInput={linkEvent(this, this.handleVerifyPasswordChange)} onInput={linkEvent(this, this.handleVerifyPasswordChange)}

View file

@ -6,6 +6,7 @@ const maxImageSize = 3000;
interface PictrsImageProps { interface PictrsImageProps {
src: string; src: string;
alt?: string;
icon?: boolean; icon?: boolean;
thumbnail?: boolean; thumbnail?: boolean;
nsfw?: boolean; nsfw?: boolean;
@ -25,6 +26,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
<source srcSet={this.src('jpg')} type="image/jpeg" /> <source srcSet={this.src('jpg')} type="image/jpeg" />
<img <img
src={this.src('jpg')} src={this.src('jpg')}
alt={this.alt()}
className={` className={`
${!this.props.icon && !this.props.iconOverlay && 'img-fluid '} ${!this.props.icon && !this.props.iconOverlay && 'img-fluid '}
${ ${
@ -71,4 +73,11 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
return out; return out;
} }
alt(): string {
if (this.props.icon) {
return '';
}
return this.props.alt || '';
}
} }

View file

@ -180,6 +180,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{this.state.suggestedTitle && ( {this.state.suggestedTitle && (
<div <div
class="mt-1 text-muted small font-weight-bold pointer" class="mt-1 text-muted small font-weight-bold pointer"
role="button"
onClick={linkEvent(this, this.copySuggestedTitle)} onClick={linkEvent(this, this.copySuggestedTitle)}
> >
{i18n.t('copy_suggested_title', { {i18n.t('copy_suggested_title', {
@ -227,7 +228,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</svg> </svg>
)} )}
{isImage(this.state.postForm.url) && ( {isImage(this.state.postForm.url) && (
<img src={this.state.postForm.url} class="img-fluid" /> <img src={this.state.postForm.url} class="img-fluid" alt="" />
)} )}
{this.state.crossPosts.length > 0 && ( {this.state.crossPosts.length > 0 && (
<> <>

View file

@ -169,6 +169,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<PictrsImage <PictrsImage
src={src} src={src}
thumbnail thumbnail
alt=""
nsfw={post_view.post.nsfw || post_view.community.nsfw} nsfw={post_view.post.nsfw || post_view.community.nsfw}
/> />
); );
@ -200,6 +201,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="float-right text-body pointer d-inline-block position-relative mb-2" class="float-right text-body pointer d-inline-block position-relative mb-2"
data-tippy-content={i18n.t('expand_here')} data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
role="button"
aria-label={i18n.t('expand_here')}
> >
{this.imgThumb(this.getImageSrc())} {this.imgThumb(this.getImageSrc())}
<svg class="icon mini-overlay"> <svg class="icon mini-overlay">
@ -328,6 +331,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
previewLines(post_view.post.body) previewLines(post_view.post.body)
)} )}
data-tippy-allowHtml={true} data-tippy-allowHtml={true}
aria-label={i18n.t('upvote')}
to={`/post/${post_view.post.id}`} to={`/post/${post_view.post.id}`}
> >
<svg class="mr-1 icon icon-inline"> <svg class="mr-1 icon icon-inline">
@ -350,6 +354,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
onClick={linkEvent(this, this.handlePostLike)} onClick={linkEvent(this, this.handlePostLike)}
data-tippy-content={i18n.t('upvote')} data-tippy-content={i18n.t('upvote')}
aria-label={i18n.t('upvote')}
> >
<svg class="icon upvote"> <svg class="icon upvote">
<use xlinkHref="#icon-arrow-up1"></use> <use xlinkHref="#icon-arrow-up1"></use>
@ -368,6 +373,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
onClick={linkEvent(this, this.handlePostDisLike)} onClick={linkEvent(this, this.handlePostDisLike)}
data-tippy-content={i18n.t('downvote')} data-tippy-content={i18n.t('downvote')}
aria-label={i18n.t('downvote')}
> >
<svg class="icon downvote"> <svg class="icon downvote">
<use xlinkHref="#icon-arrow-down1"></use> <use xlinkHref="#icon-arrow-down1"></use>
@ -504,6 +510,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
class="btn text-muted py-0 pr-0" class="btn text-muted py-0 pr-0"
data-tippy-content={this.pointsTippy} data-tippy-content={this.pointsTippy}
aria-label={i18n.t('downvote')}
> >
<small> <small>
<svg class="icon icon-inline mr-1"> <svg class="icon icon-inline mr-1">
@ -520,6 +527,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={ data-tippy-content={
post_view.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')}
> >
<small> <small>
<svg <svg
@ -545,6 +553,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
data-tippy-content={this.pointsTippy} data-tippy-content={this.pointsTippy}
onClick={linkEvent(this, this.handlePostLike)} onClick={linkEvent(this, this.handlePostLike)}
aria-label={i18n.t('upvote')}
> >
<svg class="small icon icon-inline mr-2"> <svg class="small icon icon-inline mr-2">
<use xlinkHref="#icon-arrow-up1"></use> <use xlinkHref="#icon-arrow-up1"></use>
@ -558,6 +567,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
onClick={linkEvent(this, this.handlePostDisLike)} onClick={linkEvent(this, this.handlePostDisLike)}
data-tippy-content={this.pointsTippy} data-tippy-content={this.pointsTippy}
aria-label={i18n.t('downvote')}
> >
<svg class="small icon icon-inline mr-2"> <svg class="small icon icon-inline mr-2">
<use xlinkHref="#icon-arrow-down1"></use> <use xlinkHref="#icon-arrow-down1"></use>
@ -571,6 +581,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0" class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0"
onClick={linkEvent(this, this.handleSavePostClick)} onClick={linkEvent(this, this.handleSavePostClick)}
aria-label={post_view.saved ? i18n.t('unsave') : i18n.t('save')}
data-tippy-content={ data-tippy-content={
post_view.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
@ -586,6 +597,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleShowMoreMobile)} onClick={linkEvent(this, this.handleShowMoreMobile)}
aria-label={i18n.t('more')}
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -639,6 +651,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
data-tippy-content={ data-tippy-content={
post_view.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
aria-label={
post_view.saved ? i18n.t('unsave') : i18n.t('save')
}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -694,6 +709,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleShowAdvanced)} onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t('more')} data-tippy-content={i18n.t('more')}
aria-label={i18n.t('more')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-more-vertical"></use> <use xlinkHref="#icon-more-vertical"></use>

View file

@ -116,8 +116,10 @@ export class PrivateMessageForm extends Component<
{i18n.t('message')} {i18n.t('message')}
<span <span
onClick={linkEvent(this, this.handleShowDisclaimer)} onClick={linkEvent(this, this.handleShowDisclaimer)}
role="button"
class="ml-2 pointer text-danger" class="ml-2 pointer text-danger"
data-tippy-content={i18n.t('disclaimer')} data-tippy-content={i18n.t('disclaimer')}
aria-label={i18n.t('disclaimer')}
> >
<svg class={`icon icon-inline`}> <svg class={`icon icon-inline`}>
<use xlinkHref="#icon-alert-triangle"></use> <use xlinkHref="#icon-alert-triangle"></use>

View file

@ -77,6 +77,7 @@ export class PrivateMessage extends Component<
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<div <div
role="button"
className="pointer text-monospace" className="pointer text-monospace"
onClick={linkEvent(this, this.handleMessageCollapse)} onClick={linkEvent(this, this.handleMessageCollapse)}
> >
@ -123,6 +124,11 @@ export class PrivateMessage extends Component<
? i18n.t('mark_as_unread') ? i18n.t('mark_as_unread')
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
aria-label={
message_view.private_message.read
? i18n.t('mark_as_unread')
: i18n.t('mark_as_read')
}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -138,6 +144,7 @@ export class PrivateMessage extends Component<
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t('reply')} data-tippy-content={i18n.t('reply')}
aria-label={i18n.t('reply')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-reply1"></use> <use xlinkHref="#icon-reply1"></use>
@ -153,6 +160,7 @@ export class PrivateMessage extends Component<
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-edit"></use> <use xlinkHref="#icon-edit"></use>
@ -168,6 +176,11 @@ export class PrivateMessage extends Component<
? i18n.t('delete') ? i18n.t('delete')
: i18n.t('restore') : i18n.t('restore')
} }
aria-label={
!message_view.private_message.deleted
? i18n.t('delete')
: i18n.t('restore')
}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -186,6 +199,7 @@ export class PrivateMessage extends Component<
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
aria-label={i18n.t('view_source')}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${

View file

@ -209,6 +209,7 @@ export class Search extends Component<any, SearchState> {
class="form-control mr-2 mb-2" class="form-control mr-2 mb-2"
value={this.state.searchText} value={this.state.searchText}
placeholder={`${i18n.t('search')}...`} placeholder={`${i18n.t('search')}...`}
aria-label={i18n.t('search')}
onInput={linkEvent(this, this.handleQChange)} onInput={linkEvent(this, this.handleQChange)}
required required
minLength={3} minLength={3}
@ -233,8 +234,11 @@ export class Search extends Component<any, SearchState> {
value={this.state.type_} value={this.state.type_}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
class="custom-select w-auto mb-2" class="custom-select w-auto mb-2"
aria-label={i18n.t('type')}
> >
<option disabled>{i18n.t('type')}</option> <option disabled aria-hidden="true">
{i18n.t('type')}
</option>
<option value={SearchType.All}>{i18n.t('all')}</option> <option value={SearchType.All}>{i18n.t('all')}</option>
<option value={SearchType.Comments}>{i18n.t('comments')}</option> <option value={SearchType.Comments}>{i18n.t('comments')}</option>
<option value={SearchType.Posts}>{i18n.t('posts')}</option> <option value={SearchType.Posts}>{i18n.t('posts')}</option>

View file

@ -299,9 +299,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<> <>
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<span <span
role="button"
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
aria-label={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-edit"></use> <use xlinkHref="#icon-edit"></use>
@ -313,6 +315,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent( onClick={linkEvent(
this, this,
this.handleShowConfirmLeaveModTeamClick this.handleShowConfirmLeaveModTeamClick
@ -329,6 +332,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent(this, this.handleLeaveModTeamClick)} onClick={linkEvent(this, this.handleLeaveModTeamClick)}
> >
{i18n.t('yes')} {i18n.t('yes')}
@ -337,6 +341,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent( onClick={linkEvent(
this, this,
this.handleCancelLeaveModTeamClick this.handleCancelLeaveModTeamClick
@ -357,6 +362,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
? i18n.t('delete') ? i18n.t('delete')
: i18n.t('restore') : i18n.t('restore')
} }
aria-label={
!community_view.community.deleted
? i18n.t('delete')
: i18n.t('restore')
}
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
@ -375,6 +385,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{!this.props.community_view.community.removed ? ( {!this.props.community_view.community.removed ? (
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
> >
{i18n.t('remove')} {i18n.t('remove')}
@ -382,6 +393,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
) : ( ) : (
<span <span
class="pointer" class="pointer"
role="button"
onClick={linkEvent(this, this.handleModRemoveSubmit)} onClick={linkEvent(this, this.handleModRemoveSubmit)}
> >
{i18n.t('restore')} {i18n.t('restore')}

View file

@ -40,8 +40,9 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
value={this.state.sort} value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)} onChange={linkEvent(this, this.handleSortChange)}
class="custom-select w-auto mr-2 mb-2" class="custom-select w-auto mr-2 mb-2"
aria-label={i18n.t('sort_type')}
> >
<option disabled>{i18n.t('sort_type')}</option> <option disabled aria-hidden="true">{i18n.t('sort_type')}</option>
{!this.props.hideHot && [ {!this.props.hideHot && [
<option value={SortType.Hot}>{i18n.t('hot')}</option>, <option value={SortType.Hot}>{i18n.t('hot')}</option>,
<option value={SortType.Active}>{i18n.t('active')}</option>, <option value={SortType.Active}>{i18n.t('active')}</option>,
@ -52,7 +53,7 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
{i18n.t('most_comments')} {i18n.t('most_comments')}
</option> </option>
)} )}
<option disabled></option> <option disabled aria-hidden="true"></option>
<option value={SortType.TopDay}>{i18n.t('top_day')}</option> <option value={SortType.TopDay}>{i18n.t('top_day')}</option>
<option value={SortType.TopWeek}>{i18n.t('top_week')}</option> <option value={SortType.TopWeek}>{i18n.t('top_week')}</option>
<option value={SortType.TopMonth}>{i18n.t('top_month')}</option> <option value={SortType.TopMonth}>{i18n.t('top_month')}</option>

View file

@ -526,28 +526,36 @@ export class User extends Component<any, UserState> {
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{i18n.t('language')}</label> <label htmlFor="user-language">{i18n.t('language')}</label>
<select <select
id="user-language"
value={this.state.userSettingsForm.lang} value={this.state.userSettingsForm.lang}
onChange={linkEvent(this, this.handleUserSettingsLangChange)} onChange={linkEvent(this, this.handleUserSettingsLangChange)}
class="ml-2 custom-select w-auto" class="ml-2 custom-select w-auto"
> >
<option disabled>{i18n.t('language')}</option> <option disabled aria-hidden="true">
{i18n.t('language')}
</option>
<option value="browser">{i18n.t('browser_default')}</option> <option value="browser">{i18n.t('browser_default')}</option>
<option disabled></option> <option disabled aria-hidden="true">
</option>
{languages.map(lang => ( {languages.map(lang => (
<option value={lang.code}>{lang.name}</option> <option value={lang.code}>{lang.name}</option>
))} ))}
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{i18n.t('theme')}</label> <label htmlFor="user-theme">{i18n.t('theme')}</label>
<select <select
id="user-theme"
value={this.state.userSettingsForm.theme} value={this.state.userSettingsForm.theme}
onChange={linkEvent(this, this.handleUserSettingsThemeChange)} onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
class="ml-2 custom-select w-auto" class="ml-2 custom-select w-auto"
> >
<option disabled>{i18n.t('theme')}</option> <option disabled aria-hidden="true">
{i18n.t('theme')}
</option>
<option value="browser">{i18n.t('browser_default')}</option> <option value="browser">{i18n.t('browser_default')}</option>
{themes.map(theme => ( {themes.map(theme => (
<option value={theme}>{theme}</option> <option value={theme}>{theme}</option>
@ -584,11 +592,12 @@ export class User extends Component<any, UserState> {
/> />
</form> </form>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-5 col-form-label"> <label class="col-lg-5 col-form-label" htmlFor="display-name">
{i18n.t('display_name')} {i18n.t('display_name')}
</label> </label>
<div class="col-lg-7"> <div class="col-lg-7">
<input <input
id="display-name"
type="text" type="text"
class="form-control" class="form-control"
placeholder={i18n.t('optional')} placeholder={i18n.t('optional')}
@ -636,13 +645,14 @@ export class User extends Component<any, UserState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-5 col-form-label"> <label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
<a href={elementUrl} target="_blank" rel="noopener"> <a href={elementUrl} target="_blank" rel="noopener">
{i18n.t('matrix_user_id')} {i18n.t('matrix_user_id')}
</a> </a>
</label> </label>
<div class="col-lg-7"> <div class="col-lg-7">
<input <input
id="matrix-user-id"
type="text" type="text"
class="form-control" class="form-control"
placeholder="@user:example.com" placeholder="@user:example.com"