Merge tag 'v2.9.3' into hometown-2.9.3
This commit is contained in:
@ -12,6 +12,7 @@ export default class Button extends React.PureComponent {
|
||||
secondary: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
@ -54,6 +55,7 @@ export default class Button extends React.PureComponent {
|
||||
onClick={this.handleClick}
|
||||
ref={this.setRef}
|
||||
style={style}
|
||||
title={this.props.title}
|
||||
>
|
||||
{this.props.text || this.props.children}
|
||||
</button>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
export default class DisplayName extends React.PureComponent {
|
||||
|
||||
@ -10,6 +11,47 @@ export default class DisplayName extends React.PureComponent {
|
||||
localDomain: PropTypes.string,
|
||||
};
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { others, localDomain } = this.props;
|
||||
|
||||
@ -39,7 +81,7 @@ export default class DisplayName extends React.PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className='display-name'>
|
||||
<span className='display-name' ref={this.setRef}>
|
||||
{displayName} {suffix}
|
||||
</span>
|
||||
);
|
||||
|
@ -45,7 +45,9 @@ class DropdownMenu extends React.PureComponent {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('keydown', this.handleKeyDown, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus();
|
||||
if (this.focusedItem && this.props.openedViaKeyboard) {
|
||||
this.focusedItem.focus();
|
||||
}
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
@ -81,6 +83,18 @@ class DropdownMenu extends React.PureComponent {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = items[index-1] || items[items.length-1];
|
||||
} else {
|
||||
element = items[index+1] || items[0];
|
||||
}
|
||||
if (element) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = items[0];
|
||||
if (element) {
|
||||
@ -93,11 +107,14 @@ class DropdownMenu extends React.PureComponent {
|
||||
element.focus();
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleItemKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
handleItemKeyPress = e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
this.handleClick(e);
|
||||
}
|
||||
}
|
||||
@ -122,11 +139,11 @@ class DropdownMenu extends React.PureComponent {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { text, href = '#' } = option;
|
||||
const { text, href = '#', target = '_blank', method } = option;
|
||||
|
||||
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.handleItemKeyDown} data-index={i}>
|
||||
<a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
@ -193,25 +210,41 @@ export default class Dropdown extends React.PureComponent {
|
||||
} else {
|
||||
const { top } = target.getBoundingClientRect();
|
||||
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
|
||||
|
||||
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
|
||||
}
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
if (this.activeElement) {
|
||||
this.activeElement.focus();
|
||||
this.activeElement = null;
|
||||
}
|
||||
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) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +282,7 @@ export default class Dropdown extends React.PureComponent {
|
||||
const open = this.state.id === openDropdownId;
|
||||
|
||||
return (
|
||||
<div onKeyDown={this.handleKeyDown}>
|
||||
<div>
|
||||
<IconButton
|
||||
icon={icon}
|
||||
title={title}
|
||||
@ -258,6 +291,9 @@ export default class Dropdown extends React.PureComponent {
|
||||
size={size}
|
||||
ref={this.setTargetRef}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleButtonKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
|
||||
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
||||
|
@ -12,6 +12,9 @@ export default class IconButton extends React.PureComponent {
|
||||
title: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
onMouseDown: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
onKeyPress: PropTypes.func,
|
||||
size: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
pressed: PropTypes.bool,
|
||||
@ -42,6 +45,24 @@ export default class IconButton extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (this.props.onKeyPress && !this.props.disabled) {
|
||||
this.props.onKeyPress(e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const style = {
|
||||
fontSize: `${this.props.size}px`,
|
||||
@ -84,6 +105,9 @@ export default class IconButton extends React.PureComponent {
|
||||
title={title}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
@ -103,6 +127,9 @@ export default class IconButton extends React.PureComponent {
|
||||
title={title}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
|
@ -21,8 +21,30 @@ export default 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 () {
|
||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@ -52,6 +74,7 @@ export default class ModalRoot extends React.PureComponent {
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keyup', this.handleKeyUp);
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
getSiblings = () => {
|
||||
|
@ -7,6 +7,7 @@ import Permalink from './permalink';
|
||||
import classnames from 'classnames';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
||||
|
||||
@ -71,12 +72,35 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_updateStatusEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
@ -95,6 +119,14 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
this.startXY = [e.clientX, e.clientY];
|
||||
}
|
||||
@ -133,11 +165,6 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleCollapsedClick = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ collapsed: !this.state.collapsed });
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
@ -213,45 +240,26 @@ export default class StatusContent extends React.PureComponent {
|
||||
return output;
|
||||
} else if (this.props.onClick) {
|
||||
const output = [
|
||||
<div
|
||||
ref={this.setRef}
|
||||
tabIndex='0'
|
||||
key='content'
|
||||
className={classNames}
|
||||
style={directionStyle}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.get('language')}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
/>,
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
</div>,
|
||||
];
|
||||
|
||||
if (this.state.collapsed) {
|
||||
output.push(readMoreButton);
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
output.push(<PollContainer pollId={status.get('poll')} />);
|
||||
}
|
||||
|
||||
return output;
|
||||
} else {
|
||||
const output = [
|
||||
<div
|
||||
tabIndex='0'
|
||||
ref={this.setRef}
|
||||
className='status__content'
|
||||
style={directionStyle}
|
||||
dangerouslySetInnerHTML={content}
|
||||
lang={status.get('language')}
|
||||
/>,
|
||||
];
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
|
||||
if (status.get('poll')) {
|
||||
output.push(<PollContainer pollId={status.get('poll')} />);
|
||||
}
|
||||
|
||||
return output;
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user