tangled mirror of catsky-🐱 Soothing soft social-app fork with all the niche toggles! (Unofficial); for issues and PRs please put them on github:NekoDrone/catsky-social

tidy up post reasons (#9194)

authored by samuel.fm and committed by GitHub 5222a6e1 b7f505ab

Changed files
+171 -137
src
+4 -1
src/lib/moderation/create-sanitized-display-name.ts
··· 1 + import {type ModerationUI} from '@atproto/api' 2 + 1 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 2 4 import {sanitizeHandle} from '#/lib/strings/handles' 3 5 import type * as bsky from '#/types/bsky' ··· 5 7 export function createSanitizedDisplayName( 6 8 profile: bsky.profile.AnyProfileView, 7 9 noAt = false, 10 + moderation?: ModerationUI, 8 11 ) { 9 12 if (profile.displayName != null && profile.displayName !== '') { 10 - return sanitizeDisplayName(profile.displayName) 13 + return sanitizeDisplayName(profile.displayName, moderation) 11 14 } else { 12 15 return sanitizeHandle(profile.handle, noAt ? '' : '@') 13 16 }
+11 -116
src/view/com/posts/PostFeedItem.tsx
··· 9 9 type ModerationDecision, 10 10 RichText as RichTextAPI, 11 11 } from '@atproto/api' 12 - import {msg, Trans} from '@lingui/macro' 13 - import {useLingui} from '@lingui/react' 14 12 import {useNavigation} from '@react-navigation/native' 15 13 import {useQueryClient} from '@tanstack/react-query' 16 14 17 15 import {useActorStatus} from '#/lib/actor-status' 18 - import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' 16 + import {type ReasonFeedSource} from '#/lib/api/feed/types' 19 17 import {MAX_POST_LINES} from '#/lib/constants' 20 18 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 21 19 import {usePalette} from '#/lib/hooks/usePalette' 22 20 import {makeProfileLink} from '#/lib/routes/links' 23 21 import {type NavigationProp} from '#/lib/routes/types' 24 22 import {useGate} from '#/lib/statsig/statsig' 25 - import {sanitizeDisplayName} from '#/lib/strings/display-names' 26 - import {sanitizeHandle} from '#/lib/strings/handles' 27 23 import {countLines} from '#/lib/strings/helpers' 28 24 import { 29 25 POST_TOMBSTONE, ··· 38 34 buildPostSourceKey, 39 35 setUnstablePostSource, 40 36 } from '#/state/unstable-post-source' 41 - import {FeedNameText} from '#/view/com/util/FeedInfoText' 42 - import {Link, TextLinkOnWebOnly} from '#/view/com/util/Link' 37 + import {Link} from '#/view/com/util/Link' 43 38 import {PostMeta} from '#/view/com/util/PostMeta' 44 - import {Text} from '#/view/com/util/text/Text' 45 39 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' 46 40 import {atoms as a} from '#/alf' 47 - import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 48 - import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost' 49 41 import {ContentHider} from '#/components/moderation/ContentHider' 50 42 import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' 51 43 import {PostAlerts} from '#/components/moderation/PostAlerts' ··· 56 48 import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 57 49 import {PostControls} from '#/components/PostControls' 58 50 import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug' 59 - import {ProfileHoverCard} from '#/components/ProfileHoverCard' 60 51 import {RichText} from '#/components/RichText' 61 52 import {SubtleHover} from '#/components/SubtleHover' 62 53 import * as bsky from '#/types/bsky' 54 + import {PostFeedReason} from './PostFeedReason' 63 55 64 56 interface FeedItemProps { 65 57 record: AppBskyFeedPost.Record ··· 173 165 const navigation = useNavigation<NavigationProp>() 174 166 const pal = usePalette('default') 175 167 const gate = useGate() 176 - const {_} = useLingui() 177 168 178 169 const [hover, setHover] = useState(false) 179 170 ··· 275 266 }, 276 267 ] 277 268 278 - const {currentAccount} = useSession() 279 - const isOwner = 280 - AppBskyFeedDefs.isReasonRepost(reason) && 281 - reason.by.did === currentAccount?.did 282 - 283 269 /** 284 270 * If `post[0]` in this slice is the actual root post (not an orphan thread), 285 271 * then we may have a threadgate record to reference ··· 334 320 )} 335 321 </View> 336 322 337 - <View style={{paddingTop: 10, flexShrink: 1}}> 338 - {isReasonFeedSource(reason) ? ( 339 - <Link href={reason.href}> 340 - <Text 341 - type="sm-bold" 342 - style={pal.textLight} 343 - lineHeight={1.2} 344 - numberOfLines={1}> 345 - <Trans context="from-feed"> 346 - From{' '} 347 - <FeedNameText 348 - type="sm-bold" 349 - uri={reason.uri} 350 - href={reason.href} 351 - lineHeight={1.2} 352 - numberOfLines={1} 353 - style={pal.textLight} 354 - /> 355 - </Trans> 356 - </Text> 357 - </Link> 358 - ) : AppBskyFeedDefs.isReasonRepost(reason) ? ( 359 - <Link 360 - style={styles.includeReason} 361 - href={makeProfileLink(reason.by)} 362 - title={ 363 - isOwner 364 - ? _(msg`Reposted by you`) 365 - : _( 366 - msg`Reposted by ${sanitizeDisplayName( 367 - reason.by.displayName || reason.by.handle, 368 - )}`, 369 - ) 370 - } 371 - onBeforePress={onOpenReposter}> 372 - <RepostIcon 373 - style={{color: pal.colors.textLight, marginRight: 3}} 374 - width={13} 375 - height={13} 376 - /> 377 - <Text 378 - type="sm-bold" 379 - style={pal.textLight} 380 - lineHeight={1.2} 381 - numberOfLines={1}> 382 - {isOwner ? ( 383 - <Trans>Reposted by you</Trans> 384 - ) : ( 385 - <Trans> 386 - Reposted by{' '} 387 - <ProfileHoverCard did={reason.by.did}> 388 - <TextLinkOnWebOnly 389 - type="sm-bold" 390 - style={pal.textLight} 391 - lineHeight={1.2} 392 - numberOfLines={1} 393 - text={ 394 - <Text 395 - emoji 396 - type="sm-bold" 397 - style={pal.textLight} 398 - lineHeight={1.2}> 399 - {sanitizeDisplayName( 400 - reason.by.displayName || 401 - sanitizeHandle(reason.by.handle), 402 - moderation.ui('displayName'), 403 - )} 404 - </Text> 405 - } 406 - href={makeProfileLink(reason.by)} 407 - onBeforePress={onOpenReposter} 408 - /> 409 - </ProfileHoverCard> 410 - </Trans> 411 - )} 412 - </Text> 413 - </Link> 414 - ) : AppBskyFeedDefs.isReasonPin(reason) ? ( 415 - <View style={styles.includeReason}> 416 - <PinIcon 417 - style={{color: pal.colors.textLight, marginRight: 3}} 418 - width={13} 419 - height={13} 420 - /> 421 - <Text 422 - type="sm-bold" 423 - style={pal.textLight} 424 - lineHeight={1.2} 425 - numberOfLines={1}> 426 - <Trans>Pinned</Trans> 427 - </Text> 428 - </View> 429 - ) : null} 323 + <View style={[a.pt_sm, a.flex_shrink]}> 324 + {reason && ( 325 + <PostFeedReason 326 + reason={reason} 327 + moderation={moderation} 328 + onOpenReposter={onOpenReposter} 329 + /> 330 + )} 430 331 </View> 431 332 </View> 432 333 ··· 601 502 width: 2, 602 503 marginLeft: 'auto', 603 504 marginRight: 'auto', 604 - }, 605 - includeReason: { 606 - flexDirection: 'row', 607 - alignItems: 'center', 608 - marginBottom: 2, 609 - marginLeft: -16, 610 505 }, 611 506 layout: { 612 507 flexDirection: 'row',
+139
src/view/com/posts/PostFeedReason.tsx
··· 1 + import {StyleSheet, View} from 'react-native' 2 + import {AppBskyFeedDefs, type ModerationDecision} from '@atproto/api' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' 7 + import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 8 + import {makeProfileLink} from '#/lib/routes/links' 9 + import {useSession} from '#/state/session' 10 + import {atoms as a, useTheme} from '#/alf' 11 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 12 + import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost' 13 + import {Link, WebOnlyInlineLinkText} from '#/components/Link' 14 + import {ProfileHoverCard} from '#/components/ProfileHoverCard' 15 + import {Text} from '#/components/Typography' 16 + import {FeedNameText} from '../util/FeedInfoText' 17 + 18 + export function PostFeedReason({ 19 + reason, 20 + moderation, 21 + onOpenReposter, 22 + }: { 23 + reason: 24 + | ReasonFeedSource 25 + | AppBskyFeedDefs.ReasonRepost 26 + | AppBskyFeedDefs.ReasonPin 27 + | {[k: string]: unknown; $type: string} 28 + moderation?: ModerationDecision 29 + onOpenReposter?: () => void 30 + }) { 31 + const t = useTheme() 32 + const {_} = useLingui() 33 + 34 + const {currentAccount} = useSession() 35 + 36 + if (isReasonFeedSource(reason)) { 37 + return ( 38 + <Link label={_(msg`Go to feed`)} to={reason.href}> 39 + <Text 40 + style={[ 41 + t.atoms.text_contrast_medium, 42 + a.font_medium, 43 + a.leading_snug, 44 + a.leading_snug, 45 + ]} 46 + numberOfLines={1}> 47 + <Trans context="from-feed"> 48 + From{' '} 49 + <FeedNameText 50 + uri={reason.uri} 51 + href={reason.href} 52 + style={[ 53 + t.atoms.text_contrast_medium, 54 + a.font_medium, 55 + a.leading_snug, 56 + ]} 57 + numberOfLines={1} 58 + /> 59 + </Trans> 60 + </Text> 61 + </Link> 62 + ) 63 + } 64 + 65 + if (AppBskyFeedDefs.isReasonRepost(reason)) { 66 + const isOwner = reason.by.did === currentAccount?.did 67 + const reposter = createSanitizedDisplayName( 68 + reason.by, 69 + false, 70 + moderation?.ui('displayName'), 71 + ) 72 + return ( 73 + <Link 74 + style={styles.includeReason} 75 + to={makeProfileLink(reason.by)} 76 + label={ 77 + isOwner ? _(msg`Reposted by you`) : _(msg`Reposted by ${reposter}`) 78 + } 79 + onPress={onOpenReposter}> 80 + <RepostIcon 81 + style={[t.atoms.text_contrast_medium, {marginRight: 3}]} 82 + width={13} 83 + height={13} 84 + /> 85 + <Text 86 + style={[t.atoms.text_contrast_medium, a.font_medium, a.leading_snug]} 87 + numberOfLines={1}> 88 + {isOwner ? ( 89 + <Trans>Reposted by you</Trans> 90 + ) : ( 91 + <Trans> 92 + Reposted by{' '} 93 + <ProfileHoverCard did={reason.by.did}> 94 + <WebOnlyInlineLinkText 95 + label={reposter} 96 + numberOfLines={1} 97 + to={makeProfileLink(reason.by)} 98 + onPress={onOpenReposter} 99 + style={[ 100 + t.atoms.text_contrast_medium, 101 + a.font_medium, 102 + a.leading_snug, 103 + ]}> 104 + {reposter} 105 + </WebOnlyInlineLinkText> 106 + </ProfileHoverCard> 107 + </Trans> 108 + )} 109 + </Text> 110 + </Link> 111 + ) 112 + } 113 + 114 + if (AppBskyFeedDefs.isReasonPin(reason)) { 115 + return ( 116 + <View style={styles.includeReason}> 117 + <PinIcon 118 + style={[t.atoms.text_contrast_medium, {marginRight: 3}]} 119 + width={13} 120 + height={13} 121 + /> 122 + <Text 123 + style={[t.atoms.text_contrast_medium, a.font_medium, a.leading_snug]} 124 + numberOfLines={1}> 125 + <Trans>Pinned</Trans> 126 + </Text> 127 + </View> 128 + ) 129 + } 130 + } 131 + 132 + const styles = StyleSheet.create({ 133 + includeReason: { 134 + flexDirection: 'row', 135 + alignItems: 'center', 136 + marginBottom: 2, 137 + marginLeft: -16, 138 + }, 139 + })
+17 -20
src/view/com/util/FeedInfoText.tsx
··· 1 - import {type StyleProp, StyleSheet, type TextStyle} from 'react-native' 1 + import {type StyleProp, type TextStyle} from 'react-native' 2 2 3 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 4 - import {type TypographyVariant} from '#/lib/ThemeContext' 5 4 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 6 - import {TextLinkOnWebOnly} from './Link' 5 + import {atoms as a, platform} from '#/alf' 6 + import {WebOnlyInlineLinkText} from '#/components/Link' 7 7 import {LoadingPlaceholder} from './LoadingPlaceholder' 8 8 9 9 export function FeedNameText({ 10 - type = 'md', 11 10 uri, 12 11 href, 13 - lineHeight, 14 12 numberOfLines, 15 13 style, 16 14 }: { 17 - type?: TypographyVariant 18 15 uri: string 19 16 href: string 20 - lineHeight?: number 21 17 numberOfLines?: number 22 18 style?: StyleProp<TextStyle> 23 19 }) { 24 20 const {data, isError} = useFeedSourceInfoQuery({uri}) 25 21 26 22 let inner 27 - if (data?.displayName || isError) { 23 + if (data || isError) { 28 24 const displayName = data?.displayName || uri.split('/').pop() || '' 29 25 inner = ( 30 - <TextLinkOnWebOnly 31 - type={type} 26 + <WebOnlyInlineLinkText 27 + to={href} 28 + label={displayName} 32 29 style={style} 33 - lineHeight={lineHeight} 34 - numberOfLines={numberOfLines} 35 - href={href} 36 - text={sanitizeDisplayName(displayName)} 37 - /> 30 + numberOfLines={numberOfLines}> 31 + {sanitizeDisplayName(displayName)} 32 + </WebOnlyInlineLinkText> 38 33 ) 39 34 } else { 40 35 inner = ( 41 36 <LoadingPlaceholder 42 37 width={80} 43 38 height={8} 44 - style={styles.loadingPlaceholder} 39 + style={[ 40 + a.ml_2xs, 41 + platform({ 42 + native: [a.mt_2xs], 43 + web: [{top: -1}], 44 + }), 45 + ]} 45 46 /> 46 47 ) 47 48 } 48 49 49 50 return inner 50 51 } 51 - 52 - const styles = StyleSheet.create({ 53 - loadingPlaceholder: {position: 'relative', top: 1, left: 2}, 54 - })