import { AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost, AppBskyGraphDefs, AppBskyGraphStarterpack, AppBskyLabelerDefs, } from '@atproto/api' import {ComponentChildren, h} from 'preact' import {useMemo} from 'preact/hooks' import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' import playIcon from '../../assets/play_filled_corner2_rounded.svg' import starterPackIcon from '../../assets/starterPack.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)) { // Embed type does not exist in the app, so show nothing return null } // Case 3.5: Starter pack if (AppBskyGraphDefs.isStarterPackViewBasic(record)) { return } // Case 3.6: Post not found if (AppBskyEmbedRecord.isViewNotFound(record)) { return Quoted post not found, it may have been deleted. } // Case 3.7: Post blocked if (AppBskyEmbedRecord.isViewBlocked(record)) { return The quoted post is blocked. } // Case 3.8: Detached quote post if (AppBskyEmbedRecord.isViewDetached(record)) { // Just don't show anything return null } // Unknown embed type return null } // Case 4: Video if (AppBskyEmbedVideo.isView(content)) { return } // Case 5: Record with media if ( AppBskyEmbedRecordWithMedia.isView(content) && AppBskyEmbedRecord.isViewRecord(content.record.record) ) { return (
) } // Unknown embed type return null } 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 GenericWithImageEmbed({ title, subtitle, href, image, description, }: { title: string subtitle: string href: string image?: string description?: string }) { return (
{image ? ( {title} ) : (
)}

{title}

{subtitle}

{description &&

{description}

} ) } // just the thumbnail and a play button function VideoEmbed({content}: {content: AppBskyEmbedVideo.View}) { let aspectRatio = 1 if (content.aspectRatio) { const {width, height} = content.aspectRatio aspectRatio = clamp(width / height, 1 / 1, 3 / 1) } return (
{content.alt}
) } function StarterPackEmbed({ content, }: { content: AppBskyGraphDefs.StarterPackViewBasic }) { if (!AppBskyGraphStarterpack.isRecord(content.record)) { return null } const starterPackHref = getStarterPackHref(content) const imageUri = getStarterPackImage(content) return (

{content.record.name}

Starter pack by{' '} {content.creator.displayName || `@${content.creator.handle}`}

{content.record.description && (

{content.record.description}

)} {!!content.joinedAllTimeCount && content.joinedAllTimeCount > 50 && (

{content.joinedAllTimeCount} users have joined!

)}
) } // from #/lib/strings/starter-pack.ts function getStarterPackImage(starterPack: AppBskyGraphDefs.StarterPackView) { const rkey = getRkey(starterPack.uri) return `https://ogcard.cdn.bsky.app/start/${starterPack.creator.did}/${rkey}` } function getStarterPackHref( starterPack: AppBskyGraphDefs.StarterPackViewBasic, ) { const rkey = getRkey(starterPack.uri) const handleOrDid = starterPack.creator.handle || starterPack.creator.did return `/starter-pack/${handleOrDid}/${rkey}` } function clamp(num: number, min: number, max: number) { return Math.max(min, Math.min(num, max)) }