mirror of https://github.com/Siphonay/mastodon
Merge pull request #681 from ThibG/glitch-soc/fixes/accessibility
Port various accessibility improvements from upstream
This commit is contained in:
commit
c065717b67
|
@ -9,6 +9,7 @@ export default class Column extends React.PureComponent {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
extraClasses: PropTypes.string,
|
extraClasses: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
|
label: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollTop () {
|
scrollTop () {
|
||||||
|
@ -42,10 +43,10 @@ export default class Column extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, extraClasses, name } = this.props;
|
const { children, extraClasses, name, label } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role='region' data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
|
<div role='region' aria-label={label} data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -107,7 +107,7 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
ref={this.handleRef}
|
ref={this.handleRef}
|
||||||
aria-posinset={index}
|
aria-posinset={index + 1}
|
||||||
aria-setsize={listLength}
|
aria-setsize={listLength}
|
||||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
|
@ -119,7 +119,7 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article ref={this.handleRef} aria-posinset={index} aria-setsize={listLength} data-id={id} tabIndex='0'>
|
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex='0'>
|
||||||
{children && React.cloneElement(children, { hidden: false })}
|
{children && React.cloneElement(children, { hidden: false })}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import StatusIcons from './status_icons';
|
||||||
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 'flavours/glitch/util/async-components';
|
import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
|
@ -19,6 +19,24 @@ import { autoUnfoldCW } from 'flavours/glitch/util/content_warning';
|
||||||
// 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 = {
|
||||||
|
@ -52,6 +70,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
getScrollPosition: PropTypes.func,
|
getScrollPosition: PropTypes.func,
|
||||||
updateScrollBottom: PropTypes.func,
|
updateScrollBottom: PropTypes.func,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -337,6 +356,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
} = this;
|
} = this;
|
||||||
const { router } = this.context;
|
const { router } = this.context;
|
||||||
const {
|
const {
|
||||||
|
intl,
|
||||||
status,
|
status,
|
||||||
account,
|
account,
|
||||||
settings,
|
settings,
|
||||||
|
@ -474,6 +494,12 @@ export default class Status extends ImmutablePureComponent {
|
||||||
selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
|
selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rebloggedByText;
|
||||||
|
|
||||||
|
if (prepend === 'reblog') {
|
||||||
|
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
|
||||||
|
}
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
reply: this.handleHotkeyReply,
|
reply: this.handleHotkeyReply,
|
||||||
favourite: this.handleHotkeyFavourite,
|
favourite: this.handleHotkeyFavourite,
|
||||||
|
@ -502,6 +528,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
ref={handleRef}
|
ref={handleRef}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
data-featured={featured ? 'true' : null}
|
data-featured={featured ? 'true' : null}
|
||||||
|
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
|
||||||
>
|
>
|
||||||
<header className='status__info'>
|
<header className='status__info'>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default class CommunityTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='local'>
|
<Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='users'
|
icon='users'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default class DirectTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='envelope'
|
icon='envelope'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
// Actions.
|
// Actions.
|
||||||
|
@ -25,6 +26,11 @@ import DrawerSearch from './search';
|
||||||
import { me } from 'flavours/glitch/util/initial_state';
|
import { me } from 'flavours/glitch/util/initial_state';
|
||||||
import { wrap } from 'flavours/glitch/util/redux_helpers';
|
import { wrap } from 'flavours/glitch/util/redux_helpers';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
|
||||||
|
});
|
||||||
|
|
||||||
// State mapping.
|
// State mapping.
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
account: state.getIn(['accounts', me]),
|
account: state.getIn(['accounts', me]),
|
||||||
|
@ -96,7 +102,7 @@ class Drawer extends React.Component {
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return (
|
return (
|
||||||
<div className={computedClass}>
|
<div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||||
{multiColumn ? (
|
{multiColumn ? (
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='favourites'>
|
<Column ref={this.setRef} name='favourites' label={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='star'
|
icon='star'
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
|
|
@ -33,6 +33,7 @@ const messages = defineMessages({
|
||||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||||
lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' },
|
lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' },
|
||||||
misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' },
|
misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' },
|
||||||
|
menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -148,7 +149,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
<Column name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||||
<div className='scrollable optionally-scrollable'>
|
<div className='scrollable optionally-scrollable'>
|
||||||
<div className='getting-started__wrapper'>
|
<div className='getting-started__wrapper'>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class HashtagTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='hashtag'>
|
<Column ref={this.setRef} name='hashtag' label={`#${id}`}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='hashtag'
|
icon='hashtag'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -97,7 +97,7 @@ export default class HomeTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='home'>
|
<Column ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='home'
|
icon='home'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -136,7 +136,7 @@ export default class ListTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef} label={title}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='list-ul'
|
icon='list-ul'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -203,6 +203,7 @@ export default class Notifications extends React.PureComponent {
|
||||||
ref={this.setColumnRef}
|
ref={this.setColumnRef}
|
||||||
name='notifications'
|
name='notifications'
|
||||||
extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
|
extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
|
||||||
|
label={intl.formatMessage(messages.title)}
|
||||||
>
|
>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='bell'
|
icon='bell'
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default class PublicTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='federated'>
|
<Column ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='globe'
|
icon='globe'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default class CommunityTimeline extends React.PureComponent {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='users'
|
icon='users'
|
||||||
title={intl.formatMessage(messages.title)}
|
title={intl.formatMessage(messages.title)}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default class PublicTimeline extends React.PureComponent {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='globe'
|
icon='globe'
|
||||||
title={intl.formatMessage(messages.title)}
|
title={intl.formatMessage(messages.title)}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { HotKeys } from 'react-hotkeys';
|
||||||
import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
|
||||||
import { autoUnfoldCW } from 'flavours/glitch/util/content_warning';
|
import { autoUnfoldCW } from 'flavours/glitch/util/content_warning';
|
||||||
|
import { textForScreenReader } from 'flavours/glitch/components/status';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
@ -48,6 +49,7 @@ const messages = defineMessages({
|
||||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
||||||
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
||||||
|
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -387,7 +389,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column label={intl.formatMessage(messages.detailedStatus)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
showBackButton
|
showBackButton
|
||||||
extraButton={(
|
extraButton={(
|
||||||
|
@ -400,7 +402,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}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
|
Loading…
Reference in New Issue