import { AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, AppBskyFeedPost, AppBskyGraphDefs, AppBskyLabelerDefs, } from '@atproto/api' import {ComponentChildren, h} from 'preact' import {useMemo} from 'preact/hooks' import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' import {CONTENT_LABELS, labelsToInfo} from '../labels' import {getRkey} from '../utils' import {Link} from './link' export function Embed({ content, labels, hideRecord, }: { content: AppBskyFeedDefs.PostView['embed'] labels: AppBskyFeedDefs.PostView['labels'] hideRecord?: boolean }) { const labelInfo = useMemo(() => labelsToInfo(labels), [labels]) if (!content) return null try { // Case 1: Image if (AppBskyEmbedImages.isView(content)) { return } // Case 2: External link if (AppBskyEmbedExternal.isView(content)) { return } // Case 3: Record (quote or linked post) if (AppBskyEmbedRecord.isView(content)) { if (hideRecord) { return null } const record = content.record // Case 3.1: Post if (AppBskyEmbedRecord.isViewRecord(record)) { const pwiOptOut = !!record.author.labels?.find( label => label.val === '!no-unauthenticated', ) if (pwiOptOut) { return ( The author of the quoted post has requested their posts not be displayed on external sites. ) } let text if (AppBskyFeedPost.isRecord(record.value)) { text = record.value.text } const isAuthorLabeled = record.author.labels?.some(label => CONTENT_LABELS.includes(label.val), ) return (

{record.author.displayName} @{record.author.handle}

{text &&

{text}

} {record.embeds?.map(embed => ( ))} ) } // Case 3.2: List if (AppBskyGraphDefs.isListView(record)) { return ( ) } // Case 3.3: Feed if (AppBskyFeedDefs.isGeneratorView(record)) { return ( ) } // Case 3.4: Labeler if (AppBskyLabelerDefs.isLabelerView(record)) { return ( ) } // Case 3.5: Post not found if (AppBskyEmbedRecord.isViewNotFound(record)) { return Quoted post not found, it may have been deleted. } // Case 3.6: Post blocked if (AppBskyEmbedRecord.isViewBlocked(record)) { return The quoted post is blocked. } throw new Error('Unknown embed type') } // Case 4: Record with media if ( AppBskyEmbedRecordWithMedia.isView(content) && AppBskyEmbedRecord.isViewRecord(content.record.record) ) { return (
) } throw new Error('Unsupported embed type') } catch (err) { return ( {err instanceof Error ? err.message : 'An error occurred'} ) } } function Info({children}: {children: ComponentChildren}) { return (

{children}

) } function ImageEmbed({ content, labelInfo, }: { content: AppBskyEmbedImages.View labelInfo?: string }) { if (labelInfo) { return {labelInfo} } switch (content.images.length) { case 1: return ( {content.images[0].alt} ) case 2: return (
{content.images.map((image, i) => ( {image.alt} ))}
) case 3: return (
{content.images[0].alt}
{content.images.slice(1).map((image, i) => ( {image.alt} ))}
) case 4: return (
{content.images.map((image, i) => ( {image.alt} ))}
) default: return null } } function ExternalEmbed({ content, labelInfo, }: { content: AppBskyEmbedExternal.View labelInfo?: string }) { function toNiceDomain(url: string): string { try { const urlp = new URL(url) return urlp.host ? urlp.host : url } catch (e) { return url } } if (labelInfo) { return {labelInfo} } return ( {content.external.thumb && ( )}

{toNiceDomain(content.external.uri)}

{content.external.title}

{content.external.description}

) } function GenericWithImage({ title, subtitle, href, image, description, }: { title: string subtitle: string href: string image?: string description?: string }) { return (
{image ? ( {title} ) : (
)}

{title}

{subtitle}

{description &&

{description}

} ) }