Merge branch 'remove-from-followers' into 'develop'

Add 'Remove from followers'

See merge request soapbox-pub/soapbox-fe!1408
This commit is contained in:
marcin mikołajczak 2022-05-20 18:58:36 +00:00
commit 21be58ecb4
6 changed files with 83 additions and 0 deletions

View file

@ -57,6 +57,10 @@ export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS'; export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL'; export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST';
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS';
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL';
export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST'; export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST';
export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS'; export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS';
export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL'; export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL';
@ -520,6 +524,42 @@ export function unsubscribeAccountFail(error) {
}; };
} }
export function removeFromFollowers(id) {
return (dispatch, getState) => {
if (!isLoggedIn(getState)) return;
dispatch(muteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/remove_from_followers`).then(response => {
dispatch(removeFromFollowersSuccess(response.data));
}).catch(error => {
dispatch(removeFromFollowersFail(id, error));
});
};
}
export function removeFromFollowersRequest(id) {
return {
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST,
id,
};
}
export function removeFromFollowersSuccess(relationship) {
return {
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
relationship,
};
}
export function removeFromFollowersFail(error) {
return {
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL,
error,
};
}
export function fetchFollowers(id) { export function fetchFollowers(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchFollowersRequest(id)); dispatch(fetchFollowersRequest(id));

View file

@ -48,6 +48,7 @@ const messages = defineMessages({
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
removeFromFollowers: { id: 'account.remove_from_followers', defaultMessage: 'Remove this follower' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
deactivateUser: { id: 'admin.users.actions.deactivate_user', defaultMessage: 'Deactivate @{name}' }, deactivateUser: { id: 'admin.users.actions.deactivate_user', defaultMessage: 'Deactivate @{name}' },
@ -283,6 +284,14 @@ class Header extends ImmutablePureComponent {
}); });
} }
if (features.removeFromFollowers && account.getIn(['relationship', 'followed_by'])) {
menu.push({
text: intl.formatMessage(messages.removeFromFollowers),
action: this.props.onRemoveFromFollowers,
icon: require('@tabler/icons/icons/user-x.svg'),
});
}
if (account.getIn(['relationship', 'muting'])) { if (account.getIn(['relationship', 'muting'])) {
menu.push({ menu.push({
text: intl.formatMessage(messages.unmute, { name: account.get('username') }), text: intl.formatMessage(messages.unmute, { name: account.get('username') }),

View file

@ -25,6 +25,7 @@ class Header extends ImmutablePureComponent {
onUnblockDomain: PropTypes.func.isRequired, onUnblockDomain: PropTypes.func.isRequired,
onEndorseToggle: PropTypes.func.isRequired, onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired,
onRemoveFromFollowers: PropTypes.func.isRequired,
username: PropTypes.string, username: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
}; };
@ -141,6 +142,10 @@ class Header extends ImmutablePureComponent {
this.props.onShowNote(this.props.account); this.props.onShowNote(this.props.account);
} }
handleRemoveFromFollowers = () => {
this.props.onRemoveFromFollowers(this.props.account);
}
render() { render() {
const { account } = this.props; const { account } = this.props;
const moved = (account) ? account.get('moved') : false; const moved = (account) ? account.get('moved') : false;
@ -177,6 +182,7 @@ class Header extends ImmutablePureComponent {
onSuggestUser={this.handleSuggestUser} onSuggestUser={this.handleSuggestUser}
onUnsuggestUser={this.handleUnsuggestUser} onUnsuggestUser={this.handleUnsuggestUser}
onShowNote={this.handleShowNote} onShowNote={this.handleShowNote}
onRemoveFromFollowers={this.handleRemoveFromFollowers}
username={this.props.username} username={this.props.username}
/> />
</> </>

View file

@ -13,6 +13,7 @@ import {
unpinAccount, unpinAccount,
subscribeAccount, subscribeAccount,
unsubscribeAccount, unsubscribeAccount,
removeFromFollowers,
} from 'soapbox/actions/accounts'; } from 'soapbox/actions/accounts';
import { import {
verifyUser, verifyUser,
@ -56,6 +57,7 @@ const messages = defineMessages({
demotedToUser: { id: 'admin.users.actions.demote_to_user_message', defaultMessage: '@{acct} was demoted to a regular user' }, demotedToUser: { id: 'admin.users.actions.demote_to_user_message', defaultMessage: '@{acct} was demoted to a regular user' },
userSuggested: { id: 'admin.users.user_suggested_message', defaultMessage: '@{acct} was suggested' }, userSuggested: { id: 'admin.users.user_suggested_message', defaultMessage: '@{acct} was suggested' },
userUnsuggested: { id: 'admin.users.user_unsuggested_message', defaultMessage: '@{acct} was unsuggested' }, userUnsuggested: { id: 'admin.users.user_unsuggested_message', defaultMessage: '@{acct} was unsuggested' },
removeFromFollowersConfirm: { id: 'confirmations.remove_from_followers.confirm', defaultMessage: 'Remove' },
}); });
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
@ -269,6 +271,21 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onShowNote(account) { onShowNote(account) {
dispatch(initAccountNoteModal(account)); dispatch(initAccountNoteModal(account));
}, },
onRemoveFromFollowers(account) {
dispatch((_, getState) => {
const unfollowModal = getSettings(getState()).get('unfollowModal');
if (unfollowModal) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.remove_from_followers.message' defaultMessage='Are you sure you want to remove {name} from your followers?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
onConfirm: () => dispatch(removeFromFollowers(account.get('id'))),
}));
} else {
dispatch(removeFromFollowers(account.get('id')));
}
});
},
}); });
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header)); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));

View file

@ -19,6 +19,7 @@ import {
ACCOUNT_UNSUBSCRIBE_SUCCESS, ACCOUNT_UNSUBSCRIBE_SUCCESS,
ACCOUNT_PIN_SUCCESS, ACCOUNT_PIN_SUCCESS,
ACCOUNT_UNPIN_SUCCESS, ACCOUNT_UNPIN_SUCCESS,
ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS, RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts'; } from '../actions/accounts';
import { import {
@ -108,6 +109,7 @@ export default function relationships(state = initialState, action) {
case ACCOUNT_PIN_SUCCESS: case ACCOUNT_PIN_SUCCESS:
case ACCOUNT_UNPIN_SUCCESS: case ACCOUNT_UNPIN_SUCCESS:
case ACCOUNT_NOTE_SUBMIT_SUCCESS: case ACCOUNT_NOTE_SUBMIT_SUCCESS:
case ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS:
return normalizeRelationship(state, action.relationship); return normalizeRelationship(state, action.relationship);
case RELATIONSHIPS_FETCH_SUCCESS: case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships); return normalizeRelationships(state, action.relationships);

View file

@ -420,6 +420,15 @@ const getInstanceFeatures = (instance: Instance) => {
*/ */
remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'), remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'),
/**
* Ability to remove an account from your followers.
* @see POST /api/v1/accounts/:id/remove_from_followers
*/
removeFromFollowers: any([
v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
v.software === PLEROMA && v.build === SOAPBOX && gte(v.version, '2.4.50'),
]),
reportMultipleStatuses: any([ reportMultipleStatuses: any([
v.software === MASTODON, v.software === MASTODON,
v.software === PLEROMA, v.software === PLEROMA,