diff options
Diffstat (limited to 'app/javascript/flavours/glitch/components')
5 files changed, 62 insertions, 16 deletions
diff --git a/app/javascript/flavours/glitch/components/column.js b/app/javascript/flavours/glitch/components/column.js index 57c4c7a40..dc87818a5 100644 --- a/app/javascript/flavours/glitch/components/column.js +++ b/app/javascript/flavours/glitch/components/column.js @@ -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> ); diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index 1c2b0bf25..05611c135 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -23,6 +23,7 @@ class DropdownMenu extends React.PureComponent { placement: PropTypes.string, arrowOffsetLeft: PropTypes.string, arrowOffsetTop: PropTypes.string, + openedViaKeyboard: PropTypes.bool, }; static defaultProps = { @@ -42,13 +43,15 @@ class DropdownMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('keydown', this.handleKeyDown, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - if (this.focusedItem) this.focusedItem.focus(); + if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus(); this.setState({ mounted: true }); } componentWillUnmount () { document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('keydown', this.handleKeyDown, false); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } @@ -62,13 +65,10 @@ class DropdownMenu extends React.PureComponent { handleKeyDown = e => { const items = Array.from(this.node.getElementsByTagName('a')); - const index = items.indexOf(e.currentTarget); + const index = items.indexOf(document.activeElement); let element; switch(e.key) { - case 'Enter': - this.handleClick(e); - break; case 'ArrowDown': element = items[index+1]; if (element) { @@ -96,6 +96,12 @@ class DropdownMenu extends React.PureComponent { } } + handleItemKeyDown = e => { + if (e.key === 'Enter') { + this.handleClick(e); + } + } + handleClick = e => { const i = Number(e.currentTarget.getAttribute('data-index')); const { action, to } = this.props.items[i]; @@ -120,7 +126,7 @@ class DropdownMenu extends React.PureComponent { return ( <li className='dropdown-menu__item' key={`${text}-${i}`}> - <a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyDown={this.handleKeyDown} data-index={i}> + <a href={href} target='_blank' rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyDown={this.handleItemKeyDown} data-index={i}> {text} </a> </li> @@ -170,6 +176,7 @@ export default class Dropdown extends React.PureComponent { onClose: PropTypes.func.isRequired, dropdownPlacement: PropTypes.string, openDropdownId: PropTypes.number, + openedViaKeyboard: PropTypes.bool, }; static defaultProps = { @@ -180,14 +187,14 @@ export default class Dropdown extends React.PureComponent { id: id++, }; - handleClick = ({ target }) => { + handleClick = ({ target, type }) => { if (this.state.id === this.props.openDropdownId) { this.handleClose(); } else { const { top } = target.getBoundingClientRect(); const placement = top * 2 < innerHeight ? 'bottom' : 'top'; - this.props.onOpen(this.state.id, this.handleItemClick, placement); + this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click'); } } @@ -197,6 +204,11 @@ export default class Dropdown extends React.PureComponent { handleKeyDown = e => { switch(e.key) { + case ' ': + case 'Enter': + this.handleClick(e); + e.preventDefault(); + break; case 'Escape': this.handleClose(); break; @@ -232,7 +244,7 @@ export default class Dropdown extends React.PureComponent { } render () { - const { icon, items, size, ariaLabel, disabled, dropdownPlacement, openDropdownId } = this.props; + const { icon, items, size, ariaLabel, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard } = this.props; const open = this.state.id === openDropdownId; return ( @@ -248,7 +260,7 @@ export default class Dropdown extends React.PureComponent { /> <Overlay show={open} placement={dropdownPlacement} target={this.findTarget}> - <DropdownMenu items={items} onClose={this.handleClose} /> + <DropdownMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} /> </Overlay> </div> ); diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js index f7f6b0a53..6eeca5598 100644 --- a/app/javascript/flavours/glitch/components/intersection_observer_article.js +++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js @@ -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> ); diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 1de12c5e0..605a2862b 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -78,6 +78,11 @@ class Item extends React.PureComponent { e.stopPropagation(); } + handleMouseDown = (e) => { + e.preventDefault(); + e.stopPropagation(); + } + render () { const { attachment, index, size, standalone, letterbox, displayWidth } = this.props; @@ -181,6 +186,7 @@ class Item extends React.PureComponent { onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} + onMouseDown={this.handleMouseDown} autoPlay={autoPlay} loop muted diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index f3709f653..665aa457a 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -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> @@ -539,7 +566,7 @@ export default class Status extends ImmutablePureComponent { parseClick={parseClick} disabled={!router} /> - {!isCollapsed || !muted ? ( + {!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? ( <StatusActionBar {...other} status={status} |