Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 224 lines 7.7 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/core/macro' 5import {useLingui} from '@lingui/react' 6import {useQueryClient} from '@tanstack/react-query' 7 8import {makeProfileLink} from '#/lib/routes/links' 9import {forceLTR} from '#/lib/strings/bidi' 10import {NON_BREAKING_SPACE} from '#/lib/strings/constants' 11import {sanitizeDisplayName} from '#/lib/strings/display-names' 12import {sanitizeHandle} from '#/lib/strings/handles' 13import {sanitizePronouns} from '#/lib/strings/pronouns' 14import {niceDate} from '#/lib/strings/time' 15import {useProfileShadow} from '#/state/cache/profile-shadow' 16import {unstableCacheProfileView} from '#/state/queries/profile' 17import {atoms as a, platform, useTheme, web} from '#/alf' 18import {WebOnlyInlineLinkText} from '#/components/Link' 19import {PdsBadge} from '#/components/PdsBadge' 20import {ProfileHoverCard} from '#/components/ProfileHoverCard' 21import {Text} from '#/components/Typography' 22import {useSimpleVerificationState} from '#/components/verification' 23import {VerificationCheck} from '#/components/verification/VerificationCheck' 24import {IS_ANDROID} from '#/env' 25import {useActorStatus} from '#/features/liveNow' 26import {TimeElapsed} from './TimeElapsed' 27import {PreviewableUserAvatar} from './UserAvatar' 28 29interface PostMetaOpts { 30 author: AppBskyActorDefs.ProfileViewBasic 31 moderation: ModerationDecision | undefined 32 postHref: string 33 timestamp: string 34 linkDisabled?: boolean 35 showAvatar?: boolean 36 showPronouns?: boolean 37 avatarSize?: number 38 onOpenAuthor?: () => void 39 style?: StyleProp<ViewStyle> 40} 41 42let PostMeta = (opts: PostMetaOpts): React.ReactNode => { 43 const t = useTheme() 44 const {i18n, _} = useLingui() 45 46 const author = useProfileShadow(opts.author) 47 const displayName = author.displayName || author.handle 48 const handle = author.handle 49 // remove dumb typing when you update the atproto api package!! 50 const pronouns = (author as {pronouns?: string})?.pronouns 51 const profileLink = makeProfileLink(author) 52 const queryClient = useQueryClient() 53 const onOpenAuthor = opts.onOpenAuthor 54 const onBeforePressAuthor = useCallback(() => { 55 unstableCacheProfileView(queryClient, author) 56 onOpenAuthor?.() 57 }, [queryClient, author, onOpenAuthor]) 58 const onBeforePressPost = useCallback(() => { 59 unstableCacheProfileView(queryClient, author) 60 }, [queryClient, author]) 61 62 const timestampLabel = niceDate(i18n, opts.timestamp) 63 const verification = useSimpleVerificationState({profile: author}) 64 const {isActive: live} = useActorStatus(author) 65 66 const MaybeLinkText = opts.linkDisabled ? Text : WebOnlyInlineLinkText 67 68 return ( 69 <View 70 style={[ 71 IS_ANDROID ? a.flex_1 : a.flex_shrink, 72 a.flex_row, 73 a.align_center, 74 a.pb_xs, 75 a.gap_xs, 76 a.z_20, 77 opts.style, 78 ]}> 79 {opts.showAvatar && ( 80 <View style={[a.self_center, a.mr_2xs]}> 81 <PreviewableUserAvatar 82 size={opts.avatarSize || 16} 83 profile={author} 84 moderation={opts.moderation?.ui('avatar')} 85 type={author.associated?.labeler ? 'labeler' : 'user'} 86 live={live} 87 hideLiveBadge 88 disableNavigation={opts.linkDisabled} 89 /> 90 </View> 91 )} 92 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 93 <ProfileHoverCard did={author.did}> 94 <View style={[a.flex_row, a.align_end, a.flex_shrink]}> 95 <MaybeLinkText 96 emoji 97 numberOfLines={1} 98 to={profileLink} 99 label={_(msg`View profile`)} 100 disableMismatchWarning 101 onPress={opts.linkDisabled ? undefined : onBeforePressAuthor} 102 style={[ 103 a.text_md, 104 a.font_semi_bold, 105 t.atoms.text, 106 a.leading_tight, 107 a.flex_shrink_0, 108 {maxWidth: '70%'}, 109 ]}> 110 {forceLTR( 111 sanitizeDisplayName( 112 displayName, 113 opts.moderation?.ui('displayName'), 114 ), 115 )} 116 </MaybeLinkText> 117 <View 118 style={[ 119 a.pl_2xs, 120 a.self_center, 121 { 122 marginTop: platform({web: 1, ios: 0, android: -1}), 123 }, 124 ]}> 125 <PdsBadge did={author.did} size="sm" interactive={false} /> 126 </View> 127 {verification.showBadge && ( 128 <View 129 style={[ 130 a.pl_2xs, 131 a.self_center, 132 { 133 marginTop: platform({web: 1, ios: 0, android: -1}), 134 }, 135 ]}> 136 <VerificationCheck 137 width={platform({android: 13, default: 12})} 138 verifier={verification.role === 'verifier'} 139 /> 140 </View> 141 )} 142 <MaybeLinkText 143 emoji 144 numberOfLines={1} 145 to={profileLink} 146 label={_(msg`View profile`)} 147 disableMismatchWarning 148 disableUnderline 149 onPress={opts.linkDisabled ? undefined : onBeforePressAuthor} 150 style={[ 151 a.text_md, 152 t.atoms.text_contrast_medium, 153 {lineHeight: 1.17}, 154 {flexShrink: 10}, 155 ]}> 156 {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} 157 </MaybeLinkText> 158 {opts.showPronouns && pronouns && ( 159 <WebOnlyInlineLinkText 160 emoji 161 numberOfLines={1} 162 to={profileLink} 163 label={_(msg`View Profile`)} 164 disableMismatchWarning 165 disableUnderline 166 onPress={onBeforePressAuthor} 167 style={[ 168 t.atoms.text_contrast_low, 169 a.pl_2xs, 170 a.text_md, 171 {lineHeight: 1.17}, 172 {flexShrink: 5}, 173 ]}> 174 {NON_BREAKING_SPACE + sanitizePronouns(pronouns)} 175 </WebOnlyInlineLinkText> 176 )} 177 </View> 178 </ProfileHoverCard> 179 180 <TimeElapsed timestamp={opts.timestamp}> 181 {({timeElapsed}) => ( 182 <MaybeLinkText 183 to={opts.postHref} 184 label={timestampLabel} 185 title={timestampLabel} 186 disableMismatchWarning 187 disableUnderline 188 onPress={opts.linkDisabled ? undefined : onBeforePressPost} 189 style={[ 190 a.pl_xs, 191 a.text_md, 192 a.leading_tight, 193 IS_ANDROID && a.flex_grow, 194 a.text_right, 195 t.atoms.text_contrast_medium, 196 web({ 197 whiteSpace: 'nowrap', 198 }), 199 ]}> 200 {!opts.showPronouns && ( 201 <> 202 {!IS_ANDROID && ( 203 <Text 204 style={[ 205 a.text_md, 206 a.leading_tight, 207 t.atoms.text_contrast_medium, 208 ]} 209 accessible={false}> 210 &middot;{' '} 211 </Text> 212 )} 213 {timeElapsed} 214 </> 215 )} 216 </MaybeLinkText> 217 )} 218 </TimeElapsed> 219 </View> 220 </View> 221 ) 222} 223PostMeta = memo(PostMeta) 224export {PostMeta}