diff options
46 files changed, 436 insertions, 43 deletions
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 5724dbaea4f..12c7dd2b08c 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -71,11 +71,12 @@ class Api::V1::AccountsController < ApiController def block BlockService.new.call(current_user.account, @account) - @following = { @account.id => false } - @followed_by = { @account.id => false } - @blocking = { @account.id => true } - @requested = { @account.id => false } - @muting = { @account.id => current_user.account.muting?(@account.id) } + @following = { @account.id => false } + @followed_by = { @account.id => false } + @blocking = { @account.id => true } + @requested = { @account.id => false } + @muting = { @account.id => current_account.muting?(@account.id) } + @domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) } render :relationship end @@ -107,12 +108,13 @@ class Api::V1::AccountsController < ApiController def relationships ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i] - @accounts = Account.where(id: ids).select('id') - @following = Account.following_map(ids, current_user.account_id) - @followed_by = Account.followed_by_map(ids, current_user.account_id) - @blocking = Account.blocking_map(ids, current_user.account_id) - @muting = Account.muting_map(ids, current_user.account_id) - @requested = Account.requested_map(ids, current_user.account_id) + @accounts = Account.where(id: ids).select('id') + @following = Account.following_map(ids, current_user.account_id) + @followed_by = Account.followed_by_map(ids, current_user.account_id) + @blocking = Account.blocking_map(ids, current_user.account_id) + @muting = Account.muting_map(ids, current_user.account_id) + @requested = Account.requested_map(ids, current_user.account_id) + @domain_blocking = Account.domain_blocking_map(ids, current_user.account_id) end def search @@ -128,11 +130,12 @@ class Api::V1::AccountsController < ApiController end def set_relationship - @following = Account.following_map([@account.id], current_user.account_id) - @followed_by = Account.followed_by_map([@account.id], current_user.account_id) - @blocking = Account.blocking_map([@account.id], current_user.account_id) - @muting = Account.muting_map([@account.id], current_user.account_id) - @requested = Account.requested_map([@account.id], current_user.account_id) + @following = Account.following_map([@account.id], current_user.account_id) + @followed_by = Account.followed_by_map([@account.id], current_user.account_id) + @blocking = Account.blocking_map([@account.id], current_user.account_id) + @muting = Account.muting_map([@account.id], current_user.account_id) + @requested = Account.requested_map([@account.id], current_user.account_id) + @domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id) end def pagination_params(core_params) diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb index e14547911c7..f223dd16ee5 100644 --- a/app/controllers/api/v1/domain_blocks_controller.rb +++ b/app/controllers/api/v1/domain_blocks_controller.rb @@ -17,7 +17,7 @@ class Api::V1::DomainBlocksController < ApiController end def create - current_account.block_domain!(domain_block_params[:domain]) + BlockDomainFromAccountService.new.call(current_account, domain_block_params[:domain]) render_empty end diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js new file mode 100644 index 00000000000..c884981176b --- /dev/null +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -0,0 +1,117 @@ +import api, { getLinks } from '../api' + +export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; +export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS'; +export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL'; + +export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST'; +export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS'; +export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL'; + +export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; +export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS'; +export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL'; + +export function blockDomain(domain, accountId) { + return (dispatch, getState) => { + dispatch(blockDomainRequest(domain)); + + api(getState).post('/api/v1/domain_blocks', { domain }).then(response => { + dispatch(blockDomainSuccess(domain, accountId)); + }).catch(err => { + dispatch(blockDomainFail(domain, err)); + }); + }; +}; + +export function blockDomainRequest(domain) { + return { + type: DOMAIN_BLOCK_REQUEST, + domain + }; +}; + +export function blockDomainSuccess(domain, accountId) { + return { + type: DOMAIN_BLOCK_SUCCESS, + domain, + accountId + }; +}; + +export function blockDomainFail(domain, error) { + return { + type: DOMAIN_BLOCK_FAIL, + domain, + error + }; +}; + +export function unblockDomain(domain, accountId) { + return (dispatch, getState) => { + dispatch(unblockDomainRequest(domain)); + + api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => { + dispatch(unblockDomainSuccess(domain, accountId)); + }).catch(err => { + dispatch(unblockDomainFail(domain, err)); + }); + }; +}; + +export function unblockDomainRequest(domain) { + return { + type: DOMAIN_UNBLOCK_REQUEST, + domain + }; +}; + +export function unblockDomainSuccess(domain, accountId) { + return { + type: DOMAIN_UNBLOCK_SUCCESS, + domain, + accountId + }; +}; + +export function unblockDomainFail(domain, error) { + return { + type: DOMAIN_UNBLOCK_FAIL, + domain, + error + }; +}; + +export function fetchDomainBlocks() { + return (dispatch, getState) => { + dispatch(fetchDomainBlocksRequest()); + + api(getState).get().then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); + }).catch(err => { + dispatch(fetchDomainBlocksFail(err)); + }); + }; +}; + +export function fetchDomainBlocksRequest() { + return { + type: DOMAIN_BLOCKS_FETCH_REQUEST + }; +}; + +export function fetchDomainBlocksSuccess(domains, next) { + return { + type: DOMAIN_BLOCKS_FETCH_SUCCESS, + domains, + next + }; +}; + +export function fetchDomainBlocksFail(error) { + return { + type: DOMAIN_BLOCKS_FETCH_FAIL, + error + }; +}; diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 44ed8af7d18..1997bf7b732 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -15,7 +15,9 @@ const messages = defineMessages({ mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, report: { id: 'account.report', defaultMessage: 'Report @{name}' }, - disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' } + disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }, + blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, }); class ActionBar extends React.PureComponent { @@ -28,6 +30,8 @@ class ActionBar extends React.PureComponent { onMention: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, + onBlockDomain: PropTypes.func.isRequired, + onUnblockDomain: PropTypes.func.isRequired, intl: PropTypes.object.isRequired }; @@ -59,7 +63,16 @@ class ActionBar extends React.PureComponent { } if (account.get('acct') !== account.get('username')) { + const domain = account.get('acct').split('@')[1]; extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>; + + menu.push(null); + + if (account.getIn(['relationship', 'domain_blocking'])) { + menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain }); + } else { + menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain }); + } } return ( diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index d7226d9b20b..f1a0e8d775b 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -15,7 +15,9 @@ class Header extends ImmutablePureComponent { onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired + onMute: PropTypes.func.isRequired, + onBlockDomain: PropTypes.func.isRequired, + onUnblockDomain: PropTypes.func.isRequired, }; static contextTypes = { @@ -43,6 +45,22 @@ class Header extends ImmutablePureComponent { this.props.onMute(this.props.account); } + handleBlockDomain = () => { + const domain = this.props.account.get('acct').split('@')[1]; + + if (!domain) return; + + this.props.onBlockDomain(domain, this.props.account.get('id')); + } + + handleUnblockDomain = () => { + const domain = this.props.account.get('acct').split('@')[1]; + + if (!domain) return; + + this.props.onUnblockDomain(domain, this.props.account.get('id')); + } + render () { const { account, me } = this.props; @@ -65,6 +83,8 @@ class Header extends ImmutablePureComponent { onMention={this.handleMention} onReport={this.handleReport} onMute={this.handleMute} + onBlockDomain={this.handleBlockDomain} + onUnblockDomain={this.handleUnblockDomain} /> </div> ); diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 50999d2e0e6..0964efdcfee 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -13,11 +13,13 @@ import { import { mentionCompose } from '../../../actions/compose'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; +import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; const messages = defineMessages({ blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' } + muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }, + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, }); const makeMapStateToProps = () => { @@ -70,6 +72,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onConfirm: () => dispatch(muteAccount(account.get('id'))) })); } + }, + + onBlockDomain (domain, accountId) { + 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.' values={{ domain: <strong>{domain}</strong> }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain, accountId)) + })); + }, + + onUnblockDomain (domain, accountId) { + dispatch(unblockDomain(domain, accountId)); } }); diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index e30b7e84aec..2a9d00460a6 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -1,17 +1,20 @@ { "account.block": "Øظر @{name}", + "account.block_domain": "Hide everything from {domain}", "account.disclaimer": "هذا المستخدم Ù…Ù |