diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 4fa2c1158..bd3bb16bb 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -1,5 +1,6 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import IconButton from './icon_button';
import DropdownMenuContainer from '../containers/dropdown_menu_container';
@@ -24,6 +25,7 @@ const messages = defineMessages({
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
+ removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
@@ -34,6 +36,10 @@ 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}' },
});
const obfuscatedCount = count => {
@@ -46,7 +52,12 @@ const obfuscatedCount = count => {
}
};
-export default @injectIntl
+const mapStateToProps = (state, { status }) => ({
+ relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
@@ -55,6 +66,7 @@ class StatusActionBar extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
+ relationship: ImmutablePropTypes.map,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
@@ -62,7 +74,11 @@ class StatusActionBar extends ImmutablePureComponent {
onDirect: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
+ onUnmute: PropTypes.func,
onBlock: PropTypes.func,
+ onUnblock: PropTypes.func,
+ onBlockDomain: PropTypes.func,
+ onUnblockDomain: PropTypes.func,
onReport: PropTypes.func,
onEmbed: PropTypes.func,
onMuteConversation: PropTypes.func,
@@ -76,6 +92,7 @@ class StatusActionBar extends ImmutablePureComponent {
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
+ 'relationship',
'withDismiss',
]
@@ -141,11 +158,39 @@ class StatusActionBar extends ImmutablePureComponent {
}
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 = () => {
- this.props.onBlock(this.props.status);
+ const { status, relationship, onBlock, onUnblock } = this.props;
+ const account = status.get('account');
+
+ if (relationship && relationship.get('blocking')) {
+ onBlock(status);
+ } else {
+ onUnblock(account);
+ }
+ }
+
+ 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]);
}
handleOpen = () => {
@@ -184,11 +229,12 @@ class StatusActionBar extends ImmutablePureComponent {
}
render () {
- const { status, intl, withDismiss } = this.props;
+ const { status, relationship, intl, withDismiss } = this.props;
const mutingConversation = status.get('muted');
const anonymousAccess = !me;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
+ const account = status.get('account');
let menu = [];
let reblogIcon = 'retweet';
@@ -202,6 +248,7 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}
+ menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
menu.push(null);
if (status.getIn(['account', 'id']) === me || withDismiss) {
@@ -221,16 +268,39 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
} else {
- 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({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
+ menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('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 });
- menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
+
+ 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: account.get('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'])}` });
+ menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
}
}
@@ -259,7 +329,6 @@ class StatusActionBar extends ImmutablePureComponent {
{shareButton}
-
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index f79b19202..1427f8528 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -1,4 +1,5 @@
import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
+import { fetchRelationships } from 'mastodon/actions/accounts';
import { openModal, closeModal } from '../actions/modal';
import { connect } from 'react-redux';
import DropdownMenu from '../components/dropdown_menu';
@@ -13,12 +14,15 @@ const mapStateToProps = state => ({
const mapDispatchToProps = (dispatch, { status, items }) => ({
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
+ dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
+
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
},
+
onClose(id) {
dispatch(closeModal('ACTIONS'));
dispatch(closeDropdownMenu(id));
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index 16ba02e12..35c16a20c 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -1,3 +1,4 @@
+import React from 'react';
import { connect } from 'react-redux';
import Status from '../components/status';
import { makeGetStatus } from '../selectors';
@@ -23,11 +24,19 @@ import {
hideStatus,
revealStatus,
} from '../actions/statuses';
+import {
+ unmuteAccount,
+ unblockAccount,
+} from '../actions/accounts';
+import {
+ blockDomain,
+ unblockDomain,
+} from '../actions/domain_blocks';
import { initMuteModal } from '../actions/mutes';
import { initBlockModal } from '../actions/blocks';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { boostModal, deleteModal } from '../initial_state';
import { showAlertForError } from '../actions/alerts';
@@ -38,6 +47,7 @@ const messages = defineMessages({
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
+ blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
const makeMapStateToProps = () => {
@@ -148,6 +158,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(initBlockModal(account));
},
+ onUnblock (account) {
+ dispatch(unblockAccount(account.get('id')));
+ },
+
onReport (status) {
dispatch(initReport(status.get('account'), status));
},
@@ -156,6 +170,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(initMuteModal(account));
},
+ onUnmute (account) {
+ dispatch(unmuteAccount(account.get('id')));
+ },
+
onMuteConversation (status) {
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')));
@@ -172,6 +190,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
+ onBlockDomain (domain) {
+ dispatch(openModal('CONFIRM', {
+ message:
{domain} }} />,
+ confirm: intl.formatMessage(messages.blockDomainConfirm),
+ onConfirm: () => dispatch(blockDomain(domain)),
+ }));
+ },
+
+ onUnblockDomain (domain) {
+ dispatch(unblockDomain(domain));
+ },
+
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 1b81cd245..76334de69 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -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';
@@ -30,9 +31,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 = {
@@ -41,6 +51,7 @@ 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,
@@ -49,8 +60,12 @@ class ActionBar extends React.PureComponent {
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,
@@ -90,17 +105,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')) {
+ onBlock(status);
+ } else {
+ onUnblock(account);
+ }
+ }
+
+ 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);
}
@@ -140,10 +183,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 = [];
@@ -171,9 +215,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'])}` });
@@ -207,7 +275,7 @@ class ActionBar extends React.PureComponent {
-
+
);
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 9fb3fe305..55bd99886 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -32,6 +32,14 @@ import {
hideStatus,
revealStatus,
} from '../../actions/statuses';
+import {
+ unblockAccount,
+ unmuteAccount,
+} from '../../actions/accounts';
+import {
+ blockDomain,
+ unblockDomain,
+} from '../../actions/domain_blocks';
import { initMuteModal } from '../../actions/mutes';
import { initBlockModal } from '../../actions/blocks';
import { initReport } from '../../actions/reports';
@@ -41,7 +49,7 @@ import ColumnBackButton from '../../components/column_back_button';
import ColumnHeader from '../../components/column_header';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
@@ -59,6 +67,7 @@ const messages = defineMessages({
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
+ blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
const makeMapStateToProps = () => {
@@ -317,6 +326,27 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
}
+ handleUnmuteClick = account => {
+ this.props.dispatch(unmuteAccount(account.get('id')));
+ }
+
+ handleUnblockClick = account => {
+ this.props.dispatch(unblockAccount(account.get('id')));
+ }
+
+ handleBlockDomainClick = domain => {
+ this.props.dispatch(openModal('CONFIRM', {
+ message: {domain} }} />,
+ confirm: this.props.intl.formatMessage(messages.blockDomainConfirm),
+ onConfirm: () => this.props.dispatch(blockDomain(domain)),
+ }));
+ }
+
+ handleUnblockDomainClick = domain => {
+ this.props.dispatch(unblockDomain(domain));
+ }
+
+
handleHotkeyMoveUp = () => {
this.handleMoveUp(this.props.status.get('id'));
}
@@ -514,8 +544,12 @@ class Status extends ImmutablePureComponent {
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}
onMute={this.handleMuteClick}
+ onUnmute={this.handleUnmuteClick}
onMuteConversation={this.handleConversationMuteClick}
onBlock={this.handleBlockClick}
+ onUnblock={this.handleUnblockClick}
+ onBlockDomain={this.handleBlockDomainClick}
+ onUnblockDomain={this.handleUnblockDomainClick}
onReport={this.handleReport}
onPin={this.handlePin}
onEmbed={this.handleEmbed}