From 4b3ec5573241b9c71504dfd0bd5f181cbde19a49 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Sat, 13 Apr 2024 03:58:40 +0100 Subject: [Embeds] Embed subdomain landing page (#3501) * add build output to web build * simplify post-build step by copying everything at once * make script that converts placeholder -> iframe * dynamically resize iframe based on inner content Requires the iframe content to `postMessage` its height back up to the parent * add lang to embed * svg explicit height -> viewBox * add build output to web build * simplify post-build step by copying everything at once * attempt to fix go embed issue * rm changes to bskyweb * remove another bskyweb change * embed landing page * Drop xl breakpoint, too far down * Remove pointer enter behavior * Avoid button width jump * Escape HTML --------- Co-authored-by: Dan Abramov --- bskyembed/src/components/embed.tsx | 299 +++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 bskyembed/src/components/embed.tsx (limited to 'bskyembed/src/components/embed.tsx') diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx new file mode 100644 index 000000000..2f9f6b3cd --- /dev/null +++ b/bskyembed/src/components/embed.tsx @@ -0,0 +1,299 @@ +import { + AppBskyEmbedExternal, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyFeedDefs, + AppBskyFeedPost, + AppBskyGraphDefs, + AppBskyLabelerDefs, +} from '@atproto/api' +import {ComponentChildren, h} from 'preact' + +import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' +import {getRkey} from '../utils' +import {Link} from './link' + +export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { + 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)) { + 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 + } + return ( + +
+ +

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

+
+ {text &&

{text}

} + {record.embeds + ?.filter(embed => { + if (AppBskyEmbedImages.isView(embed)) return true + if (AppBskyEmbedExternal.isView(embed)) return true + return false + }) + .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)) { + 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}: {content: AppBskyEmbedImages.View}) { + 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}: {content: AppBskyEmbedExternal.View}) { + function toNiceDomain(url: string): string { + try { + const urlp = new URL(url) + return urlp.host ? urlp.host : url + } catch (e) { + return url + } + } + 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}

} + + ) +} -- cgit 1.4.1