mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-13 13:46:57 -05:00
Add basic UI support for invalid handles in Web UI (#40045)
This commit is contained in:
@@ -71,6 +71,7 @@ export interface BaseApiAccountJSON {
|
||||
memorial?: boolean;
|
||||
hide_collections: boolean;
|
||||
email_subscriptions?: boolean;
|
||||
invalid_handle?: boolean;
|
||||
}
|
||||
|
||||
// See app/serializers/rest/muted_account_serializer.rb
|
||||
|
||||
@@ -226,6 +226,10 @@ const redesignMessages = defineMessages({
|
||||
id: 'account.menu.open_original_page',
|
||||
defaultMessage: 'View on {domain}',
|
||||
},
|
||||
openOriginalPageInvalid: {
|
||||
id: 'account.menu.open_original_page_no_domain',
|
||||
defaultMessage: 'View on original server',
|
||||
},
|
||||
removeFollower: {
|
||||
id: 'account.menu.remove_follower',
|
||||
defaultMessage: 'Remove follower',
|
||||
@@ -270,32 +274,37 @@ function getMenuItems({
|
||||
// Open on remote page.
|
||||
if (isRemote) {
|
||||
items.push({
|
||||
text: intl.formatMessage(redesignMessages.openOriginalPage, {
|
||||
domain: remoteDomain,
|
||||
}),
|
||||
text: account.invalid_handle
|
||||
? intl.formatMessage(redesignMessages.openOriginalPageInvalid)
|
||||
: intl.formatMessage(redesignMessages.openOriginalPage, {
|
||||
domain: remoteDomain,
|
||||
}),
|
||||
href: account.url,
|
||||
});
|
||||
}
|
||||
|
||||
// Mention and direct message options
|
||||
if (signedIn && !account.suspended) {
|
||||
items.push(
|
||||
null,
|
||||
{
|
||||
text: intl.formatMessage(redesignMessages.mention),
|
||||
action: () => {
|
||||
dispatch(mentionCompose(account));
|
||||
if (account.invalid_handle) items.push(null);
|
||||
else {
|
||||
items.push(
|
||||
null,
|
||||
{
|
||||
text: intl.formatMessage(redesignMessages.mention),
|
||||
action: () => {
|
||||
dispatch(mentionCompose(account));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
text: intl.formatMessage(redesignMessages.direct),
|
||||
action: () => {
|
||||
dispatch(directCompose(account));
|
||||
{
|
||||
text: intl.formatMessage(redesignMessages.direct),
|
||||
action: () => {
|
||||
dispatch(directCompose(account));
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
);
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!signedIn) {
|
||||
@@ -484,7 +493,7 @@ function getMenuItems({
|
||||
});
|
||||
}
|
||||
|
||||
if (remoteDomain) {
|
||||
if (remoteDomain && !account.invalid_handle) {
|
||||
items.push(null, {
|
||||
text: intl.formatMessage(
|
||||
relationship?.domain_blocking
|
||||
@@ -524,6 +533,7 @@ function getMenuItems({
|
||||
}
|
||||
if (
|
||||
remoteDomain &&
|
||||
!account.invalid_handle &&
|
||||
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||
PERMISSION_MANAGE_FEDERATION
|
||||
) {
|
||||
|
||||
@@ -62,17 +62,84 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
{relationship?.followed_by && <FollowsYouBadge />}
|
||||
</div>
|
||||
|
||||
<AccountNameHelp
|
||||
username={username}
|
||||
domain={domain}
|
||||
isSelf={account.id === me}
|
||||
/>
|
||||
{account.invalid_handle ? (
|
||||
<InvalidAccountHelp />
|
||||
) : (
|
||||
<AccountNameHelp
|
||||
username={username}
|
||||
domain={domain}
|
||||
isSelf={account.id === me}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AccountBadges accountId={accountId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InvalidAccountHelp: FC = () => {
|
||||
const accessibilityId = useId();
|
||||
const intl = useIntl();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [triggerElement, setTriggerElement] =
|
||||
useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setOpen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type='button'
|
||||
ref={setTriggerElement}
|
||||
className={classNames(classes.handleHelpButton)}
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account.hame.invalid_handle'
|
||||
defaultMessage='Handle unavailable'
|
||||
/>
|
||||
|
||||
<Icon
|
||||
id='help'
|
||||
icon={HelpIcon}
|
||||
aria-label={intl.formatMessage(messages.nameInfo)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<Popover
|
||||
isOpen={open}
|
||||
reference={triggerElement}
|
||||
onClose={handleClick}
|
||||
offset={5}
|
||||
>
|
||||
{({ props }) => (
|
||||
<div
|
||||
{...props}
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
className={classNames('dropdown-animation', classes.handleHelp)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account.name.help.invalid_header'
|
||||
defaultMessage="This user's handle is being updated"
|
||||
tagName='h3'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='account.name.help.invalid_explanation'
|
||||
defaultMessage='This can happen when a user changes username, and is generally temporary. If this persists, it may be because of an unavailable server or some misconfiguration on their end.'
|
||||
tagName='p'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AccountNameHelp: FC<{
|
||||
username: string;
|
||||
domain: string;
|
||||
|
||||
@@ -142,7 +142,8 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
> ol {
|
||||
> ol,
|
||||
> p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ComponentPropsWithoutRef, FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Skeleton } from '../skeleton';
|
||||
|
||||
import type { DisplayNameProps } from './index';
|
||||
import { DisplayNameWithoutDomain } from './no-domain';
|
||||
|
||||
const messages = defineMessages({
|
||||
invalidHandle: {
|
||||
id: 'account.hame.invalid_handle',
|
||||
defaultMessage: 'Handle unavailable',
|
||||
},
|
||||
});
|
||||
|
||||
export function useAccountHandle(
|
||||
account: DisplayNameProps['account'],
|
||||
localDomain: DisplayNameProps['localDomain'],
|
||||
) {
|
||||
const intl = useIntl();
|
||||
|
||||
return useMemo(() => {
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (account.invalid_handle)
|
||||
return intl.formatMessage(messages.invalidHandle);
|
||||
|
||||
let acct = account.acct;
|
||||
|
||||
if (!acct.includes('@') && localDomain) {
|
||||
acct = `${acct}@${localDomain}`;
|
||||
}
|
||||
|
||||
return `@${acct}`;
|
||||
}, [account, localDomain]);
|
||||
}, [account, localDomain, intl]);
|
||||
}
|
||||
|
||||
export const DisplayNameDefault: FC<
|
||||
|
||||
@@ -465,23 +465,25 @@ function getMenuItems({
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.mention, {
|
||||
name: account.username,
|
||||
}),
|
||||
action: () => {
|
||||
dispatch(mentionCompose(account));
|
||||
},
|
||||
});
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.direct, {
|
||||
name: account.username,
|
||||
}),
|
||||
action: () => {
|
||||
dispatch(directCompose(account));
|
||||
},
|
||||
});
|
||||
menu.push(null);
|
||||
if (!account.invalid_handle) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.mention, {
|
||||
name: account.username,
|
||||
}),
|
||||
action: () => {
|
||||
dispatch(mentionCompose(account));
|
||||
},
|
||||
});
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.direct, {
|
||||
name: account.username,
|
||||
}),
|
||||
action: () => {
|
||||
dispatch(directCompose(account));
|
||||
},
|
||||
});
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (interactions.revokeQuote) {
|
||||
menu.push({
|
||||
|
||||
@@ -314,9 +314,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
if (!account.get('invalid_handle')) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (isQuotingMe) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
@@ -342,7 +344,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
if (account.get('acct') !== account.get('username') && !account.get('invalid_handle')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
menu.push(null);
|
||||
|
||||
@@ -266,8 +266,10 @@ class ActionBar extends PureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push(null);
|
||||
if (!account.get('invalid_handle')) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (quotedAccountId === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
@@ -287,7 +289,7 @@ class ActionBar extends PureComponent {
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
if (account.get('acct') !== account.get('username') && !account.get('invalid_handle')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
menu.push(null);
|
||||
@@ -307,6 +309,7 @@ class ActionBar extends PureComponent {
|
||||
}
|
||||
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.go_to_profile": "Go to profile",
|
||||
"account.hame.invalid_handle": "Handle unavailable",
|
||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.join_modal.day": "Day",
|
||||
@@ -98,6 +99,7 @@
|
||||
"account.menu.mute": "Mute account",
|
||||
"account.menu.note.description": "Visible only to you",
|
||||
"account.menu.open_original_page": "View on {domain}",
|
||||
"account.menu.open_original_page_no_domain": "View on original server",
|
||||
"account.menu.remove_follower": "Remove follower",
|
||||
"account.menu.report": "Report account",
|
||||
"account.menu.share": "Share…",
|
||||
@@ -116,6 +118,8 @@
|
||||
"account.name.help.domain_self": "{domain} is your server that hosts your profile and posts.",
|
||||
"account.name.help.footer": "Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Fediverse apps.",
|
||||
"account.name.help.header": "A handle is like an email address",
|
||||
"account.name.help.invalid_explanation": "This can happen when a user changes username, and is generally temporary. If this persists, it may be because of an unavailable server or some misconfiguration on their end.",
|
||||
"account.name.help.invalid_header": "This user's handle is being updated",
|
||||
"account.name.help.username": "{username} is this account’s username on their server. Someone on another server might have the same username.",
|
||||
"account.name.help.username_self": "{username} is your username on this server. Someone on another server might have the same username.",
|
||||
"account.name_info": "What does this mean?",
|
||||
|
||||
@@ -112,6 +112,7 @@ export const accountDefaultValues: AccountShape = {
|
||||
moved: null,
|
||||
hide_collections: false,
|
||||
email_subscriptions: false,
|
||||
invalid_handle: false,
|
||||
// This comes from `ApiMutedAccountJSON`, but we should eventually
|
||||
// store that in a different object.
|
||||
mute_expires_at: null,
|
||||
|
||||
Reference in New Issue
Block a user