Skip to content

Commit

Permalink
Add relationship-based options to status dropdowns
Browse files Browse the repository at this point in the history
Move bookmark action in inline statuses from action bar to dropdown
  • Loading branch information
Gargron committed Nov 18, 2019
1 parent e8de558 commit 7e935b2
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 24 deletions.
91 changes: 80 additions & 11 deletions app/javascript/mastodon/components/status_action_bar.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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' },
Expand All @@ -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 => {
Expand All @@ -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 = {
Expand All @@ -55,14 +66,19 @@ class StatusActionBar extends ImmutablePureComponent {

static propTypes = {
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.map,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
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,
Expand All @@ -76,6 +92,7 @@ class StatusActionBar extends ImmutablePureComponent {
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'relationship',
'withDismiss',
]

Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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';
Expand All @@ -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) {
Expand All @@ -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')}` });
}
}
Expand Down Expand Up @@ -259,7 +329,6 @@ class StatusActionBar extends ImmutablePureComponent {
<IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
{shareButton}
<IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />

<div className='status__action-bar-dropdown'>
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/mastodon/containers/dropdown_menu_container.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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));
Expand Down
32 changes: 31 additions & 1 deletion app/javascript/mastodon/containers/status_container.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { connect } from 'react-redux';
import Status from '../components/status';
import { makeGetStatus } from '../selectors';
Expand All @@ -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';

Expand All @@ -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 = () => {
Expand Down Expand Up @@ -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));
},
Expand All @@ -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')));
Expand All @@ -172,6 +190,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},

onBlockDomain (domain) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
}));
},

onUnblockDomain (domain) {
dispatch(unblockDomain(domain));
},

});

export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
Loading

0 comments on commit 7e935b2

Please sign in to comment.