Update "Follow" button labels (#36264)

This commit is contained in:
diondiondion 2025-09-26 12:00:50 +02:00 committed by GitHub
parent e07b9dfdc1
commit cb5bbbfb05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 78 additions and 127 deletions

View File

@ -5,24 +5,61 @@ import { useIntl, defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { useIdentity } from '@/mastodon/identity_context'; import { useIdentity } from '@/mastodon/identity_context';
import { fetchRelationships, followAccount } from 'mastodon/actions/accounts'; import {
fetchRelationships,
followAccount,
unblockAccount,
unmuteAccount,
} from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { Button } from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { me } from 'mastodon/initial_state'; import { me } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({ import { useBreakpoint } from '../features/ui/hooks/useBreakpoint';
const longMessages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
follow: { id: 'account.follow', defaultMessage: 'Follow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' },
followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
followRequest: {
id: 'account.follow_request',
defaultMessage: 'Request to follow',
},
followRequestCancel: {
id: 'account.follow_request_cancel',
defaultMessage: 'Cancel request',
},
editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
}); });
const shortMessages = {
...longMessages, // Align type signature of shortMessages and longMessages
...defineMessages({
followBack: {
id: 'account.follow_back_short',
defaultMessage: 'Follow back',
},
followRequest: {
id: 'account.follow_request_short',
defaultMessage: 'Request',
},
followRequestCancel: {
id: 'account.follow_request_cancel_short',
defaultMessage: 'Cancel',
},
editProfile: { id: 'account.edit_profile_short', defaultMessage: 'Edit' },
}),
};
export const FollowButton: React.FC<{ export const FollowButton: React.FC<{
accountId?: string; accountId?: string;
compact?: boolean; compact?: boolean;
}> = ({ accountId, compact }) => { labelLength?: 'auto' | 'short' | 'long';
}> = ({ accountId, compact, labelLength = 'auto' }) => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { signedIn } = useIdentity(); const { signedIn } = useIdentity();
@ -57,29 +94,48 @@ export const FollowButton: React.FC<{
if (accountId === me) { if (accountId === me) {
return; return;
} else if (relationship.muting) {
dispatch(unmuteAccount(accountId));
} else if (account && (relationship.following || relationship.requested)) { } else if (account && (relationship.following || relationship.requested)) {
dispatch( dispatch(
openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }),
); );
} else if (relationship.blocking) {
dispatch(unblockAccount(accountId));
} else { } else {
dispatch(followAccount(accountId)); dispatch(followAccount(accountId));
} }
}, [dispatch, accountId, relationship, account, signedIn]); }, [dispatch, accountId, relationship, account, signedIn]);
const isNarrow = useBreakpoint('narrow');
const useShortLabel =
labelLength === 'short' || (labelLength === 'auto' && isNarrow);
const messages = useShortLabel ? shortMessages : longMessages;
const followMessage = account?.locked
? messages.followRequest
: messages.follow;
let label; let label;
if (!signedIn) { if (!signedIn) {
label = intl.formatMessage(messages.follow); label = intl.formatMessage(followMessage);
} else if (accountId === me) { } else if (accountId === me) {
label = intl.formatMessage(messages.editProfile); label = intl.formatMessage(messages.editProfile);
} else if (!relationship) { } else if (!relationship) {
label = <LoadingIndicator />; label = <LoadingIndicator />;
} else if (relationship.following || relationship.requested) { } else if (relationship.muting) {
label = intl.formatMessage(messages.unmute);
} else if (relationship.following) {
label = intl.formatMessage(messages.unfollow); label = intl.formatMessage(messages.unfollow);
} else if (relationship.followed_by) { } else if (relationship.blocking) {
label = intl.formatMessage(messages.unblock);
} else if (relationship.requested) {
label = intl.formatMessage(messages.followRequestCancel);
} else if (relationship.followed_by && !account?.locked) {
label = intl.formatMessage(messages.followBack); label = intl.formatMessage(messages.followBack);
} else { } else {
label = intl.formatMessage(messages.follow); label = intl.formatMessage(followMessage);
} }
if (accountId === me) { if (accountId === me) {

View File

@ -1,134 +1,23 @@
import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import {
followAccount,
unblockAccount,
unmuteAccount,
} from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name'; import { DisplayName } from 'mastodon/components/display_name';
import { FollowButton } from 'mastodon/components/follow_button';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me } from 'mastodon/initial_state'; import { autoPlayGif } from 'mastodon/initial_state';
import type { Account } from 'mastodon/models/account'; import type { Account } from 'mastodon/models/account';
import { makeGetAccount } from 'mastodon/selectors'; import { makeGetAccount } from 'mastodon/selectors';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppSelector } from 'mastodon/store';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
cancel_follow_request: {
id: 'account.cancel_follow_request',
defaultMessage: 'Withdraw follow request',
},
requested: {
id: 'account.requested',
defaultMessage: 'Awaiting approval. Click to cancel follow request',
},
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
});
const getAccount = makeGetAccount(); const getAccount = makeGetAccount();
export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => { export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
const intl = useIntl();
const account = useAppSelector((s) => getAccount(s, accountId)); const account = useAppSelector((s) => getAccount(s, accountId));
const dispatch = useAppDispatch();
const handleFollow = useCallback(() => {
if (!account) return;
if (
account.getIn(['relationship', 'following']) ||
account.getIn(['relationship', 'requested'])
) {
dispatch(
openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }),
);
} else {
dispatch(followAccount(account.get('id')));
}
}, [account, dispatch]);
const handleBlock = useCallback(() => {
if (account?.relationship?.blocking) {
dispatch(unblockAccount(account.get('id')));
}
}, [account, dispatch]);
const handleMute = useCallback(() => {
if (account?.relationship?.muting) {
dispatch(unmuteAccount(account.get('id')));
}
}, [account, dispatch]);
const handleEditProfile = useCallback(() => {
window.open('/settings/profile', '_blank');
}, []);
if (!account) return null; if (!account) return null;
let actionBtn;
if (me !== account.get('id')) {
if (!account.get('relationship')) {
// Wait until the relationship is loaded
actionBtn = '';
} else if (account.getIn(['relationship', 'requested'])) {
actionBtn = (
<Button
text={intl.formatMessage(messages.cancel_follow_request)}
title={intl.formatMessage(messages.requested)}
onClick={handleFollow}
/>
);
} else if (account.getIn(['relationship', 'muting'])) {
actionBtn = (
<Button
text={intl.formatMessage(messages.unmute)}
onClick={handleMute}
/>
);
} else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<Button
disabled={account.relationship?.blocked_by}
className={classNames({
'button--destructive': account.getIn(['relationship', 'following']),
})}
text={intl.formatMessage(
account.getIn(['relationship', 'following'])
? messages.unfollow
: messages.follow,
)}
onClick={handleFollow}
/>
);
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<Button
text={intl.formatMessage(messages.unblock)}
onClick={handleBlock}
/>
);
}
} else {
actionBtn = (
<Button
text={intl.formatMessage(messages.edit_profile)}
onClick={handleEditProfile}
/>
);
}
return ( return (
<div className='account-card'> <div className='account-card'>
<Link to={`/@${account.get('acct')}`} className='account-card__permalink'> <Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
@ -186,7 +75,9 @@ export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
</div> </div>
</div> </div>
<div className='account-card__actions__button'>{actionBtn}</div> <div className='account-card__actions__button'>
<FollowButton accountId={account.get('id')} />
</div>
</div> </div>
</div> </div>
); );

