Merge remote-tracking branch 'mkljczk/develop' into develop

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-15 12:06:37 +01:00
8 changed files with 86 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ import { GOTOSOCIAL, MASTODON } from 'pl-api';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeSetting } from '@/actions/settings';
import DropdownMenu, { type Menu } from '@/components/dropdown-menu';
import IconButton from '@/components/ui/icon-button';
import { useClient } from '@/hooks/use-client';
@@ -86,6 +87,10 @@ const messages = defineMessages({
id: 'account.load_activities.fail',
defaultMessage: 'Failed to fetch latest posts',
},
nickname: { id: 'account.nickname.modal_header', defaultMessage: 'Set nickname for @{name}' },
nicknamePlaceholder: { id: 'account.nickname.placeholder', defaultMessage: 'Enter a nickname' },
nicknameSave: { id: 'account.nickname.save', defaultMessage: 'Save nickname' },
nicknameSaved: { id: 'account.nickname.success', defaultMessage: 'Nickname saved' },
note: { id: 'account_note.modal_header', defaultMessage: 'Edit note for @{name}' },
notePlaceholder: { id: 'account_note.placeholder', defaultMessage: 'Add a note' },
noteSaved: { id: 'account_note.success', defaultMessage: 'Note saved' },
@@ -206,6 +211,28 @@ const AccountMenu: React.FC<IAccountMenu> = ({ account }) => {
});
};
const onEditNickname = () => {
const currentNickname = settings.accountNicknames[account.id] ?? '';
openModal('TEXT_FIELD', {
heading: (
<FormattedMessage
id='account.nickname.modal_header'
defaultMessage='Set nickname for @{name}'
values={{ name: account.acct }}
/>
),
placeholder: intl.formatMessage(messages.nicknamePlaceholder),
confirm: <FormattedMessage id='account.nickname.save' defaultMessage='Save nickname' />,
onConfirm: (value) => {
const trimmed = value.trim();
changeSetting(['accountNicknames', account.id], trimmed || undefined);
toast.success(messages.nicknameSaved);
},
text: currentNickname,
singleLine: true,
});
};
const onReport = () => {
openModal('REPORT', { accountId: account.id });
};
@@ -455,6 +482,12 @@ const AccountMenu: React.FC<IAccountMenu> = ({ account }) => {
});
}
menu.push({
text: intl.formatMessage(messages.nickname, { name: account.acct }),
action: onEditNickname,
icon: require('@phosphor-icons/core/regular/tag.svg'),
});
menu.push(null);
if (features.removeFromFollowers && account.relationship?.followed_by) {

View File

@@ -33,10 +33,14 @@ const messages = defineMessages({
deactivated: { id: 'account.deactivated', defaultMessage: 'Deactivated' },
bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
pronouns: { id: 'account.pronouns.with_label', defaultMessage: 'Pronouns: {pronouns}' },
originalDisplayName: {
id: 'account.original_display_name',
defaultMessage: 'You have assigned a nickname to this user.',
},
});
interface IProfileInfoPanel {
account?: Account;
account?: Account & { original_display_name?: string };
/** Username from URL params, in case the account isn't found. */
username: string;
}
@@ -177,6 +181,19 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
)}
</Text>
{account.original_display_name &&
account.original_display_name !== account.display_name && (
<Text
theme='muted'
truncate
title={intl.formatMessage(messages.originalDisplayName)}
>
{'('}
<Emojify text={account.original_display_name} emojis={account.emojis} />
{')'}
</Text>
)}
{account.bot && <Badge slug='bot' title={intl.formatMessage(messages.bot)} />}
{badges.length > 0 && <div className='flex items-center gap-1'>{badges}</div>}

View File

