mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 181 lines 6.1 kB view raw
1import {memo, useCallback} from 'react' 2import {type StyleProp, View, type ViewStyle} from 'react-native' 3import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useQueryClient} from '@tanstack/react-query' 7import type React from 'react' 8 9import {useActorStatus} from '#/lib/actor-status' 10import {makeProfileLink} from '#/lib/routes/links' 11import {forceLTR} from '#/lib/strings/bidi' 12import {NON_BREAKING_SPACE} from '#/lib/strings/constants' 13import {sanitizeDisplayName} from '#/lib/strings/display-names' 14import {sanitizeHandle} from '#/lib/strings/handles' 15import {niceDate} from '#/lib/strings/time' 16import {isAndroid} from '#/platform/detection' 17import {useProfileShadow} from '#/state/cache/profile-shadow' 18import {precacheProfile} from '#/state/queries/profile' 19import {atoms as a, platform, useTheme, web} from '#/alf' 20import {WebOnlyInlineLinkText} from '#/components/Link' 21import {ProfileHoverCard} from '#/components/ProfileHoverCard' 22import {Text} from '#/components/Typography' 23import {useSimpleVerificationState} from '#/components/verification' 24import {VerificationCheck} from '#/components/verification/VerificationCheck' 25import {TimeElapsed} from './TimeElapsed' 26import {PreviewableUserAvatar} from './UserAvatar' 27 28interface PostMetaOpts { 29 author: AppBskyActorDefs.ProfileViewBasic 30 moderation: ModerationDecision | undefined 31 postHref: string 32 timestamp: string 33 showAvatar?: boolean 34 avatarSize?: number 35 onOpenAuthor?: () => void 36 style?: StyleProp<ViewStyle> 37} 38 39let PostMeta = (opts: PostMetaOpts): React.ReactNode => { 40 const t = useTheme() 41 const {i18n, _} = useLingui() 42 43 const author = useProfileShadow(opts.author) 44 const displayName = author.displayName || author.handle 45 const handle = author.handle 46 const profileLink = makeProfileLink(author) 47 const queryClient = useQueryClient() 48 const onOpenAuthor = opts.onOpenAuthor 49 const onBeforePressAuthor = useCallback(() => { 50 precacheProfile(queryClient, author) 51 onOpenAuthor?.() 52 }, [queryClient, author, onOpenAuthor]) 53 const onBeforePressPost = useCallback(() => { 54 precacheProfile(queryClient, author) 55 }, [queryClient, author]) 56 57 const timestampLabel = niceDate(i18n, opts.timestamp) 58 const verification = useSimpleVerificationState({profile: author}) 59 const {isActive: live} = useActorStatus(author) 60 61 return ( 62 <View 63 style={[ 64 a.flex_1, 65 a.flex_row, 66 a.align_center, 67 a.pb_xs, 68 a.gap_xs, 69 a.z_20, 70 opts.style, 71 ]}> 72 {opts.showAvatar && ( 73 <View style={[a.self_center, a.mr_2xs]}> 74 <PreviewableUserAvatar 75 size={opts.avatarSize || 16} 76 profile={author} 77 moderation={opts.moderation?.ui('avatar')} 78 type={author.associated?.labeler ? 'labeler' : 'user'} 79 live={live} 80 hideLiveBadge 81 /> 82 </View> 83 )} 84 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 85 <ProfileHoverCard did={author.did}> 86 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 87 <WebOnlyInlineLinkText 88 emoji 89 numberOfLines={1} 90 to={profileLink} 91 label={_(msg`View profile`)} 92 disableMismatchWarning 93 onPress={onBeforePressAuthor} 94 style={[ 95 a.text_md, 96 a.font_bold, 97 t.atoms.text, 98 a.leading_tight, 99 {maxWidth: '70%', flexShrink: 0}, 100 ]}> 101 {forceLTR( 102 sanitizeDisplayName( 103 displayName, 104 opts.moderation?.ui('displayName'), 105 ), 106 )} 107 </WebOnlyInlineLinkText> 108 {verification.showBadge && ( 109 <View 110 style={[ 111 a.pl_2xs, 112 a.self_center, 113 { 114 marginTop: platform({web: 0, ios: 0, android: -1}), 115 }, 116 ]}> 117 <VerificationCheck 118 width={platform({android: 13, default: 12})} 119 verifier={verification.role === 'verifier'} 120 /> 121 </View> 122 )} 123 <WebOnlyInlineLinkText 124 numberOfLines={1} 125 to={profileLink} 126 label={_(msg`View profile`)} 127 disableMismatchWarning 128 disableUnderline 129 onPress={onBeforePressAuthor} 130 style={[ 131 a.text_md, 132 t.atoms.text_contrast_medium, 133 a.leading_tight, 134 {flexShrink: 10}, 135 ]}> 136 {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} 137 </WebOnlyInlineLinkText> 138 </View> 139 </ProfileHoverCard> 140 141 <TimeElapsed timestamp={opts.timestamp}> 142 {({timeElapsed}) => ( 143 <WebOnlyInlineLinkText 144 to={opts.postHref} 145 label={timestampLabel} 146 title={timestampLabel} 147 disableMismatchWarning 148 disableUnderline 149 onPress={onBeforePressPost} 150 style={[ 151 a.pl_xs, 152 a.text_md, 153 a.leading_tight, 154 isAndroid && a.flex_grow, 155 a.text_right, 156 t.atoms.text_contrast_medium, 157 web({ 158 whiteSpace: 'nowrap', 159 }), 160 ]}> 161 {!isAndroid && ( 162 <Text 163 style={[ 164 a.text_md, 165 a.leading_tight, 166 t.atoms.text_contrast_medium, 167 ]} 168 accessible={false}> 169 &middot;{' '} 170 </Text> 171 )} 172 {timeElapsed} 173 </WebOnlyInlineLinkText> 174 )} 175 </TimeElapsed> 176 </View> 177 </View> 178 ) 179} 180PostMeta = memo(PostMeta) 181export {PostMeta}