Merge tag 'v3.1.1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2020-02-21 14:21:59 +01:00
1431 changed files with 34462 additions and 10030 deletions

View File

@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import IconButton from '../../../components/icon_button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
@ -17,6 +18,8 @@ const messages = defineMessages({
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
more: { id: 'status.more', defaultMessage: 'More' },
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
@ -29,9 +32,18 @@ const messages = defineMessages({
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
});
export default @injectIntl
const mapStateToProps = (state, { status }) => ({
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
});
export default @connect(mapStateToProps)
@injectIntl
class ActionBar extends React.PureComponent {
static contextTypes = {
@ -40,15 +52,21 @@ class ActionBar extends React.PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.map,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onBookmark: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onMute: PropTypes.func,
onMuteConversation: PropTypes.func,
onUnmute: PropTypes.func,
onBlock: PropTypes.func,
onUnblock: PropTypes.func,
onBlockDomain: PropTypes.func,
onUnblockDomain: PropTypes.func,
onMuteConversation: PropTypes.func,
onReport: PropTypes.func,
onPin: PropTypes.func,
onEmbed: PropTypes.func,
@ -67,6 +85,10 @@ class ActionBar extends React.PureComponent {
this.props.onFavourite(this.props.status);
}
handleBookmarkClick = (e) => {
this.props.onBookmark(this.props.status, e);
}
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
@ -84,17 +106,45 @@ class ActionBar extends React.PureComponent {
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
const { status, relationship, onMute, onUnmute } = this.props;
const account = status.get('account');
if (relationship && relationship.get('muting')) {
onUnmute(account);
} else {
onMute(account);
}
}
handleBlockClick = () => {
const { status, relationship, onBlock, onUnblock } = this.props;
const account = status.get('account');
if (relationship && relationship.get('blocking')) {
onUnblock(account);
} else {
onBlock(status);
}
}
handleBlockDomain = () => {
const { status, onBlockDomain } = this.props;
const account = status.get('account');
onBlockDomain(account.get('acct').split('@')[1]);
}
handleUnblockDomain = () => {
const { status, onUnblockDomain } = this.props;
const account = status.get('account');
onUnblockDomain(account.get('acct').split('@')[1]);
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
handleBlockClick = () => {
this.props.onBlock(this.props.status);
}
handleReport = () => {
this.props.onReport(this.props.status);
}
@ -134,10 +184,11 @@ class ActionBar extends React.PureComponent {
}
render () {
const { status, intl } = this.props;
const { status, relationship, intl } = this.props;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const mutingConversation = status.get('muted');
const account = status.get('account');
let menu = [];
@ -165,9 +216,33 @@ class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
if (relationship && relationship.get('muting')) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick });
}
if (relationship && relationship.get('blocking')) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
} else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick });
}
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
menu.push(null);
if (relationship && relationship.get('domain_blocking')) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
} else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain });
}
}
if (isStaff) {
menu.push(null);
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
@ -175,7 +250,7 @@ class ActionBar extends React.PureComponent {
}
}
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
const shareButton = ('share' in navigator) && publicStatus && (
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
);
@ -190,17 +265,16 @@ class ActionBar extends React.PureComponent {
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
return (
<div className='detailed-status__action-bar'>
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton disabled={!publicStatus} active={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
{shareButton}
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
<div className='detailed-status__action-bar-dropdown'>
<DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' title='More' />
<DropdownMenuContainer size={18} icon='ellipsis-h' status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
</div>
</div>
);

View File

@ -148,7 +148,7 @@ export default class Card extends React.PureComponent {
const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
const interactive = card.get('type') !== 'link';
const className = classnames('status-card', { horizontal, compact, interactive });
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const ratio = card.get('width') / card.get('height');
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
@ -180,7 +180,7 @@ export default class Card extends React.PureComponent {
<div className='status-card__actions'>
<div>
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><Icon id='external-link' /></a>}
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
</div>
</div>
</div>
@ -208,7 +208,7 @@ export default class Card extends React.PureComponent {
}
return (
<a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
<a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
{embed}
{description}
</a>

View File

@ -6,7 +6,7 @@ import DisplayName from '../../../components/display_name';
import StatusContent from '../../../components/status_content';
import MediaGallery from '../../../components/media_gallery';
import { Link } from 'react-router-dom';
import { defineMessages, injectIntl, FormattedDate, FormattedNumber } from 'react-intl';
import { defineMessages, injectIntl, FormattedDate } from 'react-intl';
import Card from './card';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from '../../video';
@ -14,6 +14,7 @@ import Audio from '../../audio';
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import AnimatedNumber from 'mastodon/components/animated_number';
const messages = defineMessages({
local_only: { id: 'status.local_only', defaultMessage: 'This post is only visible by other users of your instance' },
@ -163,7 +164,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
}
if (status.get('application')) {
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></span>;
}
if (status.get('visibility') === 'direct') {
@ -179,7 +180,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
<Icon id={reblogIcon} />
<span className='detailed-status__reblogs'>
<FormattedNumber value={status.get('reblogs_count')} />
<AnimatedNumber value={status.get('reblogs_count')} />
</span>
</Link>
);
@ -188,7 +189,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
<Icon id={reblogIcon} />
<span className='detailed-status__reblogs'>
<FormattedNumber value={status.get('reblogs_count')} />
<AnimatedNumber value={status.get('reblogs_count')} />
</span>
</a>
);
@ -203,7 +204,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
<Icon id='star' />
<span className='detailed-status__favorites'>
<FormattedNumber value={status.get('favourites_count')} />
<AnimatedNumber value={status.get('favourites_count')} />
</span>
</Link>
);
@ -212,7 +213,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
<Icon id='star' />
<span className='detailed-status__favorites'>
<FormattedNumber value={status.get('favourites_count')} />
<AnimatedNumber value={status.get('favourites_count')} />
</span>
</a>
);
@ -231,7 +232,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
{media}
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{applicationLink} · {reblogLink} · {favouriteLink}{localOnly}
</div>