Merge branch 'accessibility' into 'develop'
Accessibility improvements See merge request soapbox-pub/soapbox-fe!697
This commit is contained in:
commit
e652de227c
|
@ -9,8 +9,9 @@ export function openModal(type, props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeModal() {
|
export function closeModal(type) {
|
||||||
return {
|
return {
|
||||||
type: MODAL_CLOSE,
|
type: MODAL_CLOSE,
|
||||||
|
modalType: type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class Button extends React.PureComponent {
|
||||||
|
|
||||||
if (this.props.to) {
|
if (this.props.to) {
|
||||||
return (
|
return (
|
||||||
<Link to={this.props.to}>
|
<Link to={this.props.to} tabIndex={-1}>
|
||||||
{btn}
|
{btn}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
|
@ -45,7 +45,9 @@ class DropdownMenu extends React.PureComponent {
|
||||||
document.addEventListener('click', this.handleDocumentClick, false);
|
document.addEventListener('click', this.handleDocumentClick, false);
|
||||||
document.addEventListener('keydown', this.handleKeyDown, false);
|
document.addEventListener('keydown', this.handleKeyDown, false);
|
||||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||||
if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus();
|
if (this.focusedItem && this.props.openedViaKeyboard) {
|
||||||
|
this.focusedItem.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
this.setState({ mounted: true });
|
this.setState({ mounted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,38 +68,42 @@ class DropdownMenu extends React.PureComponent {
|
||||||
handleKeyDown = e => {
|
handleKeyDown = e => {
|
||||||
const items = Array.from(this.node.getElementsByTagName('a'));
|
const items = Array.from(this.node.getElementsByTagName('a'));
|
||||||
const index = items.indexOf(document.activeElement);
|
const index = items.indexOf(document.activeElement);
|
||||||
let element;
|
let element = null;
|
||||||
|
|
||||||
switch(e.key) {
|
switch(e.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
element = items[index+1];
|
element = items[index+1] || items[0];
|
||||||
if (element) {
|
|
||||||
element.focus();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
element = items[index-1];
|
element = items[index-1] || items[items.length-1];
|
||||||
if (element) {
|
break;
|
||||||
element.focus();
|
case 'Tab':
|
||||||
|
if (e.shiftKey) {
|
||||||
|
element = items[index-1] || items[items.length-1];
|
||||||
|
} else {
|
||||||
|
element = items[index+1] || items[0];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Home':
|
case 'Home':
|
||||||
element = items[0];
|
element = items[0];
|
||||||
if (element) {
|
|
||||||
element.focus();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'End':
|
case 'End':
|
||||||
element = items[items.length-1];
|
element = items[items.length-1];
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
this.props.onClose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
e.preventDefault();
|
||||||
break;
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemKeyDown = e => {
|
handleItemKeyPress = e => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
this.handleClick(e);
|
this.handleClick(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +157,7 @@ class DropdownMenu extends React.PureComponent {
|
||||||
ref={i === 0 ? this.setFocusRef : null}
|
ref={i === 0 ? this.setFocusRef : null}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onAuxClick={this.handleAuxClick}
|
onAuxClick={this.handleAuxClick}
|
||||||
onKeyDown={this.handleItemKeyDown}
|
onKeyPress={this.handleItemKeyPress}
|
||||||
data-index={i}
|
data-index={i}
|
||||||
target={newTab ? '_blank' : null}
|
target={newTab ? '_blank' : null}
|
||||||
data-method={isLogout ? 'delete' : null}
|
data-method={isLogout ? 'delete' : null}
|
||||||
|
@ -226,19 +232,36 @@ export default class Dropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
|
if (this.activeElement) {
|
||||||
|
this.activeElement.focus();
|
||||||
|
this.activeElement = null;
|
||||||
|
}
|
||||||
this.props.onClose(this.state.id);
|
this.props.onClose(this.state.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = e => {
|
handleMouseDown = () => {
|
||||||
|
if (!this.state.open) {
|
||||||
|
this.activeElement = document.activeElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleButtonKeyDown = (e) => {
|
||||||
|
switch(e.key) {
|
||||||
|
case ' ':
|
||||||
|
case 'Enter':
|
||||||
|
this.handleMouseDown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
switch(e.key) {
|
switch(e.key) {
|
||||||
case ' ':
|
case ' ':
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
this.handleClick(e);
|
this.handleClick(e);
|
||||||
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
|
||||||
this.handleClose();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +299,7 @@ export default class Dropdown extends React.PureComponent {
|
||||||
const open = this.state.id === openDropdownId;
|
const open = this.state.id === openDropdownId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onKeyDown={this.handleKeyDown}>
|
<div>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={icon}
|
icon={icon}
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -285,6 +308,9 @@ export default class Dropdown extends React.PureComponent {
|
||||||
size={size}
|
size={size}
|
||||||
ref={this.setTargetRef}
|
ref={this.setTargetRef}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onKeyDown={this.handleButtonKeyDown}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
||||||
|
|
|
@ -13,8 +13,10 @@ export default class IconButton extends React.PureComponent {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
onMouseDown: PropTypes.func,
|
||||||
onKeyUp: PropTypes.func,
|
onKeyUp: PropTypes.func,
|
||||||
onKeyDown: PropTypes.func,
|
onKeyDown: PropTypes.func,
|
||||||
|
onKeyPress: PropTypes.func,
|
||||||
onMouseEnter: PropTypes.func,
|
onMouseEnter: PropTypes.func,
|
||||||
onMouseLeave: PropTypes.func,
|
onMouseLeave: PropTypes.func,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
@ -54,6 +56,30 @@ export default class IconButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseDown = (e) => {
|
||||||
|
if (!this.props.disabled && this.props.onMouseDown) {
|
||||||
|
this.props.onMouseDown(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (!this.props.disabled && this.props.onKeyDown) {
|
||||||
|
this.props.onKeyDown(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp = (e) => {
|
||||||
|
if (!this.props.disabled && this.props.onKeyUp) {
|
||||||
|
this.props.onKeyUp(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (this.props.onKeyPress && !this.props.disabled) {
|
||||||
|
this.props.onKeyPress(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const style = {
|
const style = {
|
||||||
fontSize: `${this.props.size}px`,
|
fontSize: `${this.props.size}px`,
|
||||||
|
@ -98,8 +124,10 @@ export default class IconButton extends React.PureComponent {
|
||||||
title={title}
|
title={title}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onKeyUp={this.props.onKeyUp}
|
onMouseDown={this.handleMouseDown}
|
||||||
onKeyDown={this.props.onKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
|
onKeyUp={this.handleKeyUp}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
onMouseEnter={this.props.onMouseEnter}
|
onMouseEnter={this.props.onMouseEnter}
|
||||||
onMouseLeave={this.props.onMouseLeave}
|
onMouseLeave={this.props.onMouseLeave}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
@ -125,8 +153,10 @@ export default class IconButton extends React.PureComponent {
|
||||||
title={title}
|
title={title}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onKeyUp={this.props.onKeyUp}
|
onMouseDown={this.handleMouseDown}
|
||||||
onKeyDown={this.props.onKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
|
onKeyUp={this.handleKeyUp}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
onMouseEnter={this.props.onMouseEnter}
|
onMouseEnter={this.props.onMouseEnter}
|
||||||
onMouseLeave={this.props.onMouseLeave}
|
onMouseLeave={this.props.onMouseLeave}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import 'wicg-inert';
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { openModal } from '../actions/modal';
|
import { openModal } from '../actions/modal';
|
||||||
|
@ -74,8 +75,31 @@ class ModalRoot extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
const focusable = Array.from(this.node.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((x) => window.getComputedStyle(x).display !== 'none');
|
||||||
|
const index = focusable.indexOf(e.target);
|
||||||
|
|
||||||
|
let element;
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
element = focusable[index - 1] || focusable[focusable.length - 1];
|
||||||
|
} else {
|
||||||
|
element = focusable[index + 1] || focusable[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.focus();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -101,6 +125,7 @@ class ModalRoot extends React.PureComponent {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('keyup', this.handleKeyUp);
|
window.removeEventListener('keyup', this.handleKeyUp);
|
||||||
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSiblings = () => {
|
getSiblings = () => {
|
||||||
|
|
|
@ -213,6 +213,21 @@ class Status extends ImmutablePureComponent {
|
||||||
this.props.OnOpenAudio(media, startTime);
|
this.props.OnOpenAudio(media, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleHotkeyOpenMedia = e => {
|
||||||
|
const { onOpenMedia, onOpenVideo } = this.props;
|
||||||
|
const status = this._properStatus();
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (status.get('media_attachments').size > 0) {
|
||||||
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
|
onOpenVideo(status.getIn(['media_attachments', 0]), 0);
|
||||||
|
} else {
|
||||||
|
onOpenMedia(status.get('media_attachments'), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleHotkeyReply = e => {
|
handleHotkeyReply = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onReply(this._properStatus(), this.context.router.history);
|
this.props.onReply(this._properStatus(), this.context.router.history);
|
||||||
|
@ -461,6 +476,7 @@ class Status extends ImmutablePureComponent {
|
||||||
moveDown: this.handleHotkeyMoveDown,
|
moveDown: this.handleHotkeyMoveDown,
|
||||||
toggleHidden: this.handleHotkeyToggleHidden,
|
toggleHidden: this.handleHotkeyToggleHidden,
|
||||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||||
|
openMedia: this.handleHotkeyOpenMedia,
|
||||||
react: this.handleHotkeyReact,
|
react: this.handleHotkeyReact,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
|
||||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
|
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
|
||||||
},
|
},
|
||||||
onClose(id) {
|
onClose(id) {
|
||||||
dispatch(closeModal());
|
dispatch(closeModal('ACTIONS'));
|
||||||
dispatch(closeDropdownMenu(id));
|
dispatch(closeDropdownMenu(id));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,8 @@ import UI from '../features/ui';
|
||||||
// import Introduction from '../features/introduction';
|
// import Introduction from '../features/introduction';
|
||||||
import { preload } from '../actions/preload';
|
import { preload } from '../actions/preload';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { previewState as previewMediaState } from 'soapbox/features/ui/components/media_modal';
|
||||||
|
import { previewState as previewVideoState } from 'soapbox/features/ui/components/video_modal';
|
||||||
import ErrorBoundary from '../components/error_boundary';
|
import ErrorBoundary from '../components/error_boundary';
|
||||||
import { fetchInstance } from 'soapbox/actions/instance';
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
import { fetchSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { fetchSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
@ -104,6 +106,10 @@ class SoapboxMount extends React.PureComponent {
|
||||||
this.maybeUpdateMessages(prevProps);
|
this.maybeUpdateMessages(prevProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldUpdateScroll(_, { location }) {
|
||||||
|
return location.state !== previewMediaState && location.state !== previewVideoState;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { me, themeCss, locale, customCss } = this.props;
|
const { me, themeCss, locale, customCss } = this.props;
|
||||||
if (me === null) return null;
|
if (me === null) return null;
|
||||||
|
@ -137,7 +143,7 @@ class SoapboxMount extends React.PureComponent {
|
||||||
<meta name='theme-color' content={this.props.brandColor} />
|
<meta name='theme-color' content={this.props.brandColor} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ScrollContext>
|
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||||
<Switch>
|
<Switch>
|
||||||
{!me && <Route exact path='/' component={PublicLayout} />}
|
{!me && <Route exact path='/' component={PublicLayout} />}
|
||||||
<Route exact path='/about/:slug?' component={PublicLayout} />
|
<Route exact path='/about/:slug?' component={PublicLayout} />
|
||||||
|
|
|
@ -314,10 +314,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='emoji-picker-wrapper'>
|
|
||||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AutosuggestTextarea
|
<AutosuggestTextarea
|
||||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
|
@ -333,6 +329,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
autoFocus={shouldAutoFocus}
|
autoFocus={shouldAutoFocus}
|
||||||
>
|
>
|
||||||
|
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||||
{
|
{
|
||||||
!condensed &&
|
!condensed &&
|
||||||
<div className='compose-form__modifiers'>
|
<div className='compose-form__modifiers'>
|
||||||
|
|
|
@ -50,7 +50,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||||
const index = items.findIndex(item => {
|
const index = items.findIndex(item => {
|
||||||
return (item.value === value);
|
return (item.value === value);
|
||||||
});
|
});
|
||||||
let element;
|
let element = null;
|
||||||
|
|
||||||
switch(e.key) {
|
switch(e.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
|
@ -60,33 +60,31 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||||
this.handleClick(e);
|
this.handleClick(e);
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
element = this.node.childNodes[index + 1];
|
element = this.node.childNodes[index + 1] || this.node.firstChild;
|
||||||
if (element) {
|
|
||||||
element.focus();
|
|
||||||
this.props.onChange(element.getAttribute('data-index'));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
element = this.node.childNodes[index - 1];
|
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||||
if (element) {
|
break;
|
||||||
element.focus();
|
case 'Tab':
|
||||||
this.props.onChange(element.getAttribute('data-index'));
|
if (e.shiftKey) {
|
||||||
|
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||||
|
} else {
|
||||||
|
element = this.node.childNodes[index + 1] || this.node.firstChild;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Home':
|
case 'Home':
|
||||||
element = this.node.firstChild;
|
element = this.node.firstChild;
|
||||||
if (element) {
|
|
||||||
element.focus();
|
|
||||||
this.props.onChange(element.getAttribute('data-index'));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'End':
|
case 'End':
|
||||||
element = this.node.lastChild;
|
element = this.node.lastChild;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus();
|
element.focus();
|
||||||
this.props.onChange(element.getAttribute('data-index'));
|
this.props.onChange(element.getAttribute('data-index'));
|
||||||
}
|
e.preventDefault();
|
||||||
break;
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('click', this.handleDocumentClick, false);
|
document.addEventListener('click', this.handleDocumentClick, false);
|
||||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||||
if (this.focusedItem) this.focusedItem.focus();
|
if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
|
||||||
this.setState({ mounted: true });
|
this.setState({ mounted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +190,9 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { top } = target.getBoundingClientRect();
|
const { top } = target.getBoundingClientRect();
|
||||||
|
if (this.state.open && this.activeElement) {
|
||||||
|
this.activeElement.focus();
|
||||||
|
}
|
||||||
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
|
||||||
this.setState({ open: !this.state.open });
|
this.setState({ open: !this.state.open });
|
||||||
}
|
}
|
||||||
|
@ -214,7 +215,25 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseDown = () => {
|
||||||
|
if (!this.state.open) {
|
||||||
|
this.activeElement = document.activeElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleButtonKeyDown = (e) => {
|
||||||
|
switch(e.key) {
|
||||||
|
case ' ':
|
||||||
|
case 'Enter':
|
||||||
|
this.handleMouseDown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
|
if (this.state.open && this.activeElement) {
|
||||||
|
this.activeElement.focus();
|
||||||
|
}
|
||||||
this.setState({ open: false });
|
this.setState({ open: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +259,8 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
active={open}
|
active={open}
|
||||||
inverted
|
inverted
|
||||||
onClick={this.handleToggle}
|
onClick={this.handleToggle}
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onKeyDown={this.handleButtonKeyDown}
|
||||||
style={{ height: null, lineHeight: '27px' }}
|
style={{ height: null, lineHeight: '27px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -263,6 +263,21 @@ class Status extends ImmutablePureComponent {
|
||||||
this.props.dispatch(openModal('VIDEO', { media, time }));
|
this.props.dispatch(openModal('VIDEO', { media, time }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleHotkeyOpenMedia = e => {
|
||||||
|
const { onOpenMedia, onOpenVideo } = this.props;
|
||||||
|
const status = this._properStatus();
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (status.get('media_attachments').size > 0) {
|
||||||
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
|
onOpenVideo(status.getIn(['media_attachments', 0]), 0);
|
||||||
|
} else {
|
||||||
|
onOpenMedia(status.get('media_attachments'), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleMuteClick = (account) => {
|
handleMuteClick = (account) => {
|
||||||
this.props.dispatch(initMuteModal(account));
|
this.props.dispatch(initMuteModal(account));
|
||||||
}
|
}
|
||||||
|
@ -548,6 +563,7 @@ class Status extends ImmutablePureComponent {
|
||||||
openProfile: this.handleHotkeyOpenProfile,
|
openProfile: this.handleHotkeyOpenProfile,
|
||||||
toggleHidden: this.handleHotkeyToggleHidden,
|
toggleHidden: this.handleHotkeyToggleHidden,
|
||||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||||
|
openMedia: this.handleHotkeyOpenMedia,
|
||||||
react: this.handleHotkeyReact,
|
react: this.handleHotkeyReact,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -62,12 +62,8 @@ class HotkeysModal extends ImmutablePureComponent {
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open post' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open post' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>x</kbd></td>
|
<td><kbd>a</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.open_media' defaultMessage='to open media' /></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><kbd>h</kbd></td>
|
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -78,6 +74,14 @@ class HotkeysModal extends ImmutablePureComponent {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>x</kbd></td>
|
||||||
|
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>h</kbd></td>
|
||||||
|
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>up</kbd>, <kbd>k</kbd></td>
|
<td><kbd>up</kbd>, <kbd>k</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
|
||||||
|
@ -106,10 +110,6 @@ class HotkeysModal extends ImmutablePureComponent {
|
||||||
<td><kbd>esc</kbd></td>
|
<td><kbd>esc</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><kbd>g</kbd> + <kbd>h</kbd></td>
|
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<table>
|
<table>
|
||||||
|
@ -119,6 +119,10 @@ class HotkeysModal extends ImmutablePureComponent {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>g</kbd> + <kbd>h</kbd></td>
|
||||||
|
<td><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>g</kbd> + <kbd>n</kbd></td>
|
<td><kbd>g</kbd> + <kbd>n</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
|
||||||
|
|
|
@ -155,6 +155,7 @@ const keyMap = {
|
||||||
goToRequests: 'g r',
|
goToRequests: 'g r',
|
||||||
toggleHidden: 'x',
|
toggleHidden: 'x',
|
||||||
toggleSensitive: 'h',
|
toggleSensitive: 'h',
|
||||||
|
openMedia: 'a',
|
||||||
};
|
};
|
||||||
|
|
||||||
class SwitchingColumnsArea extends React.PureComponent {
|
class SwitchingColumnsArea extends React.PureComponent {
|
||||||
|
|
|
@ -461,7 +461,7 @@ class Video extends React.PureComponent {
|
||||||
|
|
||||||
<div className='video-player__buttons-bar'>
|
<div className='video-player__buttons-bar'>
|
||||||
<div className='video-player__buttons left'>
|
<div className='video-player__buttons left'>
|
||||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default function modal(state = initialState, action) {
|
||||||
case MODAL_OPEN:
|
case MODAL_OPEN:
|
||||||
return { modalType: action.modalType, modalProps: action.modalProps };
|
return { modalType: action.modalType, modalProps: action.modalProps };
|
||||||
case MODAL_CLOSE:
|
case MODAL_CLOSE:
|
||||||
return initialState;
|
return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
&:focus,
|
||||||
&.active {
|
&.active {
|
||||||
border-bottom: 2px solid var(--primary-text-color);
|
border-bottom: 2px solid var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -480,7 +480,8 @@
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color--faint);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
color: hsla(var(--primary-text-color_hsl), 0.8);
|
color: hsla(var(--primary-text-color_hsl), 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,9 +62,8 @@
|
||||||
|
|
||||||
.emoji-picker-dropdown {
|
.emoji-picker-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 10px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__autosuggest-wrapper {
|
.compose-form__autosuggest-wrapper {
|
||||||
|
@ -141,7 +140,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-wrapper,
|
|
||||||
.autosuggest-textarea__suggestions-wrapper {
|
.autosuggest-textarea__suggestions-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color);
|
||||||
transition: color 200ms ease-out;
|
transition: color 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,10 +148,6 @@
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.emoji-picker-wrapper {
|
|
||||||
.emoji-picker-dropdown { top: 10px; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.compose-form {
|
.compose-form {
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
padding: 0 0 0 20px !important;
|
padding: 0 0 0 20px !important;
|
||||||
|
|
|
@ -146,7 +146,8 @@
|
||||||
"webpack-bundle-analyzer": "^4.0.0",
|
"webpack-bundle-analyzer": "^4.0.0",
|
||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-merge": "^5.2.0",
|
"webpack-merge": "^5.2.0",
|
||||||
"websocket.js": "^0.1.12"
|
"websocket.js": "^0.1.12",
|
||||||
|
"wicg-inert": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios-mock-adapter": "^1.18.1",
|
"axios-mock-adapter": "^1.18.1",
|
||||||
|
|
|
@ -13062,6 +13062,11 @@ which@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
wicg-inert@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.1.1.tgz#b033fd4fbfb9e3fd709e5d84becbdf2e06e5c229"
|
||||||
|
integrity sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A==
|
||||||
|
|
||||||
wide-align@^1.1.0:
|
wide-align@^1.1.0:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||||
|
|
Loading…
Reference in New Issue