@@ -20,6 +20,10 @@ const messages = defineMessages({
defaultMessage:
'This account privacy status is set to locked. The owner manually reviews who can follow them.',
},
originalDisplayName: {
id: 'account.original_display_name',
defaultMessage: 'You have assigned a nickname to this user.',
},
});
interface IUserPanel {
@@ -83,6 +87,19 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
<Emojify text={account.display_name} emojis={account.emojis} />
</Text>
{account.original_display_name &&
account.original_display_name !== account.display_name && (
<Text
theme='muted'
truncate
title={intl.formatMessage(messages.originalDisplayName)}
>
{'('}
<Emojify text={account.original_display_name} emojis={account.emojis} />
{')'}
</Text>
)}
{verified && <VerificationBadge />}
{badges && badges.length > 0 && (

View File

@@ -146,6 +146,9 @@ const layouts = {
getParentRoute: () => rootRoute,
path: '/@{$username}',
component: ProfileLayout,
validateSearch: v.object({
with_replies: v.optional(v.boolean()),
}),
}),
remoteInstance: createRoute({
getParentRoute: () => rootRoute,

View File

@@ -26,6 +26,7 @@ import { LOCAL_STORAGE_REDIRECT_KEY } from '@/utils/redirect';
/** Layout to display a user's profile. */
const ProfileLayout: React.FC = () => {
const { username } = layouts.profile.useParams();
const { with_replies: withReplies } = layouts.profile.useSearch();
const location = useLocation();
const { data: account, isUnauthorized } = useAccountLookup(username, true);
@@ -80,7 +81,7 @@ const ProfileLayout: React.FC = () => {
let activeItem;
const pathname = location.pathname.replace(`@${username}/`, '');
if (pathname.endsWith('/with_replies')) {
if (withReplies) {
activeItem = 'replies';
} else if (pathname.endsWith('/media')) {
activeItem = 'media';

View File

@@ -53,6 +53,11 @@
"account.mute": "Mute @{name}",
"account.muted": "Muted",
"account.never_active": "Never",
"account.nickname.modal_header": "Set nickname for @{name}",
"account.nickname.placeholder": "Enter a nickname",
"account.nickname.save": "Save nickname",
"account.nickname.success": "Nickname saved",
"account.original_display_name": "You have assigned a nickname to this user.",
"account.posts": "Posts",
"account.posts_with_replies": "Posts & replies",
"account.profile": "Profile",

View File

@@ -7,6 +7,7 @@ import { useLoggedIn } from '@/hooks/use-logged-in';
import { useCredentialAccount } from '@/queries/accounts/use-account-credentials';
import { useRelationshipQuery } from '@/queries/accounts/use-relationship';
import { queryKeys } from '@/queries/keys';
import { useSettings } from '@/stores/settings';
import type { NicoliumResponse } from '@/api';
@@ -30,6 +31,9 @@ const useAccount = (accountId?: string, withRelationship = false) => {
const features = useFeatures();
const { me } = useLoggedIn();
const queryClient = useQueryClient();
const { accountNicknames } = useSettings();
const nickname = accountNicknames[accountId ?? ''];
const accountQuery = useQuery({
queryKey: queryKeys.accounts.show(accountId!),
@@ -63,19 +67,14 @@ const useAccount = (accountId?: string, withRelationship = false) => {
const mergedRelationship = relationship ?? accountQuery.data.relationship;
const mergedIsAdmin = credentialIsAdmin ?? accountQuery.data.is_admin;
if (
mergedRelationship === accountQuery.data.relationship &&
mergedIsAdmin === accountQuery.data.is_admin
) {
return accountQuery.data;
}
return {
...accountQuery.data,
display_name: nickname ?? accountQuery.data.display_name,
original_display_name: accountQuery.data.display_name,
relationship: mergedRelationship,
is_admin: mergedIsAdmin,
};
}, [accountQuery.data, relationship, credentialIsAdmin]);
}, [accountQuery.data, relationship, credentialIsAdmin, nickname]);
return {
...accountQuery,

View File

@@ -55,6 +55,7 @@ const settingsSchema = v.object({
storeSettingsInNotes: v.fallback(v.boolean(), false),
composeInTimelines: v.fallback(v.boolean(), true),
rememberTimelinePosition: v.fallback(v.boolean(), true),
accountNicknames: v.fallback(v.record(v.string(), v.string()), {}),
theme: v.optional(
coerceObject({