diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index 3b0280805..ab86bfd50 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -14,8 +14,9 @@ import { shouldShowFollowedByText, } from '#/lib/metrics-display' import {makeProfileLink} from '#/lib/routes/links' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' @@ -120,6 +121,7 @@ function KnownFollowersInner({ const t = useTheme() const {t: l} = useLingui() const enableSquareAvatars = useEnableSquareAvatars() + const hideDisplayNames = useHideDisplayNames() const textStyle = [a.text_sm, a.leading_snug, t.atoms.text_contrast_medium] const showText = shouldShowFollowedByText(followedByDisplay) @@ -130,10 +132,10 @@ function KnownFollowersInner({ return { profile: { ...f, - displayName: sanitizeDisplayName( - f.displayName || f.handle, - moderation.ui('displayName'), - ), + displayName: getAuthorPrimaryName(f, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }), }, moderation, } diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 9ea553aea..0ee45701b 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -17,10 +17,11 @@ import {getModerationCauseKey} from '#/lib/moderation' import {makeProfileLink} from '#/lib/routes/links' import {forceLTR} from '#/lib/strings/bidi' import {NON_BREAKING_SPACE} from '#/lib/strings/constants' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useConfirmFollowUnfollow} from '#/state/preferences/confirm-follow-unfollow' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useShowFollowsYouBadge} from '#/state/preferences/show-follows-you-badge' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {type SessionAccount, useSession} from '#/state/session' @@ -149,6 +150,7 @@ export function Link({ profile: bsky.profile.AnyProfileView } & Omit) { const {t: l} = useLingui() + const hideDisplayNames = useHideDisplayNames() const profileURL = makeProfileLink({ did: profile.did, @@ -158,9 +160,9 @@ export function Link({ return ( @@ -254,11 +256,12 @@ function InlineNameAndHandle({ moderationOpts: ModerationOpts }) { const t = useTheme() + const hideDisplayNames = useHideDisplayNames() const moderation = moderateProfile(profile, moderationOpts) - const name = sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - ) + const name = getAuthorPrimaryName(profile, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }) const handle = sanitizeHandle(profile.handle, '@') return ( @@ -268,7 +271,7 @@ function InlineNameAndHandle({ a.font_semi_bold, a.leading_tight, a.flex_shrink_0, - {maxWidth: '70%'}, + {maxWidth: hideDisplayNames ? '100%' : '70%'}, ]} numberOfLines={1}> {forceLTR(name)} @@ -281,16 +284,18 @@ function InlineNameAndHandle({ ]}> - - {NON_BREAKING_SPACE + handle} - + {!hideDisplayNames && ( + + {NON_BREAKING_SPACE + handle} + + )} ) } @@ -306,11 +311,12 @@ export function Name({ style?: StyleProp textStyle?: StyleProp }) { + const hideDisplayNames = useHideDisplayNames() const moderation = moderateProfile(profile, moderationOpts) - const name = sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - ) + const name = getAuthorPrimaryName(profile, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }) return ( }) { const t = useTheme() + const hideDisplayNames = useHideDisplayNames() + if (hideDisplayNames) { + return null + } const handle = sanitizeHandle(profile.handle, '@') return ( @@ -510,6 +520,11 @@ export function FollowButtonInner({ account => account.did !== currentAccount?.did, ) const confirmFollowUnfollow = useConfirmFollowUnfollow() + const hideDisplayNames = useHideDisplayNames() + const authorName = getAuthorPrimaryName(profile, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }) const promptControl = Prompt.usePromptControl() const [confirmationAction, setConfirmationAction] = useState<'follow' | 'unfollow'>('follow') const [pendingEphemeralAccount, setPendingEphemeralAccount] = @@ -519,10 +534,7 @@ export function FollowButtonInner({ try { await queueFollow() Toast.show( - l`Following ${sanitizeDisplayName( - profile.displayName || profile.handle, - moderation.ui('displayName'), - )}`, + l`Following ${authorName}`, ) onPressProp?.(e) onFollow?.() @@ -540,10 +552,7 @@ export function FollowButtonInner({ try { await queueUnfollow() Toast.show( - l`No longer following ${sanitizeDisplayName( - profile.displayName || profile.handle, - moderation.ui('displayName'), - )}`, + l`No longer following ${authorName}`, ) onPressProp?.(e) } catch (e) { @@ -678,10 +687,7 @@ export function FollowButtonInner({ {confirmFollowUnfollow && ( { @@ -567,10 +568,7 @@ function Inner({ onRequestFollowConfirmation({ actionType: 'unfollow', onConfirm: unfollow, - displayName: sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - ), + displayName: authorPrimaryName, handle: profile.handle, }) } else { @@ -580,9 +578,8 @@ function Inner({ confirmFollowUnfollow, unfollow, onRequestFollowConfirmation, - profile.displayName, + authorPrimaryName, profile.handle, - moderation, ]) return ( @@ -663,10 +660,7 @@ function Inner({ a.font_semi_bold, a.self_start, ]}> - {sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} + {authorPrimaryName} (props: T): string { +>(props: T, hideDisplayNames = false): string { + if (hideDisplayNames) { + return sanitizeHandle(props.handle) + } return sanitizeDisplayName( props.displayName || sanitizeHandle(props.handle, '@'), ) diff --git a/src/lib/moderation/create-sanitized-display-name.ts b/src/lib/moderation/create-sanitized-display-name.ts index d15564d38..b09d62e6d 100644 --- a/src/lib/moderation/create-sanitized-display-name.ts +++ b/src/lib/moderation/create-sanitized-display-name.ts @@ -8,10 +8,15 @@ export function createSanitizedDisplayName( profile: bsky.profile.AnyProfileView, noAt = false, moderation?: ModerationUI, + hideDisplayNames = false, ) { - if (profile.displayName != null && profile.displayName !== '') { + if ( + !hideDisplayNames && + profile.displayName != null && + profile.displayName !== '' + ) { return sanitizeDisplayName(profile.displayName, moderation) } else { - return sanitizeHandle(profile.handle, noAt ? '' : '@') + return sanitizeHandle(profile.handle, noAt || hideDisplayNames ? '' : '@') } } diff --git a/src/lib/strings/display-names.ts b/src/lib/strings/display-names.ts index 612a317ea..3b196dc49 100644 --- a/src/lib/strings/display-names.ts +++ b/src/lib/strings/display-names.ts @@ -1,5 +1,7 @@ import {type ModerationUI} from '@atproto/api' +import {sanitizeHandle} from '#/lib/strings/handles' + // \u2705 = ✅ // \u2713 = ✓ // \u2714 = ✔ @@ -26,16 +28,41 @@ export function sanitizeDisplayName( return '' } +/** + * Primary identity label for an author. When hideDisplayNames is on, returns + * the bare handle (e.g. alice.bsky.social) instead of a custom display name. + */ +export function getAuthorPrimaryName( + author: {displayName?: string; handle: string}, + opts?: { + hideDisplayNames?: boolean + moderation?: ModerationUI + }, +): string { + if (opts?.hideDisplayNames) { + return sanitizeHandle(author.handle) + } + return sanitizeDisplayName( + author.displayName || sanitizeHandle(author.handle), + opts?.moderation, + ) +} + export function combinedDisplayName({ handle, displayName, + hideDisplayNames, }: { handle?: string displayName?: string + hideDisplayNames?: boolean }): string { if (!handle) { return '' } + if (hideDisplayNames) { + return sanitizeHandle(handle) + } return displayName ? `${sanitizeDisplayName(displayName)} (@${handle})` : `@${handle}` diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index fdbed21e2..556a758bf 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -18,7 +18,7 @@ import { shouldShowThreadExpandedMetric, } from '#/lib/metrics-display' import {makeProfileLink} from '#/lib/routes/links' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {niceDate} from '#/lib/strings/time' import { @@ -31,6 +31,7 @@ import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' import {useCompactPosts} from '#/state/preferences/compact-posts' import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useHideScaryFollowButtons} from '#/state/preferences/hide-scary-follow-buttons' import { useLikesMetricsDisplay, @@ -271,10 +272,11 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ const threadRootUri = record.reply?.root?.uri || post.uri const authorHref = makeProfileLink(post.author) - const displayName = sanitizeDisplayName( - post.author.displayName || sanitizeHandle(post.author.handle), - moderation.ui('displayName'), - ) + const hideDisplayNames = useHideDisplayNames() + const displayName = getAuthorPrimaryName(post.author, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }) const isAllLowercaseDisplayName = displayName === displayName.toLowerCase() && /[a-z]/.test(displayName) const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did @@ -411,7 +413,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ @@ -429,13 +431,19 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ to={authorHref} style={[ a.flex_1, - isCompactPosts && { - marginTop: isAllLowercaseDisplayName ? -4 : -3, - }, + isCompactPosts && + !hideDisplayNames && { + marginTop: isAllLowercaseDisplayName ? -4 : -3, + }, ]} label={displayName} onPress={onOpenAuthor}> - + - - {sanitizeHandle(post.author.handle, '@')} - + {!hideDisplayNames && ( + + {sanitizeHandle(post.author.handle, '@')} + + )} diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index 684a9fe53..dc5b0191d 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,9 +1,9 @@ import {View} from 'react-native' import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' -import {sanitizeDisplayName} from '#/lib/strings/display-names' -import {sanitizeHandle} from '#/lib/strings/handles' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {type Shadow} from '#/state/cache/types' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' @@ -17,6 +17,7 @@ export function ProfileHeaderDisplayName({ }) { const t = useTheme() const {gtMobile} = useBreakpoints() + const hideDisplayNames = useHideDisplayNames() return ( @@ -30,10 +31,10 @@ export function ProfileHeaderDisplayName({ a.font_bold, a.leading_tight, ]}> - {sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} + {getAuthorPrimaryName(profile, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + })} diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index a772b89a2..97600c458 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -7,6 +7,7 @@ import {Trans} from '@lingui/react/macro' import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' import {sanitizePronouns} from '#/lib/strings/pronouns' import {type Shadow} from '#/state/cache/types' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useShowFollowsYouBadge} from '#/state/preferences/show-follows-you-badge' import {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx' import {useShowLinkInHandleOnlyOnWorkingLinks} from '#/state/preferences/show-link-in-handle-only-on-working-links' @@ -30,6 +31,7 @@ export function ProfileHeaderHandle({ }) { const t = useTheme() const {_} = useLingui() + const hideDisplayNames = useHideDisplayNames() const invalidHandle = isInvalidHandle(profile.handle) const pronouns = profile.pronouns const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy @@ -88,25 +90,26 @@ export function ProfileHeaderHandle({ ) : undefined} - {invalidHandle ? ( - - {_(msg`⚠Invalid Handle`)} - - ) : shouldShowProfileLink ? ( - - {sanitized} - - ) : ( - - {sanitized} - - )} + {!hideDisplayNames && + (invalidHandle ? ( + + {_(msg`⚠Invalid Handle`)} + + ) : shouldShowProfileLink ? ( + + {sanitized} + + ) : ( + + {sanitized} + + ))} {pronouns && ( {sanitizePronouns(pronouns, IS_NATIVE)} diff --git a/src/screens/Profile/ProfileFollowers.tsx b/src/screens/Profile/ProfileFollowers.tsx index f4ab64f7b..3b9e3f4df 100644 --- a/src/screens/Profile/ProfileFollowers.tsx +++ b/src/screens/Profile/ProfileFollowers.tsx @@ -7,7 +7,8 @@ import { type CommonNavigatorParams, type NativeStackScreenProps, } from '#/lib/routes/types' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useProfileQuery} from '#/state/queries/profile' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/ProfileFollowers' @@ -17,6 +18,7 @@ type Props = NativeStackScreenProps export const ProfileFollowersScreen = ({route}: Props) => { const {name} = route.params const {_} = useLingui() + const hideDisplayNames = useHideDisplayNames() const {data: resolvedDid} = useResolveDidQuery(name) const {data: profile} = useProfileQuery({ @@ -33,7 +35,7 @@ export const ProfileFollowersScreen = ({route}: Props) => { {profile && ( <> - {sanitizeDisplayName(profile.displayName || profile.handle)} + {getAuthorPrimaryName(profile, {hideDisplayNames})} export const ProfileFollowsScreen = ({route}: Props) => { const {name} = route.params const {_} = useLingui() + const hideDisplayNames = useHideDisplayNames() const {data: resolvedDid} = useResolveDidQuery(name) const {data: profile} = useProfileQuery({ @@ -35,7 +37,7 @@ export const ProfileFollowsScreen = ({route}: Props) => { {profile && ( <> - {sanitizeDisplayName(profile.displayName || profile.handle)} + {getAuthorPrimaryName(profile, {hideDisplayNames})} setSixSevenCelebration(value)}> + name="show_thread_post_indicators" + label={l`Show thread position indicators`} + value={showThreadPostIndicators} + onChange={value => setShowThreadPostIndicators(value)}> - + - 67 celebration + Show thread position indicators @@ -144,14 +152,27 @@ export function RunesDisplaySettingsScreen() { setShowThreadPostIndicators(value)}> + name="hide_display_names" + label={l`Hide display names`} + value={hideDisplayNames} + onChange={value => setHideDisplayNames(value)}> - + - Show thread position indicators + Hide display names + + + + + setSixSevenCelebration(value)}> + + + + 67 celebration diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 8821003c0..176bfb646 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -18,12 +18,13 @@ import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import * as persisted from '#/state/persisted' import {clearStorage} from '#/state/persisted' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' @@ -540,15 +541,16 @@ function ProfilePreview({ const {gtMobile} = useBreakpoints() const shadow = useProfileShadow(profile) const moderationOpts = useModerationOpts() + const hideDisplayNames = useHideDisplayNames() const {isActive: live} = useActorStatus(profile) if (!moderationOpts) return null const moderation = moderateProfile(profile, moderationOpts) - const displayName = sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - ) + const displayName = getAuthorPrimaryName(profile, { + hideDisplayNames, + moderation: moderation.ui('displayName'), + }) return ( <> @@ -591,9 +593,11 @@ function ProfilePreview({ ]} /> - - {sanitizeHandle(profile.handle, '@')} - + {!hideDisplayNames && ( + + {sanitizeHandle(profile.handle, '@')} + + )} ) } diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 549a481e2..63e4493fa 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -51,7 +51,7 @@ import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' import {logger} from '#/logger' @@ -69,6 +69,7 @@ import { } from '#/state/feed-feedback' import {useConfirmFollowUnfollow} from '#/state/preferences/confirm-follow-unfollow' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useFeedInfo} from '#/state/queries/feed' import {usePostLikeMutationQueue} from '#/state/queries/post' import { @@ -750,6 +751,10 @@ function Overlay({ [accounts, currentAccount?.did], ) const confirmFollowUnfollow = useConfirmFollowUnfollow() + const hideDisplayNames = useHideDisplayNames() + const authorPrimaryName = getAuthorPrimaryName(post.author, { + hideDisplayNames, + }) const promptControl = Prompt.usePromptControl() const [confirmationAction, setConfirmationAction] = useState<'follow' | 'unfollow'>('follow') @@ -868,9 +873,7 @@ function Overlay({ - {sanitizeDisplayName( - post.author.displayName || post.author.handle, - )} - - - {handle} + {authorPrimaryName} + {!hideDisplayNames && ( + + {handle} + + )} {/* show button based on non-reactive version, so it doesn't hide on press */} @@ -1043,9 +1046,7 @@ function Overlay({ {confirmFollowUnfollow && ( void + +const stateContext = createContext( + persisted.defaults.hideDisplayNames, +) +const setContext = createContext( + (_: persisted.Schema['hideDisplayNames']) => {}, +) + +export function Provider({children}: PropsWithChildren<{}>) { + const [state, setState] = useState(persisted.get('hideDisplayNames')) + + const setStateWrapped = useCallback( + (value: persisted.Schema['hideDisplayNames']) => { + setState(value) + persisted.write('hideDisplayNames', value) + }, + [setState], + ) + + useEffect(() => { + return persisted.onUpdate('hideDisplayNames', next => { + setState(next) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useHideDisplayNames() { + return useContext(stateContext) +} + +export function useSetHideDisplayNames() { + return useContext(setContext) +} diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index b33e25063..dba273ce8 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -30,6 +30,7 @@ import {Provider as GoLinksProvider} from './go-links-enabled' import {Provider as HiddenPostsProvider} from './hidden-posts' import {Provider as HideFeedsPromoTabProvider} from './hide-feeds-promo-tab' import {Provider as HideScaryFollowButtonsProvider} from './hide-scary-follow-buttons.tsx' +import {Provider as HideDisplayNamesProvider} from './hide-display-names' import {Provider as HideSimilarAccountsRecommProvider} from './hide-similar-accounts-recommendations' import {Provider as HideUnreplyablePostsProvider} from './hide-unreplyable-posts' import {Provider as IgnoredAppLabelersProvider} from './ignored-app-labelers' @@ -112,6 +113,10 @@ export { useHideScaryFollowButtons, useSetHideScaryFollowButtons, } from './hide-scary-follow-buttons' +export { + useHideDisplayNames, + useSetHideDisplayNames, +} from './hide-display-names' export {useImageCdnHost, useSetImageCdnHost} from './image-cdn-host' export {useLabelDefinitions} from './label-defs' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' @@ -212,29 +217,31 @@ export function Provider({children}: PropsWithChildren<{}>) { - - - - - - - - - - - { - children - } - - - - - - - - - - + + + + + + + + + + + + { + children + } + + + + + + + + + + + diff --git a/src/state/preferences/settings-sync.tsx b/src/state/preferences/settings-sync.tsx index 1e744e34d..c29426f9a 100644 --- a/src/state/preferences/settings-sync.tsx +++ b/src/state/preferences/settings-sync.tsx @@ -78,6 +78,7 @@ export const SYNCED_PREFS_KEYS = [ 'autoCompactAccountSwitcher', 'disableVerifyEmailReminder', 'showViaClient', + 'hideDisplayNames', 'sixSevenCelebration', 'deerVerification', 'thumbnailFormat', diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index ea2bd3131..d6f022e4f 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -12,9 +12,9 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' -import {sanitizeDisplayName} from '#/lib/strings/display-names' -import {sanitizeHandle} from '#/lib/strings/handles' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {sanitizePronouns} from '#/lib/strings/pronouns' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {type ComposerOptsPostRef} from '#/state/shell/composer' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, utils, web} from '#/alf' @@ -26,6 +26,7 @@ import {parseEmbed} from '#/types/bsky/post' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const t = useTheme() const {_} = useLingui() + const hideDisplayNames = useHideDisplayNames() const {embed} = replyTo const [showFull, setShowFull] = useState(false) @@ -117,10 +118,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { style={[a.font_semi_bold, a.text_md, a.leading_snug, a.flex_shrink]} numberOfLines={1} emoji> - {sanitizeDisplayName( - replyTo.author.displayName || - sanitizeHandle(replyTo.author.handle), - )} + {getAuthorPrimaryName(replyTo.author, {hideDisplayNames})} {replyTo.author?.pronouns && ( diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 2e8db740a..303e80b39 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -39,12 +39,13 @@ import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {forceLTR} from '#/lib/strings/bidi' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {niceDate} from '#/lib/strings/time' import {s} from '#/lib/styles' import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useConfirmFollowUnfollow} from '#/state/preferences/confirm-follow-unfollow' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {type FeedNotification} from '#/state/queries/notifications/feed' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' @@ -200,9 +201,10 @@ let NotificationFeedItem = ({ const niceTimestamp = niceDate(i18n, item.notification.indexedAt) const firstAuthor = authors[0] - const firstAuthorName = sanitizeDisplayName( - firstAuthor.profile.displayName || firstAuthor.profile.handle, - ) + const hideDisplayNames = useHideDisplayNames() + const firstAuthorName = getAuthorPrimaryName(firstAuthor.profile, { + hideDisplayNames, + }) // Calculate if this is a follow-back notification const isFollowBack = useMemo(() => { @@ -642,9 +644,7 @@ let NotificationFeedItem = ({ { name: 'viewProfile', label: _( - msg`View ${ - authors[0].profile.displayName || authors[0].profile.handle - }'s profile`, + msg`View ${firstAuthorName}'s profile`, ), }, ] @@ -902,6 +902,8 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { const {_} = useLingui() const {currentAccount, hasSession} = useSession() const profileShadow = useProfileShadow(profile) + const hideDisplayNames = useHideDisplayNames() + const authorName = getAuthorPrimaryName(profile, {hideDisplayNames}) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profileShadow, 'ProfileCard', @@ -922,9 +924,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { await queueFollow() Toast.show( _( - msg`Following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, + msg`Following ${authorName}`, ), ) } catch (err: any) { @@ -941,9 +941,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { await queueUnfollow() Toast.show( _( - msg`No longer following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, + msg`No longer following ${authorName}`, ), ) } catch (err: any) { @@ -1038,9 +1036,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { {confirmFollowUnfollow && ( { const {i18n, _} = useLingui() const author = useProfileShadow(opts.author) - const displayName = author.displayName || author.handle + const hideDisplayNames = useHideDisplayNames() + const displayName = getAuthorPrimaryName(author, { + hideDisplayNames, + moderation: opts.moderation?.ui('displayName'), + }) const handle = author.handle // remove dumb typing when you update the atproto api package!! const pronouns = (author as {pronouns?: string})?.pronouns @@ -117,12 +122,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { a.leading_tight, a.flex_shrink, ]}> - {forceLTR( - sanitizeDisplayName( - displayName, - opts.moderation?.ui('displayName'), - ), - )} + {forceLTR(displayName)} { }, ]} /> - - {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} - + {!hideDisplayNames && ( + + {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} + + )} {opts.showPronouns && pronouns && ( ) } else if (profile) { - const text = `${prefix || ''}${sanitizeDisplayName( - typeof profile[attr] === 'string' && profile[attr] - ? (profile[attr] as string) - : sanitizeHandle(profile.handle), - )}` + const value = + hideDisplayNames || attr === 'handle' + ? getAuthorPrimaryName(profile, {hideDisplayNames: true}) + : sanitizeDisplayName( + typeof profile[attr] === 'string' && profile[attr] + ? (profile[attr] as string) + : sanitizeHandle(profile.handle), + ) + const text = `${prefix || ''}${value}` return ( (null) @@ -203,7 +205,7 @@ function ProfileScreenLoaded({ const starterPacksSectionRef = useRef(null) const labelsSectionRef = useRef(null) - useSetTitle(combinedDisplayName(profile)) + useSetTitle(combinedDisplayName({...profile, hideDisplayNames})) const description = profile.description ?? '' const hasDescription = description !== '' diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 039ebbccb..3210ff2db 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -13,11 +13,12 @@ import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' -import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {getAuthorPrimaryName} from '#/lib/strings/display-names' import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' import {emitSoftReset} from '#/state/events' import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' +import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useFetchHandle} from '#/state/queries/handle' import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' import {useUnreadNotifications} from '#/state/queries/notifications/unread' @@ -124,6 +125,7 @@ function ProfileCard({minimal}: {minimal: boolean}) { const enableSquareButtons = useEnableSquareButtons() const enableSquareAvatars = useEnableSquareAvatars() + const hideDisplayNames = useHideDisplayNames() return ( @@ -186,19 +188,19 @@ function ProfileCard({minimal}: {minimal: boolean}) { emoji style={[a.font_bold, a.text_sm, a.leading_snug]} numberOfLines={1}> - {sanitizeDisplayName( - profile.displayName || profile.handle, - )} - - - {sanitizeHandle(profile.handle, '@')} + {getAuthorPrimaryName(profile, {hideDisplayNames})} + {!hideDisplayNames && ( + + {sanitizeHandle(profile.handle, '@')} + + )}