From 54152702323423fa373ea719db1bfb13928df870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sat, 14 Mar 2026 11:55:47 +0100 Subject: [PATCH] nicolium: Allow assigning nicknames to users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../src/components/birthday-input.tsx | 2 +- .../src/components/statuses/status.tsx | 48 ++++++------------- .../account/components/account-menu.tsx | 35 ++++++++++++++ .../components/panels/profile-info-panel.tsx | 19 +++++++- .../ui/components/panels/user-panel.tsx | 17 +++++++ packages/nicolium/src/locales/en.json | 5 ++ .../src/pages/dashboard/frontend-config.tsx | 1 - .../src/queries/accounts/use-account.ts | 15 +++--- .../src/queries/statuses/use-status.ts | 1 - .../nicolium/src/schemas/frontend-settings.ts | 1 + 10 files changed, 99 insertions(+), 45 deletions(-) diff --git a/packages/nicolium/src/components/birthday-input.tsx b/packages/nicolium/src/components/birthday-input.tsx index 4206af90d..89fb48add 100644 --- a/packages/nicolium/src/components/birthday-input.tsx +++ b/packages/nicolium/src/components/birthday-input.tsx @@ -4,7 +4,7 @@ import { defineMessages, FormattedDate, useIntl } from 'react-intl'; import IconButton from '@/components/ui/icon-button'; import { DatePicker } from '@/features/ui/util/async-components'; import { useFeatures } from '@/hooks/use-features'; -import { useInstance } from '@/stores/instance'; +import { useInstance } from '@/hooks/use-instance'; const messages = defineMessages({ birthdayPlaceholder: { diff --git a/packages/nicolium/src/components/statuses/status.tsx b/packages/nicolium/src/components/statuses/status.tsx index deef148f2..8ee9cedc7 100644 --- a/packages/nicolium/src/components/statuses/status.tsx +++ b/packages/nicolium/src/components/statuses/status.tsx @@ -415,39 +415,21 @@ const Status: React.FC = React.memo((props) => { /> ); } else if (isReblog) { - const accounts = status.accounts ?? [status.account]; - - const renderedAccounts = accounts.slice(0, 2).map( - (account) => - !!account && ( - - - - - - - - ), - ); - - if (accounts.length > 2) { - renderedAccounts.push( - , - ); - } - const values = { - name: , - count: accounts.length, + name: ( + + + + + + + + ), }; return ( @@ -535,7 +517,7 @@ const Status: React.FC = React.memo((props) => { ) ); } - }, [status.accounts, group?.id]); + }, [status.account, group?.id]); if (!status) return null; diff --git a/packages/nicolium/src/features/account/components/account-menu.tsx b/packages/nicolium/src/features/account/components/account-menu.tsx index a753eefef..4fbaf2500 100644 --- a/packages/nicolium/src/features/account/components/account-menu.tsx +++ b/packages/nicolium/src/features/account/components/account-menu.tsx @@ -4,8 +4,10 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { initReport, ReportableEntities } from '@/actions/reports'; +import { changeSetting } from '@/actions/settings'; import DropdownMenu, { type Menu } from '@/components/dropdown-menu'; import IconButton from '@/components/ui/icon-button'; +import { useAppDispatch } from '@/hooks/use-app-dispatch'; import { useClient } from '@/hooks/use-client'; import { useFeatures } from '@/hooks/use-features'; import { useOwnAccount } from '@/hooks/use-own-account'; @@ -91,6 +93,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' }, @@ -105,6 +111,7 @@ interface IAccountMenu { const AccountMenu: React.FC = ({ account }) => { const intl = useIntl(); + const dispatch = useAppDispatch(); const { mentionCompose, directCompose } = useComposeActions(); const client = useClient(); @@ -212,6 +219,28 @@ const AccountMenu: React.FC = ({ account }) => { }); }; + const onEditNickname = () => { + const currentNickname = settings.accountNicknames[account.id] ?? ''; + openModal('TEXT_FIELD', { + heading: ( + + ), + placeholder: intl.formatMessage(messages.nicknamePlaceholder), + confirm: , + onConfirm: (value) => { + const trimmed = value.trim(); + dispatch(changeSetting(['accountNicknames', account.id], trimmed || undefined)); + toast.success(messages.nicknameSaved); + }, + text: currentNickname, + singleLine: true, + }); + }; + const onReport = () => { initReport(ReportableEntities.ACCOUNT, account); }; @@ -461,6 +490,12 @@ const AccountMenu: React.FC = ({ 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) { diff --git a/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx b/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx index 4285cf0ca..115440afa 100644 --- a/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/profile-info-panel.tsx @@ -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 = ({ account, username }) => )} + {account.original_display_name && + account.original_display_name !== account.display_name && ( + + {'('} + + {')'} + + )} + {account.bot && } {badges.length > 0 &&
{badges}
} diff --git a/packages/nicolium/src/features/ui/components/panels/user-panel.tsx b/packages/nicolium/src/features/ui/components/panels/user-panel.tsx index 866491346..72a71924f 100644 --- a/packages/nicolium/src/features/ui/components/panels/user-panel.tsx +++ b/packages/nicolium/src/features/ui/components/panels/user-panel.tsx @@ -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 = ({ accountId, action, badges, domain }) + {account.original_display_name && + account.original_display_name !== account.display_name && ( + + {'('} + + {')'} + + )} + {verified && } {badges && badges.length > 0 && ( diff --git a/packages/nicolium/src/locales/en.json b/packages/nicolium/src/locales/en.json index b7119434b..d068e0815 100644 --- a/packages/nicolium/src/locales/en.json +++ b/packages/nicolium/src/locales/en.json @@ -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", diff --git a/packages/nicolium/src/pages/dashboard/frontend-config.tsx b/packages/nicolium/src/pages/dashboard/frontend-config.tsx index 7b6217adb..3adee3eeb 100644 --- a/packages/nicolium/src/pages/dashboard/frontend-config.tsx +++ b/packages/nicolium/src/pages/dashboard/frontend-config.tsx @@ -69,7 +69,6 @@ const FrontendConfigEditor: React.FC = () => { const features = useFeatures(); const initialData = useAppSelector((state) => state.frontendConfig); - console.log(initialData); const { mutate: updateConfig, isPending } = useUpdateAdminConfig(); const [data, setData] = useState(v.parse(frontendConfigSchema, initialData)); diff --git a/packages/nicolium/src/queries/accounts/use-account.ts b/packages/nicolium/src/queries/accounts/use-account.ts index 89b19f09a..0a3ad1f2d 100644 --- a/packages/nicolium/src/queries/accounts/use-account.ts +++ b/packages/nicolium/src/queries/accounts/use-account.ts @@ -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, diff --git a/packages/nicolium/src/queries/statuses/use-status.ts b/packages/nicolium/src/queries/statuses/use-status.ts index 3a0008322..4792bf417 100644 --- a/packages/nicolium/src/queries/statuses/use-status.ts +++ b/packages/nicolium/src/queries/statuses/use-status.ts @@ -66,7 +66,6 @@ const useStatusQuery = (statusId?: string) => { data: { ...statusQuery.data, account: account.data!, - accounts: [account.data!], }, }; }, [statusQuery.data, account.data]) as unknown as UseQueryResult; diff --git a/packages/nicolium/src/schemas/frontend-settings.ts b/packages/nicolium/src/schemas/frontend-settings.ts index 6e52a4cf6..143cf6a86 100644 --- a/packages/nicolium/src/schemas/frontend-settings.ts +++ b/packages/nicolium/src/schemas/frontend-settings.ts @@ -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({