diff --git a/app/controllers/api/v1/accounts/exclude_antennas_controller.rb b/app/controllers/api/v1/accounts/exclude_antennas_controller.rb new file mode 100644 index 0000000000..c1f5c5981c --- /dev/null +++ b/app/controllers/api/v1/accounts/exclude_antennas_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::ExcludeAntennasController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_account + + def index + @antennas = @account.suspended? ? [] : current_account.antennas.where('exclude_accounts @> \'[?]\'', @account.id) + render json: @antennas, each_serializer: REST::AntennaSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/javascript/mastodon/actions/antennas.js b/app/javascript/mastodon/actions/antennas.js index 2b0d5d042a..51002b6c58 100644 --- a/app/javascript/mastodon/actions/antennas.js +++ b/app/javascript/mastodon/actions/antennas.js @@ -122,6 +122,10 @@ export const ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_ANTENNAS_FETC export const ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS'; export const ANTENNA_ADDER_ANTENNAS_FETCH_FAIL = 'ANTENNA_ADDER_ANTENNAS_FETCH_FAIL'; +export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST'; +export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS'; +export const ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL = 'ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL'; + export const fetchAntenna = id => (dispatch, getState) => { if (getState().getIn(['antennas', id])) { return; @@ -905,6 +909,15 @@ export const setupAntennaAdder = accountId => (dispatch, getState) => { dispatch(fetchAccountAntennas(accountId)); }; +export const setupExcludeAntennaAdder = accountId => (dispatch, getState) => { + dispatch({ + type: ANTENNA_ADDER_SETUP, + account: getState().getIn(['accounts', accountId]), + }); + dispatch(fetchAntennas()); + dispatch(fetchExcludeAccountAntennas(accountId)); +}; + export const fetchAccountAntennas = accountId => (dispatch, getState) => { dispatch(fetchAccountAntennasRequest(accountId)); @@ -930,6 +943,31 @@ export const fetchAccountAntennasFail = (id, err) => ({ err, }); +export const fetchExcludeAccountAntennas = accountId => (dispatch, getState) => { + dispatch(fetchExcludeAccountAntennasRequest(accountId)); + + api(getState).get(`/api/v1/accounts/${accountId}/exclude_antennas`) + .then(({ data }) => dispatch(fetchExcludeAccountAntennasSuccess(accountId, data))) + .catch(err => dispatch(fetchExcludeAccountAntennasFail(accountId, err))); +}; + +export const fetchExcludeAccountAntennasRequest = id => ({ + type:ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST, + id, +}); + +export const fetchExcludeAccountAntennasSuccess = (id, antennas) => ({ + type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS, + id, + antennas, +}); + +export const fetchExcludeAccountAntennasFail = (id, err) => ({ + type: ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL, + id, + err, +}); + export const addToAntennaAdder = antennaId => (dispatch, getState) => { dispatch(addToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId']))); }; @@ -938,3 +976,11 @@ export const removeFromAntennaAdder = antennaId => (dispatch, getState) => { dispatch(removeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId']))); }; +export const addExcludeToAntennaAdder = antennaId => (dispatch, getState) => { + dispatch(addExcludeToAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId']))); +}; + +export const removeExcludeFromAntennaAdder = antennaId => (dispatch, getState) => { + dispatch(removeExcludeFromAntenna(antennaId, getState().getIn(['antennaAdder', 'accountId']))); +}; + diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index c347cf2f87..9b3424f3e5 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -59,6 +59,7 @@ const messages = defineMessages({ unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, add_or_remove_from_antenna: { id: 'account.add_or_remove_from_antenna', defaultMessage: 'Add or Remove from antennas' }, + add_or_remove_from_exclude_antenna: { id: 'account.add_or_remove_from_exclude_antenna', defaultMessage: 'Add or Remove from antennas as exclusion' }, add_or_remove_from_circle: { id: 'account.add_or_remove_from_circle', defaultMessage: 'Add or Remove from circles' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, @@ -106,6 +107,7 @@ class Header extends ImmutablePureComponent { onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, onAddToAntenna: PropTypes.func.isRequired, + onAddToExcludeAntenna: PropTypes.func.isRequired, onAddToCircle: PropTypes.func.isRequired, onEditAccountNote: PropTypes.func.isRequired, onChangeLanguages: PropTypes.func.isRequired, @@ -332,6 +334,7 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList }); } menu.push({ text: intl.formatMessage(messages.add_or_remove_from_antenna), action: this.props.onAddToAntenna }); + menu.push({ text: intl.formatMessage(messages.add_or_remove_from_exclude_antenna), action: this.props.onAddToExcludeAntenna }); if (account.getIn(['relationship', 'followed_by'])) { menu.push({ text: intl.formatMessage(messages.add_or_remove_from_circle), action: this.props.onAddToCircle }); } diff --git a/app/javascript/mastodon/features/account_timeline/components/header.jsx b/app/javascript/mastodon/features/account_timeline/components/header.jsx index ccf8290574..aaa0c57f83 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx @@ -28,6 +28,7 @@ export default class Header extends ImmutablePureComponent { onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, onAddToAntenna: PropTypes.func.isRequired, + onAddToExcludeAntenna: PropTypes.func.isRequired, onAddToCircle: PropTypes.func.isRequired, onChangeLanguages: PropTypes.func.isRequired, onInteractionModal: PropTypes.func.isRequired, @@ -102,6 +103,10 @@ export default class Header extends ImmutablePureComponent { this.props.onAddToAntenna(this.props.account); }; + handleAddToExcludeAntenna = () => { + this.props.onAddToExcludeAntenna(this.props.account); + }; + handleAddToCircle = () => { this.props.onAddToCircle(this.props.account); }; @@ -149,6 +154,7 @@ export default class Header extends ImmutablePureComponent { onEndorseToggle={this.handleEndorseToggle} onAddToList={this.handleAddToList} onAddToAntenna={this.handleAddToAntenna} + onAddToExcludeAntenna={this.handleAddToExcludeAntenna} onAddToCircle={this.handleAddToCircle} onEditAccountNote={this.handleEditAccountNote} onChangeLanguages={this.handleChangeLanguages} diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index b71ee4b945..92bcd167fb 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -169,6 +169,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ modalType: 'ANTENNA_ADDER', modalProps: { accountId: account.get('id'), + isExclude: false, + }, + })); + }, + + onAddToExcludeAntenna (account) { + dispatch(openModal({ + modalType: 'ANTENNA_ADDER', + modalProps: { + accountId: account.get('id'), + isExclude: true, }, })); }, diff --git a/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx b/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx index 2c90c311c1..b7f6134b79 100644 --- a/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx +++ b/app/javascript/mastodon/features/antenna_adder/components/antenna.jsx @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { Icon } from 'mastodon/components/icon'; -import { removeFromAntennaAdder, addToAntennaAdder } from '../../../actions/antennas'; +import { removeFromAntennaAdder, addToAntennaAdder, removeExcludeFromAntennaAdder, addExcludeToAntennaAdder } from '../../../actions/antennas'; import { IconButton } from '../../../components/icon_button'; const messages = defineMessages({ @@ -24,15 +24,20 @@ const MapStateToProps = (state, { antennaId, added }) => ({ const mapDispatchToProps = (dispatch, { antennaId }) => ({ onRemove: () => dispatch(removeFromAntennaAdder(antennaId)), onAdd: () => dispatch(addToAntennaAdder(antennaId)), + onExcludeRemove: () => dispatch(removeExcludeFromAntennaAdder(antennaId)), + onExcludeAdd: () => dispatch(addExcludeToAntennaAdder(antennaId)), }); class Antenna extends ImmutablePureComponent { static propTypes = { antenna: ImmutablePropTypes.map.isRequired, + isExclude: PropTypes.bool.isRequired, intl: PropTypes.object.isRequired, onRemove: PropTypes.func.isRequired, onAdd: PropTypes.func.isRequired, + onExcludeRemove: PropTypes.func.isRequired, + onExcludeAdd: PropTypes.func.isRequired, added: PropTypes.bool, }; @@ -40,15 +45,31 @@ class Antenna extends ImmutablePureComponent { added: false, }; + handleRemove = () => { + if (this.props.isExclude) { + this.props.onExcludeRemove(); + } else { + this.props.onRemove(); + } + }; + + handleAdd = () => { + if (this.props.isExclude) { + this.props.onExcludeAdd(); + } else { + this.props.onAdd(); + } + }; + render () { - const { antenna, intl, onRemove, onAdd, added } = this.props; + const { antenna, intl, added } = this.props; let button; if (added) { - button = ; + button = ; } else { - button = ; + button = ; } return ( diff --git a/app/javascript/mastodon/features/antenna_adder/index.jsx b/app/javascript/mastodon/features/antenna_adder/index.jsx index a3d6c34a4c..7fecf8fa64 100644 --- a/app/javascript/mastodon/features/antenna_adder/index.jsx +++ b/app/javascript/mastodon/features/antenna_adder/index.jsx @@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { setupAntennaAdder, resetAntennaAdder } from '../../actions/antennas'; +import { setupAntennaAdder, resetAntennaAdder, setupExcludeAntennaAdder } from '../../actions/antennas'; import NewAntennaForm from '../antennas/components/new_antenna_form'; import Account from '../list_adder/components/account'; @@ -28,6 +28,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ onInitialize: accountId => dispatch(setupAntennaAdder(accountId)), + onExcludeInitialize: accountId => dispatch(setupExcludeAntennaAdder(accountId)), onReset: () => dispatch(resetAntennaAdder()), }); @@ -35,16 +36,22 @@ class AntennaAdder extends ImmutablePureComponent { static propTypes = { accountId: PropTypes.string.isRequired, + isExclude: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, onInitialize: PropTypes.func.isRequired, + onExcludeInitialize: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired, antennaIds: ImmutablePropTypes.list.isRequired, }; componentDidMount () { - const { onInitialize, accountId } = this.props; - onInitialize(accountId); + const { isExclude, onInitialize, onExcludeInitialize, accountId } = this.props; + if (isExclude) { + onExcludeInitialize(accountId); + } else { + onInitialize(accountId); + } } componentWillUnmount () { @@ -53,7 +60,7 @@ class AntennaAdder extends ImmutablePureComponent { } render () { - const { accountId, antennaIds } = this.props; + const { accountId, antennaIds, isExclude } = this.props; return (
@@ -65,7 +72,7 @@ class AntennaAdder extends ImmutablePureComponent {
- {antennaIds.map(antennaId => )} + {antennaIds.map(antennaId => )}
); diff --git a/app/javascript/mastodon/features/antenna_editor/components/account.jsx b/app/javascript/mastodon/features/antenna_editor/components/account.jsx index ea3a950b2d..278698ed9f 100644 --- a/app/javascript/mastodon/features/antenna_editor/components/account.jsx +++ b/app/javascript/mastodon/features/antenna_editor/components/account.jsx @@ -20,9 +20,9 @@ const messages = defineMessages({ const makeMapStateToProps = () => { const getAccount = makeGetAccount(); - const mapStateToProps = (state, { accountId, added, isExclude }) => ({ + const mapStateToProps = (state, { accountId, added }) => ({ account: getAccount(state, accountId), - added: typeof added === 'undefined' ? state.getIn(['antennaEditor', isExclude ? 'excludeAccounts' : 'accounts', 'items']).includes(accountId) : added, + added: typeof added === 'undefined' ? state.getIn(['antennaEditor', 'accounts', 'items']).includes(accountId) : added, }); return mapStateToProps; diff --git a/app/javascript/mastodon/features/antenna_editor/index.jsx b/app/javascript/mastodon/features/antenna_editor/index.jsx index d558161171..95b6b43f88 100644 --- a/app/javascript/mastodon/features/antenna_editor/index.jsx +++ b/app/javascript/mastodon/features/antenna_editor/index.jsx @@ -15,8 +15,8 @@ import Account from './components/account'; import EditAntennaForm from './components/edit_antenna_form'; import Search from './components/search'; -const mapStateToProps = (state, { isExclude }) => ({ - accountIds: state.getIn(['antennaEditor', isExclude ? 'excludeAccounts' : 'accounts', 'items']), +const mapStateToProps = (state) => ({ + accountIds: state.getIn(['antennaEditor', 'accounts', 'items']), searchAccountIds: state.getIn(['antennaEditor', 'suggestions', 'items']), }); diff --git a/app/javascript/mastodon/reducers/antenna_adder.js b/app/javascript/mastodon/reducers/antenna_adder.js index 947574fdc2..ace105d634 100644 --- a/app/javascript/mastodon/reducers/antenna_adder.js +++ b/app/javascript/mastodon/reducers/antenna_adder.js @@ -6,8 +6,13 @@ import { ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST, ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS, ANTENNA_ADDER_ANTENNAS_FETCH_FAIL, + ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST, + ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS, + ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL, ANTENNA_EDITOR_ADD_SUCCESS, ANTENNA_EDITOR_REMOVE_SUCCESS, + ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS, + ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS, } from '../actions/antennas'; const initialState = ImmutableMap({ @@ -29,18 +34,23 @@ export default function antennaAdderReducer(state = initialState, action) { map.set('accountId', action.account.get('id')); }); case ANTENNA_ADDER_ANTENNAS_FETCH_REQUEST: + case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_REQUEST: return state.setIn(['antennas', 'isLoading'], true); case ANTENNA_ADDER_ANTENNAS_FETCH_FAIL: - return state.setIn(['antennas', 'isLoading'], false); + case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL: + return state.setIn(['antennas', 'isLoading'], false); case ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS: - return state.update('antennas', antennas => antennas.withMutations(map => { + case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS: + return state.update('antennas', antennas => antennas.withMutations(map => { map.set('isLoading', false); map.set('loaded', true); map.set('items', ImmutableList(action.antennas.map(item => item.id))); })); case ANTENNA_EDITOR_ADD_SUCCESS: + case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS: return state.updateIn(['antennas', 'items'], antenna => antenna.unshift(action.antennaId)); case ANTENNA_EDITOR_REMOVE_SUCCESS: + case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS: return state.updateIn(['antennas', 'items'], antenna => antenna.filterNot(item => item === action.antennaId)); default: return state; diff --git a/app/javascript/mastodon/reducers/antenna_editor.js b/app/javascript/mastodon/reducers/antenna_editor.js index 7a164070be..c647d108b2 100644 --- a/app/javascript/mastodon/reducers/antenna_editor.js +++ b/app/javascript/mastodon/reducers/antenna_editor.js @@ -38,12 +38,6 @@ const initialState = ImmutableMap({ isLoading: false, }), - excludeAccounts: ImmutableMap({ - items: ImmutableList(), - loaded: false, - isLoading: false, - }), - suggestions: ImmutableMap({ value: '', items: ImmutableList(), @@ -92,11 +86,11 @@ export default function antennaEditorReducer(state = initialState, action) { map.set('items', ImmutableList(action.accounts.map(item => item.id))); })); case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_REQUEST: - return state.setIn(['excludeAccounts', 'isLoading'], true); + return state.setIn(['accounts', 'isLoading'], true); case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_FAIL: - return state.setIn(['excludeAccounts', 'isLoading'], false); + return state.setIn(['accounts', 'isLoading'], false); case ANTENNA_EXCLUDE_ACCOUNTS_FETCH_SUCCESS: - return state.update('excludeAccounts', accounts => accounts.withMutations(map => { + return state.update('accounts', accounts => accounts.withMutations(map => { map.set('isLoading', false); map.set('loaded', true); map.set('items', ImmutableList(action.accounts.map(item => item.id))); @@ -111,13 +105,11 @@ export default function antennaEditorReducer(state = initialState, action) { map.set('value', ''); })); case ANTENNA_EDITOR_ADD_SUCCESS: + case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS: return state.updateIn(['accounts', 'items'], antenna => antenna.unshift(action.accountId)); case ANTENNA_EDITOR_REMOVE_SUCCESS: - return state.updateIn(['accounts', 'items'], antenna => antenna.filterNot(item => item === action.accountId)); - case ANTENNA_EDITOR_ADD_EXCLUDE_SUCCESS: - return state.updateIn(['excludeAccounts', 'items'], antenna => antenna.unshift(action.accountId)); case ANTENNA_EDITOR_REMOVE_EXCLUDE_SUCCESS: - return state.updateIn(['excludeAccounts', 'items'], antenna => antenna.filterNot(item => item === action.accountId)); + return state.updateIn(['accounts', 'items'], antenna => antenna.filterNot(item => item === action.accountId)); default: return state; } diff --git a/config/routes/api.rb b/config/routes/api.rb index f760edc524..ff6f84f328 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -180,6 +180,7 @@ namespace :api, format: false do resources :following, only: :index, controller: 'accounts/following_accounts' resources :lists, only: :index, controller: 'accounts/lists' resources :antennas, only: :index, controller: 'accounts/antennas' + resources :exclude_antennas, only: :index, controller: 'accounts/exclude_antennas' resources :circles, only: :index, controller: 'accounts/circles' resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs' resources :featured_tags, only: :index, controller: 'accounts/featured_tags'