Merge pull request #681 from ThibG/glitch-soc/fixes/accessibility
Port various accessibility improvements from upstream
This commit is contained in:
		| @@ -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} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user