An ATproto social media client -- with an independent Appview.

refactor: comment out file

for typecheck purposes

serenity 98de81a4 e703962e

Changed files
+415 -415
src
view
com
util
post-embeds
+415 -415
src/view/com/util/post-embeds/QuoteEmbed.tsx
··· 1 - import React from 'react' 2 - import { 3 - type StyleProp, 4 - StyleSheet, 5 - TouchableOpacity, 6 - View, 7 - type ViewStyle, 8 - } from 'react-native' 9 - import { 10 - AppBskyEmbedExternal, 11 - AppBskyEmbedImages, 12 - AppBskyEmbedRecord, 13 - AppBskyEmbedRecordWithMedia, 14 - AppBskyEmbedVideo, 15 - type AppBskyFeedDefs, 16 - AppBskyFeedPost, 17 - moderatePost, 18 - type ModerationDecision, 19 - RichText as RichTextAPI, 20 - } from '@atproto/api' 21 - import {AtUri} from '@atproto/api' 22 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 23 - import {msg, Trans} from '@lingui/macro' 24 - import {useLingui} from '@lingui/react' 25 - import {useQueryClient} from '@tanstack/react-query' 26 - 27 - import {HITSLOP_20} from '#/lib/constants' 28 - import {usePalette} from '#/lib/hooks/usePalette' 29 - import {InfoCircleIcon} from '#/lib/icons' 30 - import {makeProfileLink} from '#/lib/routes/links' 31 - import {s} from '#/lib/styles' 32 - import {useDirectFetchRecords} from '#/state/preferences/direct-fetch-records' 33 - import {useModerationOpts} from '#/state/preferences/moderation-opts' 34 - import {useDirectFetchRecord} from '#/state/queries/direct-fetch-record' 35 - import {precacheProfile} from '#/state/queries/profile' 36 - import {useResolveLinkQuery} from '#/state/queries/resolve-link' 37 - import {useSession} from '#/state/session' 38 - import {atoms as a, useTheme} from '#/alf' 39 - import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash' 40 - import {RichText} from '#/components/RichText' 41 - import {SubtleWebHover} from '#/components/SubtleWebHover' 42 - import * as bsky from '#/types/bsky' 43 - import {ContentHider} from '../../../../components/moderation/ContentHider' 44 - import {PostAlerts} from '../../../../components/moderation/PostAlerts' 45 - import {Link} from '../Link' 46 - import {PostMeta} from '../PostMeta' 47 - import {Text} from '../text/Text' 48 - import {PostEmbeds} from '.' 49 - import {type QuoteEmbedViewContext} from './types' 50 - 51 - export function MaybeQuoteEmbed({ 52 - embed, 53 - onOpen, 54 - style, 55 - allowNestedQuotes, 56 - viewContext, 57 - }: { 58 - embed: AppBskyEmbedRecord.View 59 - onOpen?: () => void 60 - style?: StyleProp<ViewStyle> 61 - allowNestedQuotes?: boolean 62 - viewContext?: QuoteEmbedViewContext 63 - }) { 64 - const {_} = useLingui() 65 - const t = useTheme() 66 - const pal = usePalette('default') 67 - const {currentAccount} = useSession() 68 - 69 - const directFetchEnabled = useDirectFetchRecords() 70 - const shouldDirectFetch = 71 - (AppBskyEmbedRecord.isViewBlocked(embed.record) || 72 - AppBskyEmbedRecord.isViewDetached(embed.record)) && 73 - directFetchEnabled 74 - 75 - const directRecord = useDirectFetchRecord({ 76 - uri: 77 - AppBskyEmbedRecord.isViewBlocked(embed.record) || 78 - AppBskyEmbedRecord.isViewDetached(embed.record) 79 - ? embed.record.uri 80 - : '', 81 - enabled: shouldDirectFetch, 82 - }) 83 - if ( 84 - AppBskyEmbedRecord.isViewRecord(embed.record) && 85 - AppBskyFeedPost.isRecord(embed.record.value) && 86 - AppBskyFeedPost.validateRecord(embed.record.value).success 87 - ) { 88 - return ( 89 - <QuoteEmbedModerated 90 - viewRecord={embed.record} 91 - onOpen={onOpen} 92 - style={style} 93 - allowNestedQuotes={allowNestedQuotes} 94 - viewContext={viewContext} 95 - /> 96 - ) 97 - } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) { 98 - const record = directRecord.data 99 - if (record !== undefined) { 100 - return ( 101 - <View> 102 - <QuoteEmbedModerated 103 - viewRecord={record} 104 - onOpen={onOpen} 105 - style={style} 106 - allowNestedQuotes={allowNestedQuotes} 107 - viewContext={viewContext} 108 - visibilityLabel={_(msg`Blocked`)} 109 - /> 110 - </View> 111 - ) 112 - } 113 - 114 - return ( 115 - <View 116 - style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 117 - <InfoCircleIcon size={18} style={pal.text} /> 118 - <Text type="lg" style={pal.text}> 119 - {directFetchEnabled ? ( 120 - <Trans>Blocked...</Trans> 121 - ) : ( 122 - <Trans>Blocked</Trans> 123 - )} 124 - </Text> 125 - </View> 126 - ) 127 - } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) { 128 - return ( 129 - <View 130 - style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 131 - <InfoCircleIcon size={18} style={pal.text} /> 132 - <Text type="lg" style={pal.text}> 133 - <Trans>Deleted</Trans> 134 - </Text> 135 - </View> 136 - ) 137 - } else if (AppBskyEmbedRecord.isViewDetached(embed.record)) { 138 - const isViewerOwner = currentAccount?.did 139 - ? embed.record.uri.includes(currentAccount.did) 140 - : false 141 - 142 - const record = directRecord.data 143 - if (record !== undefined) { 144 - return ( 145 - <View> 146 - <QuoteEmbedModerated 147 - viewRecord={record} 148 - onOpen={onOpen} 149 - style={style} 150 - allowNestedQuotes={allowNestedQuotes} 151 - viewContext={viewContext} 152 - visibilityLabel={ 153 - isViewerOwner ? _(`Removed by you`) : _(msg`Removed by author`) 154 - } 155 - /> 156 - </View> 157 - ) 158 - } 159 - 160 - return ( 161 - <View 162 - style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 163 - <InfoCircleIcon size={18} style={pal.text} /> 164 - <Text type="lg" style={pal.text}> 165 - {isViewerOwner ? ( 166 - <Trans>Removed by you</Trans> 167 - ) : ( 168 - <Trans>Removed by author</Trans> 169 - )} 170 - {directFetchEnabled ? <Trans>...</Trans> : undefined} 171 - </Text> 172 - </View> 173 - ) 174 - } 175 - return null 176 - } 177 - 178 - function QuoteEmbedModerated({ 179 - viewRecord, 180 - onOpen, 181 - style, 182 - allowNestedQuotes, 183 - viewContext, 184 - visibilityLabel, 185 - }: { 186 - viewRecord: AppBskyEmbedRecord.ViewRecord 187 - onOpen?: () => void 188 - style?: StyleProp<ViewStyle> 189 - allowNestedQuotes?: boolean 190 - viewContext?: QuoteEmbedViewContext 191 - visibilityLabel?: string 192 - }) { 193 - const moderationOpts = useModerationOpts() 194 - const postView = React.useMemo( 195 - () => viewRecordToPostView(viewRecord), 196 - [viewRecord], 197 - ) 198 - const moderation = React.useMemo(() => { 199 - return moderationOpts ? moderatePost(postView, moderationOpts) : undefined 200 - }, [postView, moderationOpts]) 201 - 202 - return ( 203 - <QuoteEmbed 204 - quote={postView} 205 - moderation={moderation} 206 - onOpen={onOpen} 207 - style={style} 208 - allowNestedQuotes={allowNestedQuotes} 209 - viewContext={viewContext} 210 - visibilityLabel={visibilityLabel} 211 - /> 212 - ) 213 - } 214 - 215 - export function QuoteEmbed({ 216 - quote, 217 - moderation, 218 - onOpen, 219 - style, 220 - allowNestedQuotes, 221 - visibilityLabel, 222 - }: { 223 - quote: AppBskyFeedDefs.PostView 224 - moderation?: ModerationDecision 225 - onOpen?: () => void 226 - style?: StyleProp<ViewStyle> 227 - allowNestedQuotes?: boolean 228 - viewContext?: QuoteEmbedViewContext 229 - visibilityLabel?: string 230 - }) { 231 - const t = useTheme() 232 - const queryClient = useQueryClient() 233 - const pal = usePalette('default') 234 - const itemUrip = new AtUri(quote.uri) 235 - const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey) 236 - const itemTitle = `Post by ${quote.author.handle}` 237 - 238 - const richText = React.useMemo(() => { 239 - if ( 240 - !bsky.dangerousIsType<AppBskyFeedPost.Record>( 241 - quote.record, 242 - AppBskyFeedPost.isRecord, 243 - ) 244 - ) 245 - return undefined 246 - const {text, facets} = quote.record 247 - return text.trim() 248 - ? new RichTextAPI({text: text, facets: facets}) 249 - : undefined 250 - }, [quote.record]) 251 - 252 - const embed = React.useMemo(() => { 253 - const e = quote.embed 254 - 255 - if (allowNestedQuotes) { 256 - return e 257 - } else { 258 - if ( 259 - AppBskyEmbedImages.isView(e) || 260 - AppBskyEmbedExternal.isView(e) || 261 - AppBskyEmbedVideo.isView(e) 262 - ) { 263 - return e 264 - } else if ( 265 - AppBskyEmbedRecordWithMedia.isView(e) && 266 - (AppBskyEmbedImages.isView(e.media) || 267 - AppBskyEmbedExternal.isView(e.media) || 268 - AppBskyEmbedVideo.isView(e.media)) 269 - ) { 270 - return e.media 271 - } 272 - } 273 - }, [quote.embed, allowNestedQuotes]) 274 - 275 - const onBeforePress = React.useCallback(() => { 276 - precacheProfile(queryClient, quote.author) 277 - onOpen?.() 278 - }, [queryClient, quote.author, onOpen]) 279 - 280 - const [hover, setHover] = React.useState(false) 281 - return ( 282 - <View 283 - onPointerEnter={() => { 284 - setHover(true) 285 - }} 286 - onPointerLeave={() => { 287 - setHover(false) 288 - }}> 289 - <ContentHider 290 - modui={moderation?.ui('contentList')} 291 - style={[ 292 - a.rounded_md, 293 - a.p_md, 294 - a.mt_sm, 295 - a.border, 296 - t.atoms.border_contrast_low, 297 - style, 298 - ]} 299 - childContainerStyle={[a.pt_sm]}> 300 - <SubtleWebHover hover={hover} /> 301 - <Link 302 - hoverStyle={{borderColor: pal.colors.borderLinkHover}} 303 - href={itemHref} 304 - title={itemTitle} 305 - onBeforePress={onBeforePress}> 306 - <View pointerEvents="none"> 307 - {visibilityLabel !== undefined ? ( 308 - <View style={[styles.blockHeader, t.atoms.border_contrast_low]}> 309 - <EyeSlashIcon size="sm" style={pal.text} /> 310 - <Text type="lg" style={pal.text}> 311 - {visibilityLabel} 312 - </Text> 313 - </View> 314 - ) : undefined} 315 - <PostMeta 316 - author={quote.author} 317 - moderation={moderation} 318 - showAvatar 319 - postHref={itemHref} 320 - timestamp={quote.indexedAt} 321 - /> 322 - </View> 323 - {moderation ? ( 324 - <PostAlerts 325 - modui={moderation.ui('contentView')} 326 - style={[a.py_xs]} 327 - /> 328 - ) : null} 329 - {richText ? ( 330 - <RichText 331 - value={richText} 332 - style={a.text_md} 333 - numberOfLines={20} 334 - disableLinks 335 - /> 336 - ) : null} 337 - {embed && <PostEmbeds embed={embed} moderation={moderation} />} 338 - </Link> 339 - </ContentHider> 340 - </View> 341 - ) 342 - } 343 - 344 - export function QuoteX({onRemove}: {onRemove: () => void}) { 345 - const {_} = useLingui() 346 - return ( 347 - <TouchableOpacity 348 - style={[ 349 - a.absolute, 350 - a.p_xs, 351 - a.rounded_full, 352 - a.align_center, 353 - a.justify_center, 354 - { 355 - top: 16, 356 - right: 10, 357 - backgroundColor: 'rgba(0, 0, 0, 0.75)', 358 - }, 359 - ]} 360 - onPress={onRemove} 361 - accessibilityRole="button" 362 - accessibilityLabel={_(msg`Remove quote`)} 363 - accessibilityHint={_(msg`Removes quoted post`)} 364 - onAccessibilityEscape={onRemove} 365 - hitSlop={HITSLOP_20}> 366 - <FontAwesomeIcon size={12} icon="xmark" style={s.white} /> 367 - </TouchableOpacity> 368 - ) 369 - } 370 - 371 - export function LazyQuoteEmbed({uri}: {uri: string}) { 372 - const {data} = useResolveLinkQuery(uri) 373 - const moderationOpts = useModerationOpts() 374 - if (!data || data.type !== 'record' || data.kind !== 'post') { 375 - return null 376 - } 377 - const moderation = moderationOpts 378 - ? moderatePost(data.view, moderationOpts) 379 - : undefined 380 - return <QuoteEmbed quote={data.view} moderation={moderation} /> 381 - } 382 - 383 - function viewRecordToPostView( 384 - viewRecord: AppBskyEmbedRecord.ViewRecord, 385 - ): AppBskyFeedDefs.PostView { 386 - const {value, embeds, ...rest} = viewRecord 387 - return { 388 - ...rest, 389 - $type: 'app.bsky.feed.defs#postView', 390 - record: value, 391 - embed: embeds?.[0], 392 - } 393 - } 394 - 395 - const styles = StyleSheet.create({ 396 - errorContainer: { 397 - flexDirection: 'row', 398 - alignItems: 'center', 399 - gap: 4, 400 - borderRadius: 8, 401 - marginTop: 8, 402 - paddingVertical: 14, 403 - paddingHorizontal: 14, 404 - borderWidth: StyleSheet.hairlineWidth, 405 - }, 406 - alert: { 407 - marginBottom: 6, 408 - }, 409 - blockHeader: { 410 - flexDirection: 'row', 411 - alignItems: 'center', 412 - gap: 4, 413 - marginBottom: 8, 414 - }, 415 - })
··· 1 + // import React from 'react' 2 + // import { 3 + // type StyleProp, 4 + // StyleSheet, 5 + // TouchableOpacity, 6 + // View, 7 + // type ViewStyle, 8 + // } from 'react-native' 9 + // import { 10 + // AppBskyEmbedExternal, 11 + // AppBskyEmbedImages, 12 + // AppBskyEmbedRecord, 13 + // AppBskyEmbedRecordWithMedia, 14 + // AppBskyEmbedVideo, 15 + // type AppBskyFeedDefs, 16 + // AppBskyFeedPost, 17 + // moderatePost, 18 + // type ModerationDecision, 19 + // RichText as RichTextAPI, 20 + // } from '@atproto/api' 21 + // import {AtUri} from '@atproto/api' 22 + // import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 23 + // import {msg, Trans} from '@lingui/macro' 24 + // import {useLingui} from '@lingui/react' 25 + // import {useQueryClient} from '@tanstack/react-query' 26 + // 27 + // import {HITSLOP_20} from '#/lib/constants' 28 + // import {usePalette} from '#/lib/hooks/usePalette' 29 + // import {InfoCircleIcon} from '#/lib/icons' 30 + // import {makeProfileLink} from '#/lib/routes/links' 31 + // import {s} from '#/lib/styles' 32 + // import {useDirectFetchRecords} from '#/state/preferences/direct-fetch-records' 33 + // import {useModerationOpts} from '#/state/preferences/moderation-opts' 34 + // import {useDirectFetchRecord} from '#/state/queries/direct-fetch-record' 35 + // import {precacheProfile} from '#/state/queries/profile' 36 + // import {useResolveLinkQuery} from '#/state/queries/resolve-link' 37 + // import {useSession} from '#/state/session' 38 + // import {atoms as a, useTheme} from '#/alf' 39 + // import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash' 40 + // import {RichText} from '#/components/RichText' 41 + // import {SubtleWebHover} from '#/components/SubtleWebHover' 42 + // import * as bsky from '#/types/bsky' 43 + // import {ContentHider} from '../../../../components/moderation/ContentHider' 44 + // import {PostAlerts} from '../../../../components/moderation/PostAlerts' 45 + // import {Link} from '../Link' 46 + // import {PostMeta} from '../PostMeta' 47 + // import {Text} from '../text/Text' 48 + // import {PostEmbeds} from '.' 49 + // import {type QuoteEmbedViewContext} from './types' 50 + // 51 + // export function MaybeQuoteEmbed({ 52 + // embed, 53 + // onOpen, 54 + // style, 55 + // allowNestedQuotes, 56 + // viewContext, 57 + // }: { 58 + // embed: AppBskyEmbedRecord.View 59 + // onOpen?: () => void 60 + // style?: StyleProp<ViewStyle> 61 + // allowNestedQuotes?: boolean 62 + // viewContext?: QuoteEmbedViewContext 63 + // }) { 64 + // const {_} = useLingui() 65 + // const t = useTheme() 66 + // const pal = usePalette('default') 67 + // const {currentAccount} = useSession() 68 + // 69 + // const directFetchEnabled = useDirectFetchRecords() 70 + // const shouldDirectFetch = 71 + // (AppBskyEmbedRecord.isViewBlocked(embed.record) || 72 + // AppBskyEmbedRecord.isViewDetached(embed.record)) && 73 + // directFetchEnabled 74 + // 75 + // const directRecord = useDirectFetchRecord({ 76 + // uri: 77 + // AppBskyEmbedRecord.isViewBlocked(embed.record) || 78 + // AppBskyEmbedRecord.isViewDetached(embed.record) 79 + // ? embed.record.uri 80 + // : '', 81 + // enabled: shouldDirectFetch, 82 + // }) 83 + // if ( 84 + // AppBskyEmbedRecord.isViewRecord(embed.record) && 85 + // AppBskyFeedPost.isRecord(embed.record.value) && 86 + // AppBskyFeedPost.validateRecord(embed.record.value).success 87 + // ) { 88 + // return ( 89 + // <QuoteEmbedModerated 90 + // viewRecord={embed.record} 91 + // onOpen={onOpen} 92 + // style={style} 93 + // allowNestedQuotes={allowNestedQuotes} 94 + // viewContext={viewContext} 95 + // /> 96 + // ) 97 + // } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) { 98 + // const record = directRecord.data 99 + // if (record !== undefined) { 100 + // return ( 101 + // <View> 102 + // <QuoteEmbedModerated 103 + // viewRecord={record} 104 + // onOpen={onOpen} 105 + // style={style} 106 + // allowNestedQuotes={allowNestedQuotes} 107 + // viewContext={viewContext} 108 + // visibilityLabel={_(msg`Blocked`)} 109 + // /> 110 + // </View> 111 + // ) 112 + // } 113 + // 114 + // return ( 115 + // <View 116 + // style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 117 + // <InfoCircleIcon size={18} style={pal.text} /> 118 + // <Text type="lg" style={pal.text}> 119 + // {directFetchEnabled ? ( 120 + // <Trans>Blocked...</Trans> 121 + // ) : ( 122 + // <Trans>Blocked</Trans> 123 + // )} 124 + // </Text> 125 + // </View> 126 + // ) 127 + // } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) { 128 + // return ( 129 + // <View 130 + // style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 131 + // <InfoCircleIcon size={18} style={pal.text} /> 132 + // <Text type="lg" style={pal.text}> 133 + // <Trans>Deleted</Trans> 134 + // </Text> 135 + // </View> 136 + // ) 137 + // } else if (AppBskyEmbedRecord.isViewDetached(embed.record)) { 138 + // const isViewerOwner = currentAccount?.did 139 + // ? embed.record.uri.includes(currentAccount.did) 140 + // : false 141 + // 142 + // const record = directRecord.data 143 + // if (record !== undefined) { 144 + // return ( 145 + // <View> 146 + // <QuoteEmbedModerated 147 + // viewRecord={record} 148 + // onOpen={onOpen} 149 + // style={style} 150 + // allowNestedQuotes={allowNestedQuotes} 151 + // viewContext={viewContext} 152 + // visibilityLabel={ 153 + // isViewerOwner ? _(`Removed by you`) : _(msg`Removed by author`) 154 + // } 155 + // /> 156 + // </View> 157 + // ) 158 + // } 159 + // 160 + // return ( 161 + // <View 162 + // style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}> 163 + // <InfoCircleIcon size={18} style={pal.text} /> 164 + // <Text type="lg" style={pal.text}> 165 + // {isViewerOwner ? ( 166 + // <Trans>Removed by you</Trans> 167 + // ) : ( 168 + // <Trans>Removed by author</Trans> 169 + // )} 170 + // {directFetchEnabled ? <Trans>...</Trans> : undefined} 171 + // </Text> 172 + // </View> 173 + // ) 174 + // } 175 + // return null 176 + // } 177 + // 178 + // function QuoteEmbedModerated({ 179 + // viewRecord, 180 + // onOpen, 181 + // style, 182 + // allowNestedQuotes, 183 + // viewContext, 184 + // visibilityLabel, 185 + // }: { 186 + // viewRecord: AppBskyEmbedRecord.ViewRecord 187 + // onOpen?: () => void 188 + // style?: StyleProp<ViewStyle> 189 + // allowNestedQuotes?: boolean 190 + // viewContext?: QuoteEmbedViewContext 191 + // visibilityLabel?: string 192 + // }) { 193 + // const moderationOpts = useModerationOpts() 194 + // const postView = React.useMemo( 195 + // () => viewRecordToPostView(viewRecord), 196 + // [viewRecord], 197 + // ) 198 + // const moderation = React.useMemo(() => { 199 + // return moderationOpts ? moderatePost(postView, moderationOpts) : undefined 200 + // }, [postView, moderationOpts]) 201 + // 202 + // return ( 203 + // <QuoteEmbed 204 + // quote={postView} 205 + // moderation={moderation} 206 + // onOpen={onOpen} 207 + // style={style} 208 + // allowNestedQuotes={allowNestedQuotes} 209 + // viewContext={viewContext} 210 + // visibilityLabel={visibilityLabel} 211 + // /> 212 + // ) 213 + // } 214 + // 215 + // export function QuoteEmbed({ 216 + // quote, 217 + // moderation, 218 + // onOpen, 219 + // style, 220 + // allowNestedQuotes, 221 + // visibilityLabel, 222 + // }: { 223 + // quote: AppBskyFeedDefs.PostView 224 + // moderation?: ModerationDecision 225 + // onOpen?: () => void 226 + // style?: StyleProp<ViewStyle> 227 + // allowNestedQuotes?: boolean 228 + // viewContext?: QuoteEmbedViewContext 229 + // visibilityLabel?: string 230 + // }) { 231 + // const t = useTheme() 232 + // const queryClient = useQueryClient() 233 + // const pal = usePalette('default') 234 + // const itemUrip = new AtUri(quote.uri) 235 + // const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey) 236 + // const itemTitle = `Post by ${quote.author.handle}` 237 + // 238 + // const richText = React.useMemo(() => { 239 + // if ( 240 + // !bsky.dangerousIsType<AppBskyFeedPost.Record>( 241 + // quote.record, 242 + // AppBskyFeedPost.isRecord, 243 + // ) 244 + // ) 245 + // return undefined 246 + // const {text, facets} = quote.record 247 + // return text.trim() 248 + // ? new RichTextAPI({text: text, facets: facets}) 249 + // : undefined 250 + // }, [quote.record]) 251 + // 252 + // const embed = React.useMemo(() => { 253 + // const e = quote.embed 254 + // 255 + // if (allowNestedQuotes) { 256 + // return e 257 + // } else { 258 + // if ( 259 + // AppBskyEmbedImages.isView(e) || 260 + // AppBskyEmbedExternal.isView(e) || 261 + // AppBskyEmbedVideo.isView(e) 262 + // ) { 263 + // return e 264 + // } else if ( 265 + // AppBskyEmbedRecordWithMedia.isView(e) && 266 + // (AppBskyEmbedImages.isView(e.media) || 267 + // AppBskyEmbedExternal.isView(e.media) || 268 + // AppBskyEmbedVideo.isView(e.media)) 269 + // ) { 270 + // return e.media 271 + // } 272 + // } 273 + // }, [quote.embed, allowNestedQuotes]) 274 + // 275 + // const onBeforePress = React.useCallback(() => { 276 + // precacheProfile(queryClient, quote.author) 277 + // onOpen?.() 278 + // }, [queryClient, quote.author, onOpen]) 279 + // 280 + // const [hover, setHover] = React.useState(false) 281 + // return ( 282 + // <View 283 + // onPointerEnter={() => { 284 + // setHover(true) 285 + // }} 286 + // onPointerLeave={() => { 287 + // setHover(false) 288 + // }}> 289 + // <ContentHider 290 + // modui={moderation?.ui('contentList')} 291 + // style={[ 292 + // a.rounded_md, 293 + // a.p_md, 294 + // a.mt_sm, 295 + // a.border, 296 + // t.atoms.border_contrast_low, 297 + // style, 298 + // ]} 299 + // childContainerStyle={[a.pt_sm]}> 300 + // <SubtleWebHover hover={hover} /> 301 + // <Link 302 + // hoverStyle={{borderColor: pal.colors.borderLinkHover}} 303 + // href={itemHref} 304 + // title={itemTitle} 305 + // onBeforePress={onBeforePress}> 306 + // <View pointerEvents="none"> 307 + // {visibilityLabel !== undefined ? ( 308 + // <View style={[styles.blockHeader, t.atoms.border_contrast_low]}> 309 + // <EyeSlashIcon size="sm" style={pal.text} /> 310 + // <Text type="lg" style={pal.text}> 311 + // {visibilityLabel} 312 + // </Text> 313 + // </View> 314 + // ) : undefined} 315 + // <PostMeta 316 + // author={quote.author} 317 + // moderation={moderation} 318 + // showAvatar 319 + // postHref={itemHref} 320 + // timestamp={quote.indexedAt} 321 + // /> 322 + // </View> 323 + // {moderation ? ( 324 + // <PostAlerts 325 + // modui={moderation.ui('contentView')} 326 + // style={[a.py_xs]} 327 + // /> 328 + // ) : null} 329 + // {richText ? ( 330 + // <RichText 331 + // value={richText} 332 + // style={a.text_md} 333 + // numberOfLines={20} 334 + // disableLinks 335 + // /> 336 + // ) : null} 337 + // {embed && <PostEmbeds embed={embed} moderation={moderation} />} 338 + // </Link> 339 + // </ContentHider> 340 + // </View> 341 + // ) 342 + // } 343 + // 344 + // export function QuoteX({onRemove}: {onRemove: () => void}) { 345 + // const {_} = useLingui() 346 + // return ( 347 + // <TouchableOpacity 348 + // style={[ 349 + // a.absolute, 350 + // a.p_xs, 351 + // a.rounded_full, 352 + // a.align_center, 353 + // a.justify_center, 354 + // { 355 + // top: 16, 356 + // right: 10, 357 + // backgroundColor: 'rgba(0, 0, 0, 0.75)', 358 + // }, 359 + // ]} 360 + // onPress={onRemove} 361 + // accessibilityRole="button" 362 + // accessibilityLabel={_(msg`Remove quote`)} 363 + // accessibilityHint={_(msg`Removes quoted post`)} 364 + // onAccessibilityEscape={onRemove} 365 + // hitSlop={HITSLOP_20}> 366 + // <FontAwesomeIcon size={12} icon="xmark" style={s.white} /> 367 + // </TouchableOpacity> 368 + // ) 369 + // } 370 + // 371 + // export function LazyQuoteEmbed({uri}: {uri: string}) { 372 + // const {data} = useResolveLinkQuery(uri) 373 + // const moderationOpts = useModerationOpts() 374 + // if (!data || data.type !== 'record' || data.kind !== 'post') { 375 + // return null 376 + // } 377 + // const moderation = moderationOpts 378 + // ? moderatePost(data.view, moderationOpts) 379 + // : undefined 380 + // return <QuoteEmbed quote={data.view} moderation={moderation} /> 381 + // } 382 + // 383 + // function viewRecordToPostView( 384 + // viewRecord: AppBskyEmbedRecord.ViewRecord, 385 + // ): AppBskyFeedDefs.PostView { 386 + // const {value, embeds, ...rest} = viewRecord 387 + // return { 388 + // ...rest, 389 + // $type: 'app.bsky.feed.defs#postView', 390 + // record: value, 391 + // embed: embeds?.[0], 392 + // } 393 + // } 394 + // 395 + // const styles = StyleSheet.create({ 396 + // errorContainer: { 397 + // flexDirection: 'row', 398 + // alignItems: 'center', 399 + // gap: 4, 400 + // borderRadius: 8, 401 + // marginTop: 8, 402 + // paddingVertical: 14, 403 + // paddingHorizontal: 14, 404 + // borderWidth: StyleSheet.hairlineWidth, 405 + // }, 406 + // alert: { 407 + // marginBottom: 6, 408 + // }, 409 + // blockHeader: { 410 + // flexDirection: 'row', 411 + // alignItems: 'center', 412 + // gap: 4, 413 + // marginBottom: 8, 414 + // }, 415 + // })