Merge branch 'unmute-action' into 'develop'

Add ability to Unmute user from within `/mutes` page.

See merge request soapbox-pub/soapbox-fe!1302
api-accept
Justin 2 years ago
commit 13eeb608cb

@ -3,7 +3,7 @@ import { Link, useHistory } from 'react-router-dom';
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
import VerificationBadge from 'soapbox/components/verification_badge';
import ActionButton from 'soapbox/features/ui/components/action_button';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { useAppSelector, useOnScreen } from 'soapbox/hooks';
import { getAcct } from 'soapbox/utils/accounts';
import { displayFqn } from 'soapbox/utils/state';
@ -46,6 +46,8 @@ interface IAccount {
actionAlignment?: 'center' | 'top',
actionIcon?: string,
actionTitle?: string,
/** Override other actions for specificity like mute/unmute. */
actionType?: 'muting' | 'blocking',
avatarSize?: number,
hidden?: boolean,
hideActions?: boolean,
@ -60,6 +62,7 @@ interface IAccount {
const Account = ({
account,
actionType,
action,
actionIcon,
actionTitle,
@ -111,7 +114,7 @@ const Account = ({
}
if (account.id !== me) {
return <ActionButton account={account} />;
return <ActionButton account={account} actionType={actionType} />;
}
return null;

@ -10,7 +10,7 @@ import {
updateProfileHoverCard,
} from 'soapbox/actions/profile_hover_card';
import Badge from 'soapbox/components/badge';
import ActionButton from 'soapbox/features/ui/components/action_button';
import ActionButton from 'soapbox/features/ui/components/action-button';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { UserPanel } from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';

@ -16,7 +16,7 @@ import Badge from 'soapbox/components/badge';
import StillImage from 'soapbox/components/still_image';
import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import ActionButton from 'soapbox/features/ui/components/action_button';
import ActionButton from 'soapbox/features/ui/components/action-button';
import {
isLocal,
isRemote,

@ -48,7 +48,7 @@ const Blocks: React.FC = () => {
itemClassName='pb-4'
>
{accountIds.map((id: string) =>
<AccountContainer key={id} id={id} />,
<AccountContainer key={id} id={id} actionType='blocking' />,
)}
</ScrollableList>
</Column>

@ -12,7 +12,7 @@ import DisplayName from 'soapbox/components/display_name';
import Permalink from 'soapbox/components/permalink';
import RelativeTimestamp from 'soapbox/components/relative_timestamp';
import { Text } from 'soapbox/components/ui';
import ActionButton from 'soapbox/features/ui/components/action_button';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { makeGetAccount } from 'soapbox/selectors';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
@ -82,4 +82,4 @@ class AccountCard extends ImmutablePureComponent {
);
}
}
}

@ -3,7 +3,7 @@ import React from 'react';
import Avatar from 'soapbox/components/avatar';
import DisplayName from 'soapbox/components/display_name';
import Permalink from 'soapbox/components/permalink';
import ActionButton from 'soapbox/features/ui/components/action_button';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { useAppSelector } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';

@ -48,7 +48,7 @@ const Mutes: React.FC = () => {
itemClassName='pb-4'
>
{accountIds.map((id: string) =>
<AccountContainer key={id} id={id} />,
<AccountContainer key={id} id={id} actionType='muting' />,
)}
</ScrollableList>
</Column>

@ -0,0 +1,199 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount,
muteAccount,
unmuteAccount,
} from 'soapbox/actions/accounts';
import { openModal } from 'soapbox/actions/modals';
import { Button } from 'soapbox/components/ui';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import type { Account as AccountEntity } from 'soapbox/types/entities';
const messages = defineMessages({
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
blocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
remote_follow: { id: 'account.remote_follow', defaultMessage: 'Remote follow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
requested_small: { id: 'account.requested_small', defaultMessage: 'Awaiting approval' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
});
interface iActionButton {
account: AccountEntity
actionType?: 'muting' | 'blocking'
small?: boolean
}
const ActionButton = ({ account, actionType, small }: iActionButton) => {
const dispatch = useDispatch();
const features = useFeatures();
const intl = useIntl();
const me = useAppSelector((state) => state.me);
const handleFollow = () => {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(unfollowAccount(account.get('id')));
} else {
dispatch(followAccount(account.get('id')));
}
};
const handleBlock = () => {
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(blockAccount(account.get('id')));
}
};
const handleMute = () => {
if (account.getIn(['relationship', 'muting'])) {
dispatch(unmuteAccount(account.get('id')));
} else {
dispatch(muteAccount(account.get('id')));
}
};
const handleRemoteFollow = () => {
dispatch(openModal('UNAUTHORIZED', {
action: 'FOLLOW',
account: account.get('id'),
ap_id: account.get('url'),
}));
};
const mutingAction = () => {
const isMuted = account.getIn(['relationship', 'muting']);
const messageKey = isMuted ? messages.unmute : messages.mute;
const text = intl.formatMessage(messageKey, { name: account.get('username') });
return (
<Button
theme={isMuted ? 'danger' : 'secondary'}
size='sm'
text={text}
onClick={handleMute}
/>
);
};
const blockingAction = () => {
const isBlocked = account.getIn(['relationship', 'blocking']);
const messageKey = isBlocked ? messages.unblock : messages.block;
const text = intl.formatMessage(messageKey, { name: account.get('username') });
return (
<Button
theme={isBlocked ? 'danger' : 'secondary'}
size='sm'
text={text}
onClick={handleBlock}
/>
);
};
const empty = <></>;
if (!me) {
// Remote follow
if (features.remoteInteractionsAPI) {
return (
<Button
onClick={handleRemoteFollow}
icon={require('@tabler/icons/icons/plus.svg')}
text={intl.formatMessage(messages.follow)}
/>
);
}
return (
<form method='POST' action='/main/ostatus'>
<input type='hidden' name='nickname' value={account.get('acct')} />
<input type='hidden' name='profile' value='' />
<Button text={intl.formatMessage(messages.remote_follow)} type='submit' />
</form>
);
}
if (me !== account.get('id')) {
const isFollowing = account.getIn(['relationship', 'following']);
const blockedBy = account.getIn(['relationship', 'blocked_by']) as boolean;
if (actionType) {
if (actionType === 'muting') {
return mutingAction();
} else if (actionType === 'blocking') {
return blockingAction();
}
}
if (!account.get('relationship')) {
// Wait until the relationship is loaded
return empty;
} else if (account.getIn(['relationship', 'requested'])) {
// Awaiting acceptance
return (
<Button
size='sm'
theme='secondary'
text={small ? intl.formatMessage(messages.requested_small) : intl.formatMessage(messages.requested)}
onClick={handleFollow}
/>
);
} else if (!account.getIn(['relationship', 'blocking']) && !account.getIn(['relationship', 'muting'])) {
// Follow & Unfollow
return (
<Button
size='sm'
disabled={blockedBy}
theme={isFollowing ? 'secondary' : 'primary'}
icon={blockedBy ? require('@tabler/icons/icons/ban.svg') : (!isFollowing && require('@tabler/icons/icons/plus.svg'))}
onClick={handleFollow}
>
{isFollowing ? (
intl.formatMessage(messages.unfollow)
) : (
intl.formatMessage(blockedBy ? messages.blocked : messages.follow)
)}
</Button>
);
} else if (account.getIn(['relationship', 'blocking'])) {
// Unblock
return (
<Button
theme='danger'
size='sm'
text={intl.formatMessage(messages.unblock, { name: account.get('username') })}
onClick={handleBlock}
/>
);
}
} else {
// Edit profile
return (
<Button
theme='secondary'
size='sm'
text={intl.formatMessage(messages.edit_profile)}
to='/settings/profile'
/>
);
}
return empty;
};
export default ActionButton;

@ -1,162 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount,
} from 'soapbox/actions/accounts';
import { openModal } from 'soapbox/actions/modals';
import Icon from 'soapbox/components/icon';
import { Button } from 'soapbox/components/ui';
import { getFeatures } from 'soapbox/utils/features';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
remote_follow: { id: 'account.remote_follow', defaultMessage: 'Remote follow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
requested_small: { id: 'account.requested_small', defaultMessage: 'Awaiting approval' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
blocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
});
const mapStateToProps = state => {
const me = state.get('me');
const instance = state.get('instance');
return {
me,
features: getFeatures(instance),
};
};
const mapDispatchToProps = (dispatch) => ({
onFollow(account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(unfollowAccount(account.get('id')));
} else {
dispatch(followAccount(account.get('id')));
}
},
onBlock(account) {
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(blockAccount(account.get('id')));
}
},
onOpenUnauthorizedModal(account) {
dispatch(openModal('UNAUTHORIZED', {
action: 'FOLLOW',
account: account.get('id'),
ap_id: account.get('url'),
}));
},
});
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class ActionButton extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.record.isRequired,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onOpenUnauthorizedModal: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
small: PropTypes.bool,
features: PropTypes.object.isRequired,
};
static defaultProps = {
small: false,
}
componentDidMount() {
window.addEventListener('resize', this.handleResize, { passive: true });
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
handleFollow = () => {
this.props.onFollow(this.props.account);
}
handleBlock = () => {
this.props.onBlock(this.props.account);
}
handleRemoteFollow = () => {
this.props.onOpenUnauthorizedModal(this.props.account);
}
render() {
const { account, intl, me, small, features } = this.props;
const empty = <></>;
if (!me) {
// Remote follow
if (features.remoteInteractionsAPI) {
return (<Button
className='button--follow'
onClick={this.handleRemoteFollow}
>
{intl.formatMessage(messages.follow)}
<Icon src={require('@tabler/icons/icons/plus.svg')} />
</Button>);
}
return (<form method='POST' action='/main/ostatus'>
<input type='hidden' name='nickname' value={account.get('acct')} />
<input type='hidden' name='profile' value='' />
<Button className='logo-button' text={intl.formatMessage(messages.remote_follow)} click='submit' />
</form>);
}
if (me !== account.get('id')) {
const isFollowing = account.getIn(['relationship', 'following']);
const blockedBy = account.getIn(['relationship', 'blocked_by']);
if (!account.get('relationship')) { // Wait until the relationship is loaded
return empty;
} else if (account.getIn(['relationship', 'requested'])) {
// Awaiting acceptance
return <Button size='sm' theme='secondary' text={small ? intl.formatMessage(messages.requested_small) : intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) {
// Follow & Unfollow
return (<Button
size='sm'
disabled={blockedBy}
theme={isFollowing ? 'secondary' : 'primary'}
icon={blockedBy ? require('@tabler/icons/icons/ban.svg') : (!isFollowing && require('@tabler/icons/icons/plus.svg'))}
onClick={this.handleFollow}
>
{isFollowing ? (
intl.formatMessage(messages.unfollow)
) : (
intl.formatMessage(blockedBy ? messages.blocked : messages.follow)
)}
</Button>);
} else if (account.getIn(['relationship', 'blocking'])) {
// Unblock
return <Button theme='danger' size='sm' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
}
} else {
// Edit profile
return <Button theme='secondary' size='sm' text={intl.formatMessage(messages.edit_profile)} to='/settings/profile' />;
}
return empty;
}
}

@ -114,18 +114,6 @@ a.button {
}
}
.button--follow {
display: flex;
align-items: center;
justify-content: center;
.svg-icon {
margin: 0 0 0 6px;
width: 20px;
height: 20px;
}
}
.button--welcome {
.emojione {
margin: -1px 6px 0 -4px;

Loading…
Cancel
Save