fix(app): show the full user tag beside display names instead of a bare discriminator (#1696)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-17 14:46:06 +02:00
1 parent 10ae4bfe1e
commit 3fa766c39c
24 files changed
+53 -58

No files matched your search

@@ -13,6 +13,7 @@ import Relationships from '@app/features/relationship/state/Relationships';
import Slowmode from '@app/features/slowmode/state/Slowmode';
import {useNow} from '@app/features/ui/state/Tick';
import Users from '@app/features/user/state/Users';
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
import {CHANNEL_RATE_LIMIT_PER_USER_MAX} from '@fluxer/constants/src/LimitConstants';
@@ -63,7 +64,7 @@ function resolveChannelDisplayName(channel: Channel, i18n: I18n): string {
if (channel.type === ChannelTypes.DM) {
const recipient = channel.recipientIds.length > 0 ? Users.getUser(channel.recipientIds[0]) : null;
if (recipient) {
return Relationships.getRelationship(recipient.id)?.nickname || recipient.displayName;
return NicknameUtils.getNickname(recipient, null);
}
}
if (
@@ -236,6 +236,8 @@
}
.accountMenuDiscriminator {
margin-left: 0.25rem;
font-size: 0.75rem;
color: var(--text-tertiary);
font-weight: 400;
}
@@ -301,7 +301,10 @@ const SwitchAccountsMenu = observer(
const isCurrent = account.userId === currentAccountId;
const avatarUrl = getAccountAvatarUrl(account);
const displayName = getAccountDisplayName(account, '???');
const discriminator = account.userData?.discriminator ?? '0000';
const userData = account.userData;
const accountTag = userData
? NicknameUtils.formatTagForStreamerMode(`${userData.username}#${userData.discriminator}`)
: displayName;
return (
<FocusRing
key={account.userId}
@@ -344,7 +347,7 @@ const SwitchAccountsMenu = observer(
className={styles.accountMenuDiscriminator}
data-flx="app.floating.user-area-popout.switch-accounts-menu.account-menu-discriminator"
>
#{discriminator}
{accountTag}
</span>
</span>
{isCurrent && (
@@ -37,9 +37,9 @@ const OPEN_PROFILE_FOR_DESCRIPTOR = msg({
comment: 'Short label in the app layout grouped voice participant. Preserve {nickname}; it is inserted by code.',
});
function useOpenProfileAriaLabel(user: User) {
function useOpenProfileAriaLabel(user: User, guildId: string) {
const {i18n} = useLingui();
const nickname = NicknameUtils.getNickname(user);
const nickname = NicknameUtils.getNickname(user, guildId);
return useMemo(() => i18n._(OPEN_PROFILE_FOR_DESCRIPTOR, {nickname}), [nickname, i18n.locale]);
}
@@ -56,7 +56,7 @@ export const GroupedVoiceParticipant = observer(function GroupedVoiceParticipant
guildId,
anySpeaking: propAnySpeaking,
}: GroupedVoiceParticipantProps) {
const openProfileAriaLabel = useOpenProfileAriaLabel(user);
const openProfileAriaLabel = useOpenProfileAriaLabel(user, guildId);
const mediaEngineVersion = useMediaEngineVersion();
const [isExpanded, setIsExpanded] = useState(false);
const currentUser = Users.getCurrentUser();
@@ -77,7 +77,7 @@ export const GroupedVoiceParticipant = observer(function GroupedVoiceParticipant
ContextMenuCommands.openFromEvent(event, ({onClose}) => (
<VoiceParticipantContextMenu
user={user}
participantName={NicknameUtils.getNickname(user)}
participantName={NicknameUtils.getNickname(user, guildId)}
onClose={onClose}
guildId={guildId}
isGroupedItem={true}
@@ -244,7 +244,7 @@ export const GroupedVoiceParticipant = observer(function GroupedVoiceParticipant
)}
data-flx="app.grouped-voice-participant.participant-name"
>
{NicknameUtils.getNickname(user)}
{NicknameUtils.getNickname(user, guildId)}
</span>
<Tooltip
text={
@@ -127,7 +127,7 @@ const ResolvedDMListItem = observer(function ResolvedDMListItem({
const isMuted = UserGuildSettings.isChannelMuted(null, channel.id);
const mentionCount = ReadStates.getMentionCount(channel.id);
const unreadState = resolveDMListItemUnreadState(channel.id);
const directMessageName = recipient ? NicknameUtils.getNickname(recipient) : null;
const directMessageName = recipient ? NicknameUtils.getNickname(recipient, null, channel.id) : null;
const computedDisplayName = ChannelUtils.getDMDisplayName(channel);
const displayName = isGroupDM ? computedDisplayName : (directMessageName ?? computedDisplayName);
const hasActiveCall = CallState.hasActiveCall(channel.id);
@@ -85,6 +85,7 @@ button.clickable:active {
}
.discriminator {
margin-left: 0.25rem;
color: var(--text-tertiary);
font-weight: 400;
}
@@ -7,6 +7,7 @@ import type {Account} from '@app/features/platform/state/AuthSession';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {MockAvatar} from '@app/features/ui/components/MockAvatar';
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
import {CaretRightIcon, CheckIcon, DotsThreeIcon} from '@phosphor-icons/react';
@@ -52,7 +53,10 @@ export const AccountRow = observer(
const {i18n} = useLingui();
const avatarUrl = getAccountAvatarUrl(account);
const displayName = getAccountDisplayName(account, '???');
const discriminator = account.userData?.discriminator ?? '0000';
const userData = account.userData;
const accountTag = userData
? NicknameUtils.formatTagForStreamerMode(`${userData.username}#${userData.discriminator}`)
: displayName;
const menuButtonRef = useRef<HTMLButtonElement | null>(null);
const isContextMenuOpen = useContextMenuHoverState(menuButtonRef, Boolean(onMenuClick));
const handleMenuClick = useCallback(
@@ -96,7 +100,7 @@ export const AccountRow = observer(
>
{displayName}
<span className={styles.discriminator} data-flx="auth.accounts.account-row.discriminator">
#{discriminator}
{accountTag}
</span>
</span>
</div>
@@ -110,7 +114,7 @@ export const AccountRow = observer(
>
{displayName}
<span className={styles.discriminator} data-flx="auth.accounts.account-row.discriminator--2">
#{discriminator}
{accountTag}
</span>
</span>
) : (
@@ -124,10 +128,7 @@ export const AccountRow = observer(
</div>
{variant !== 'manage' ? (
<span className={clsx('user-text', styles.tag)} data-flx="auth.accounts.account-row.user-text--4">
{displayName}
<span className={styles.discriminator} data-flx="auth.accounts.account-row.discriminator--3">
#{discriminator}
</span>
{accountTag}
</span>
) : null}
{variant === 'manage' && isCurrent ? (
@@ -83,7 +83,7 @@ export const AutocompleteMention = observer(function AutocompleteMention({
const routeParams = useParams() as {guildId?: string};
const guildId = routeParams.guildId;
const guild = Guilds.getGuild(guildId == null ? '' : guildId);
const nicknameGuildId = guild == null ? undefined : guild.id;
const nicknameGuildId = guild == null ? null : guild.id;
const members = options.filter(isMentionMember);
const users = options.filter(isMentionUser);
const roles = options.filter(isMentionRole);
@@ -43,7 +43,7 @@ const HANDFUL_DESCRIPTOR = A_HANDFUL_OF_KEYBOARD_WARRIORS_ARE_ASSEMBLING_DESCRIP
const SYMPHONY_DESCRIPTOR = A_SYMPHONY_OF_CLACKING_KEYS_IS_UNDERWAY_DESCRIPTOR;
const FIESTA_DESCRIPTOR = IT_S_A_FULL_BLOWN_TYPING_FIESTA_IN_DESCRIPTOR;
const APOCALYPSE_DESCRIPTOR = WHOA_IT_S_A_TYPING_APOCALYPSE_DESCRIPTOR;
const getDisplayName = (user: User, guildId?: string | null) => NicknameUtils.getNickname(user, guildId ?? undefined);
const getDisplayName = (user: User, guildId?: string | null) => NicknameUtils.getNickname(user, guildId ?? null);
export const getTypingText = (i18n: I18n, typingUsers: ReadonlyArray<User>, channel: Channel) => {
const [a, b, c] = typingUsers.map((user) => {
const member = GuildMembers.getMember(channel.guildId ?? '', user.id);
@@ -458,6 +458,7 @@
align-items: center;
min-width: 0;
overflow: hidden;
gap: 0.35rem;
}
.channelInfoUsername {
@@ -471,9 +472,9 @@
}
.channelInfoDiscriminator {
font-weight: 600;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 400;
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--text-tertiary);
flex-shrink: 0;
}
@@ -35,7 +35,7 @@ export const ChannelInfoHeader: React.FC<ChannelInfoHeaderProps> = ({
const isDM = channel.type === ChannelTypes.DM;
const isPersonalNotes = channel.type === ChannelTypes.DM_PERSONAL_NOTES;
const isGroupDM = channel.type === ChannelTypes.GROUP_DM;
const recipientDisplayName = recipient ? NicknameUtils.getNickname(recipient) : '';
const recipientDisplayName = recipient ? NicknameUtils.getNickname(recipient, null, channel.id) : '';
return (
<div className={styles.channelInfoSection} data-flx="channel.channel-details-bottom-sheet.channel-info-section">
<Sheet.CloseButton
@@ -83,7 +83,7 @@ export const ChannelInfoHeader: React.FC<ChannelInfoHeaderProps> = ({
className={styles.channelInfoDiscriminator}
data-flx="channel.channel-details-bottom-sheet.channel-info-discriminator"
>
#{recipient.discriminator}
{NicknameUtils.formatUserTagForStreamerMode(recipient)}
</span>
</div>
{recipient.bot && (
@@ -39,7 +39,7 @@ export const DMMembersList: React.FC<DMMembersListProps> = ({
const {i18n} = useLingui();
const isDM = channel.type === ChannelTypes.DM;
const isGroupDM = channel.type === ChannelTypes.GROUP_DM;
const recipientDisplayName = recipient ? NicknameUtils.getNickname(recipient) : '';
const recipientDisplayName = recipient ? NicknameUtils.getNickname(recipient, null, channel.id) : '';
return (
<div className={styles.dmMembersContainer} data-flx="channel.channel-details-bottom-sheet.dm-members-container">
{isDM && recipient && (
@@ -9,6 +9,7 @@ import {
} from '@app/features/i18n/utils/CommonMessageDescriptors';
import type {User} from '@app/features/user/models/User';
import Users from '@app/features/user/state/Users';
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import {useLingui} from '@lingui/react/macro';
import {useMemo} from 'react';
@@ -39,12 +40,7 @@ export const useChannelHeaderData = (channel?: Channel): ChannelHeaderData => {
}
return Users.getUser(channel.recipientIds[0]) ?? null;
}, [channel, isDM]);
const directMessageName = useMemo(() => {
if (!isDM || !recipient) {
return '';
}
return recipient.displayName;
}, [isDM, recipient]);
const directMessageName = isDM && recipient && channel ? NicknameUtils.getNickname(recipient, null, channel.id) : '';
const groupDMName = useMemo(() => {
if (!isGroupDM || !channel) {
return '';
@@ -249,7 +249,7 @@ const ResolvedDMListItem = observer(function ResolvedDMListItem({
const isCurrentUser = lastMessage.author.id === currentUser?.id;
const authorPrefix = isCurrentUser
? `${i18n._(YOU_DESCRIPTOR)}: `
: `${NicknameUtils.getNickname(lastMessage.author)}: `;
: `${NicknameUtils.getNickname(lastMessage.author, null, channel.id)}: `;
if (lastMessage.type !== MessageTypes.DEFAULT && lastMessage.type !== MessageTypes.REPLY) {
const systemText = SystemMessageUtils.stringify(lastMessage, i18n);
if (systemText) {
@@ -157,7 +157,7 @@ const getDirectMessageDisplayName = (channel: Channel): string => {
return i18n._(UNKNOWN_USER_DESCRIPTOR);
}
const recipient = Users.getUser(channel.recipientIds[0]);
const nickname = recipient ? NicknameUtils.getNickname(recipient) : null;
const nickname = recipient ? NicknameUtils.getNickname(recipient, null, channel.id) : null;
return nickname ?? i18n._(UNKNOWN_USER_DESCRIPTOR);
};
const getGroupDMDisplayName = (channel: Channel): string => {
@@ -119,13 +119,7 @@ export const MentionRenderer = observer(function MentionRenderer({
const user = kind.id ? Users.getUser(kind.id) : null;
const channel = channelId ? Channels.getChannel(channelId) : undefined;
const resolvedGuildId = channel?.guildId || options.guildId || '';
let name: string | null = null;
if (user) {
name = user.displayName;
if (resolvedGuildId) {
name = NicknameUtils.getNickname(user, resolvedGuildId) || name;
}
}
const name = user ? NicknameUtils.getNickname(user, resolvedGuildId || null, channelId) : null;
const genericMention = (
<span key={id} className={markupStyles.mention} data-flx="messaging.markdown.renderers.mention-renderer.span">
<MentionLabel data-flx="messaging.markdown.renderers.mention-renderer.mention-label">
@@ -42,7 +42,7 @@ export function getMemberDisplayName(member: GuildMember): string {
}
export function getUserDisplayName(user: User): string {
return firstDisplayText(user.displayName, user.username, user.id);
return DisplayNameUtils.getUntruncatedNickname(user, null);
}
export interface ParsedMentionQuery {
@@ -46,7 +46,7 @@ export function getMemberDisplayName(member: GuildMember): string {
}
export function getUserDisplayName(user: User): string {
return firstDisplayText(user.displayName, user.username, user.id);
return DisplayNameUtils.getUntruncatedNickname(user, null);
}
export interface ParsedMentionQuery {
@@ -364,14 +364,8 @@ function renderMentionToPlaintext(node: MentionNode, options: PlaintextRenderOpt
if (!user) {
return `@${kind.id}`;
}
let name = user.displayName;
if (options.channelId) {
const channel = Channels.getChannel(options.channelId);
if (channel?.guildId) {
name = NicknameUtils.getNickname(user, channel.guildId) || name;
}
}
return `@${name}`;
const channel = options.channelId ? Channels.getChannel(options.channelId) : null;
return `@${NicknameUtils.getNickname(user, channel?.guildId ?? null, options.channelId)}`;
}
case MentionKind.Role: {
const channel = options.channelId ? Channels.getChannel(options.channelId) : null;
@@ -55,7 +55,7 @@ export const IARModalPreview: React.FC<IARModalPreviewProps> = ({context, curren
{NicknameUtils.getDisplayName(context.user)}
</div>
<div className={styles.previewSubtitle} data-flx="moderation.iar-modal-preview.preview-subtitle">
#{context.user.discriminator?.toString().padStart(4, '0') ?? '0000'}
{NicknameUtils.formatUserTagForStreamerMode(context.user)}
</div>
</div>
);
@@ -133,7 +133,7 @@ const MutualFriendsList: React.FC<{profile: Profile; onClose: () => void}> = obs
/>
<div className={styles.itemInfo} data-flx="user.mutual-items-sheet.mutual-friends-list.item-info">
<span className={styles.itemName} data-flx="user.mutual-items-sheet.mutual-friends-list.item-name">
{NicknameUtils.getNickname(friendRecord, profile.guildId ?? undefined)}
{NicknameUtils.getNickname(friendRecord, profile.guildId ?? null)}
</span>
<span className={styles.itemDetail} data-flx="user.mutual-items-sheet.mutual-friends-list.item-detail">
{NicknameUtils.formatTagForStreamerMode(friendRecord.tag)}
@@ -79,6 +79,7 @@
display: flex;
min-width: 0;
align-items: center;
gap: var(--spacing-1);
}
.username {
@@ -93,10 +94,11 @@
}
.discriminator {
font-weight: 600;
flex-shrink: 0;
font-weight: 400;
color: color-mix(in srgb, var(--text-primary) 50%, transparent);
font-size: 0.875rem;
line-height: 1.25rem;
font-size: 0.75rem;
line-height: 1rem;
}
.actions {
@@ -50,7 +50,7 @@ export const BlockedUsersContent: React.FC = observer(() => {
const userA = Users.getUser(a.id);
const userB = Users.getUser(b.id);
if (!userA || !userB) return 0;
return NicknameUtils.getNickname(userA).localeCompare(NicknameUtils.getNickname(userB));
return NicknameUtils.getNickname(userA, null).localeCompare(NicknameUtils.getNickname(userB, null));
});
}, [relationships]);
const handleUnblockUser = (userId: string, event?: {shiftKey?: boolean}) => {
@@ -132,7 +132,7 @@ export const BlockedUsersContent: React.FC = observer(() => {
{blockedUsers.map((relationship) => {
const user = Users.getUser(relationship.id);
if (!user) return null;
const displayName = NicknameUtils.getNickname(user);
const displayName = NicknameUtils.getNickname(user, null);
return (
<div key={user.id} className={styles.userCard} data-flx="user.blocked-users-tab.user-card">
<div className={styles.userInfo} data-flx="user.blocked-users-tab.user-info">
@@ -162,7 +162,7 @@ export const BlockedUsersContent: React.FC = observer(() => {
{displayName}
</span>
<span className={styles.discriminator} data-flx="user.blocked-users-tab.discriminator">
#{user.discriminator}
{NicknameUtils.formatUserTagForStreamerMode(user)}
</span>
</div>
</button>
@@ -55,7 +55,7 @@ export const MutualFriendItem: React.FC<MutualFriendItemProps> = ({
className={userProfileModalStyles.mutualFriendName}
data-flx="user.user-profile-modal.mutual-friend-item.span"
>
{NicknameUtils.getNickname(user, profile?.guildId ?? undefined)}
{NicknameUtils.getNickname(user, profile?.guildId ?? null)}
</span>
<span
className={userProfileModalStyles.mutualFriendUsername}