import React from 'react' import { StyleProp, StyleSheet, TouchableOpacity, View, ViewStyle, } from 'react-native' import { AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost, moderatePost, ModerationDecision, RichText as RichTextAPI, } from '@atproto/api' import {AtUri} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {HITSLOP_20} from '#/lib/constants' import {usePalette} from '#/lib/hooks/usePalette' import {InfoCircleIcon} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {s} from '#/lib/styles' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {precacheProfile} from '#/state/queries/profile' import {useResolveLinkQuery} from '#/state/queries/resolve-link' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {RichText} from '#/components/RichText' import {SubtleWebHover} from '#/components/SubtleWebHover' import * as bsky from '#/types/bsky' import {ContentHider} from '../../../../components/moderation/ContentHider' import {PostAlerts} from '../../../../components/moderation/PostAlerts' import {Link} from '../Link' import {PostMeta} from '../PostMeta' import {Text} from '../text/Text' import {PostEmbeds} from '.' import {QuoteEmbedViewContext} from './types' export function MaybeQuoteEmbed({ embed, onOpen, style, allowNestedQuotes, viewContext, }: { embed: AppBskyEmbedRecord.View onOpen?: () => void style?: StyleProp allowNestedQuotes?: boolean viewContext?: QuoteEmbedViewContext }) { const t = useTheme() const pal = usePalette('default') const {currentAccount} = useSession() if ( AppBskyEmbedRecord.isViewRecord(embed.record) && AppBskyFeedPost.isRecord(embed.record.value) && AppBskyFeedPost.validateRecord(embed.record.value).success ) { return ( ) } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) { return ( Blocked ) } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) { return ( Deleted ) } else if (AppBskyEmbedRecord.isViewDetached(embed.record)) { const isViewerOwner = currentAccount?.did ? embed.record.uri.includes(currentAccount.did) : false return ( {isViewerOwner ? ( Removed by you ) : ( Removed by author )} ) } return null } function QuoteEmbedModerated({ viewRecord, onOpen, style, allowNestedQuotes, viewContext, }: { viewRecord: AppBskyEmbedRecord.ViewRecord onOpen?: () => void style?: StyleProp allowNestedQuotes?: boolean viewContext?: QuoteEmbedViewContext }) { const moderationOpts = useModerationOpts() const postView = React.useMemo( () => viewRecordToPostView(viewRecord), [viewRecord], ) const moderation = React.useMemo(() => { return moderationOpts ? moderatePost(postView, moderationOpts) : undefined }, [postView, moderationOpts]) return ( ) } export function QuoteEmbed({ quote, moderation, onOpen, style, allowNestedQuotes, }: { quote: AppBskyFeedDefs.PostView moderation?: ModerationDecision onOpen?: () => void style?: StyleProp allowNestedQuotes?: boolean viewContext?: QuoteEmbedViewContext }) { const t = useTheme() const queryClient = useQueryClient() const pal = usePalette('default') const itemUrip = new AtUri(quote.uri) const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey) const itemTitle = `Post by ${quote.author.handle}` const richText = React.useMemo(() => { if ( !bsky.dangerousIsType( quote.record, AppBskyFeedPost.isRecord, ) ) return undefined const {text, facets} = quote.record return text.trim() ? new RichTextAPI({text: text, facets: facets}) : undefined }, [quote.record]) const embed = React.useMemo(() => { const e = quote.embed if (allowNestedQuotes) { return e } else { if ( AppBskyEmbedImages.isView(e) || AppBskyEmbedExternal.isView(e) || AppBskyEmbedVideo.isView(e) ) { return e } else if ( AppBskyEmbedRecordWithMedia.isView(e) && (AppBskyEmbedImages.isView(e.media) || AppBskyEmbedExternal.isView(e.media) || AppBskyEmbedVideo.isView(e.media)) ) { return e.media } } }, [quote.embed, allowNestedQuotes]) const onBeforePress = React.useCallback(() => { precacheProfile(queryClient, quote.author) onOpen?.() }, [queryClient, quote.author, onOpen]) const [hover, setHover] = React.useState(false) return ( { setHover(true) }} onPointerLeave={() => { setHover(false) }}> {moderation ? ( ) : null} {richText ? ( ) : null} {embed && } ) } export function QuoteX({onRemove}: {onRemove: () => void}) { const {_} = useLingui() return ( ) } export function LazyQuoteEmbed({uri}: {uri: string}) { const {data} = useResolveLinkQuery(uri) const moderationOpts = useModerationOpts() if (!data || data.type !== 'record' || data.kind !== 'post') { return null } const moderation = moderationOpts ? moderatePost(data.view, moderationOpts) : undefined return } function viewRecordToPostView( viewRecord: AppBskyEmbedRecord.ViewRecord, ): AppBskyFeedDefs.PostView { const {value, embeds, ...rest} = viewRecord return { ...rest, $type: 'app.bsky.feed.defs#postView', record: value, embed: embeds?.[0], } } const styles = StyleSheet.create({ errorContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, borderRadius: 8, marginTop: 8, paddingVertical: 14, paddingHorizontal: 14, borderWidth: StyleSheet.hairlineWidth, }, alert: { marginBottom: 6, }, })