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