View File

@ -25,8 +25,6 @@ import { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({ const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
next: { id: 'lightbox.next', defaultMessage: 'Next' }, next: { id: 'lightbox.next', defaultMessage: 'Next' },
dismiss: { dismiss: {

View File

@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
const breakpoints = { const breakpoints = {
narrow: 479, // Device width under which horizontal space is constrained
openable: 759, // Device width at which the sidebar becomes an openable hamburger menu openable: 759, // Device width at which the sidebar becomes an openable hamburger menu
full: 1174, // Device width at which all 3 columns can be displayed full: 1174, // Device width at which all 3 columns can be displayed
}; };
type Breakpoint = 'openable' | 'full'; type Breakpoint = keyof typeof breakpoints;
export const useBreakpoint = (breakpoint: Breakpoint) => { export const useBreakpoint = (breakpoint: Breakpoint) => {
const [isMatching, setIsMatching] = useState(false); const [isMatching, setIsMatching] = useState(false);

View File

@ -28,6 +28,7 @@
"account.disable_notifications": "Stop notifying me when @{name} posts", "account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocking": "Blocking domain", "account.domain_blocking": "Blocking domain",
"account.edit_profile": "Edit profile", "account.edit_profile": "Edit profile",
"account.edit_profile_short": "Edit",
"account.enable_notifications": "Notify me when @{name} posts", "account.enable_notifications": "Notify me when @{name} posts",
"account.endorse": "Feature on profile", "account.endorse": "Feature on profile",
"account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}", "account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}",
@ -40,6 +41,11 @@
"account.featured_tags.last_status_never": "No posts", "account.featured_tags.last_status_never": "No posts",
"account.follow": "Follow", "account.follow": "Follow",
"account.follow_back": "Follow back", "account.follow_back": "Follow back",
"account.follow_back_short": "Follow back",
"account.follow_request": "Request to follow",
"account.follow_request_cancel": "Cancel request",
"account.follow_request_cancel_short": "Cancel",
"account.follow_request_short": "Request",
"account.followers": "Followers", "account.followers": "Followers",
"account.followers.empty": "No one follows this user yet.", "account.followers.empty": "No one follows this user yet.",
"account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}",
@ -70,7 +76,6 @@
"account.posts_with_replies": "Posts and replies", "account.posts_with_replies": "Posts and replies",
"account.remove_from_followers": "Remove {name} from followers", "account.remove_from_followers": "Remove {name} from followers",
"account.report": "Report @{name}", "account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.requested_follow": "{name} has requested to follow you", "account.requested_follow": "{name} has requested to follow you",
"account.requests_to_follow_you": "Requests to follow you", "account.requests_to_follow_you": "Requests to follow you",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",