Give focused status a sensible aria-label for screen readers (#8387)

* Give focused status a sensible aria-label for screen readers

Fix #8192

* Use content warning in aria-label unless expanded
master
Eugen Rochko 6 years ago committed by GitHub
parent 43b8df3228
commit 248df68c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      app/javascript/mastodon/components/status.js
  2. 3
      app/javascript/mastodon/features/status/index.js

@ -8,7 +8,7 @@ import DisplayName from './display_name';
import StatusContent from './status_content'; import StatusContent from './status_content';
import StatusActionBar from './status_action_bar'; import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list'; import AttachmentList from './attachment_list';
import { FormattedMessage } from 'react-intl'; import { injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components'; import { MediaGallery, Video } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
@ -18,6 +18,24 @@ import classNames from 'classnames';
// to use the progress bar to show download progress // to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle'; import Bundle from '../features/ui/components/bundle';
export const textForScreenReader = (intl, status, rebloggedByText = false, expanded = false) => {
const displayName = status.getIn(['account', 'display_name']);
const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
status.get('spoiler_text') && !expanded ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length),
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
];
if (rebloggedByText) {
values.push(rebloggedByText);
}
return values.join(', ');
};
@injectIntl
export default class Status extends ImmutablePureComponent { export default class Status extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
@ -138,9 +156,9 @@ export default class Status extends ImmutablePureComponent {
render () { render () {
let media = null; let media = null;
let statusAvatar, prepend; let statusAvatar, prepend, rebloggedByText;
const { hidden, featured } = this.props; const { intl, hidden, featured } = this.props;
let { status, account, ...other } = this.props; let { status, account, ...other } = this.props;
@ -189,6 +207,8 @@ export default class Status extends ImmutablePureComponent {
</div> </div>
); );
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
account = status.get('account'); account = status.get('account');
status = status.get('reblog'); status = status.get('reblog');
} }
@ -248,7 +268,7 @@ export default class Status extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null}> <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
{prepend} {prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { muted: this.props.muted })} data-id={status.get('id')}> <div className={classNames('status', `status-${status.get('visibility')}`, { muted: this.props.muted })} data-id={status.get('id')}>

@ -43,6 +43,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state'; import { boostModal, deleteModal } from '../../initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import { textForScreenReader } from '../../components/status';
const messages = defineMessages({ const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -418,7 +419,7 @@ export default class Status extends ImmutablePureComponent {
{ancestors} {ancestors}
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className='focusable' tabIndex='0'> <div className='focusable' tabIndex='0' aria-label={textForScreenReader(intl, status, false, !status.get('hidden'))}>
<DetailedStatus <DetailedStatus
status={status} status={status}
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}

Loading…
Cancel
Save