import '../index.css' import {AppBskyFeedDefs, AppBskyFeedPost, AtUri, BskyAgent} from '@atproto/api' import {h, render} from 'preact' import {useEffect, useMemo, useRef, useState} from 'preact/hooks' import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg' import logo from '../../assets/logo.svg' import {initColorMode} from '../color-mode' import {Container} from '../components/container' import {Link} from '../components/link' import {Post} from '../components/post' import {niceDate} from '../utils' const DEFAULT_POST = 'https://bsky.app/profile/emilyliu.me/post/3jzn6g7ixgq2y' const DEFAULT_URI = 'at://did:plc:vjug55kidv6sye7ykr5faxxn/app.bsky.feed.post/3jzn6g7ixgq2y' export const EMBED_SERVICE = 'https://embed.bsky.app' export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` const root = document.getElementById('app') if (!root) throw new Error('No root element') initColorMode() const agent = new BskyAgent({ service: 'https://public.api.bsky.app', }) render(, root) function LandingPage() { const [uri, setUri] = useState('') const [error, setError] = useState(null) const [loading, setLoading] = useState(false) const [thread, setThread] = useState( null, ) useEffect(() => { void (async () => { setError(null) setThread(null) setLoading(true) try { let atUri = DEFAULT_URI if (uri) { if (uri.startsWith('at://')) { atUri = uri } else { try { const urlp = new URL(uri) if (!urlp.hostname.endsWith('bsky.app')) { throw new Error('Invalid hostname') } const split = urlp.pathname.slice(1).split('/') if (split.length < 4) { throw new Error('Invalid pathname') } const [profile, didOrHandle, type, rkey] = split if (profile !== 'profile' || type !== 'post') { throw new Error('Invalid profile or type') } let did = didOrHandle if (!didOrHandle.startsWith('did:')) { const resolution = await agent.resolveHandle({ handle: didOrHandle, }) if (!resolution.data.did) { throw new Error('No DID found') } did = resolution.data.did } atUri = `at://${did}/app.bsky.feed.post/${rkey}` } catch (err) { console.log(err) throw new Error('Invalid Bluesky URL') } } } const {data} = await agent.getPostThread({ uri: atUri, depth: 0, parentHeight: 0, }) if (!AppBskyFeedDefs.isThreadViewPost(data.thread)) { throw new Error('Post not found') } const pwiOptOut = !!data.thread.post.author.labels?.find( label => label.val === '!no-unauthenticated', ) if (pwiOptOut) { throw new Error( 'The author of this post has requested their posts not be displayed on external sites.', ) } setThread(data.thread) } catch (err) { console.error(err) setError(err instanceof Error ? err.message : 'Invalid Bluesky URL') } finally { setLoading(false) } })() }, [uri]) return (

Embed a Bluesky Post

setUri(e.currentTarget.value)} className="border rounded-lg py-3 w-full max-w-[600px] px-4 dark:bg-dimmedBg dark:border-slate-500" placeholder={DEFAULT_POST} /> {loading ? (
) : (
{!error && thread && uri && } {!error && thread && } {error && (

{error}

)}
)}
) } function Skeleton() { return (
) } function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { const ref = useRef(null) const [copied, setCopied] = useState(false) // reset copied state after 2 seconds useEffect(() => { if (copied) { const timeout = setTimeout(() => { setCopied(false) }, 2000) return () => clearTimeout(timeout) } }, [copied]) const snippet = useMemo(() => { const record = thread.post.record if (!AppBskyFeedPost.isRecord(record)) { return '' } const lang = record.langs && record.langs.length > 0 ? record.langs[0] : '' const profileHref = toShareUrl( ['/profile', thread.post.author.did].join('/'), ) const urip = new AtUri(thread.post.uri) const href = toShareUrl( ['/profile', thread.post.author.did, 'post', urip.rkey].join('/'), ) // x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x // DO NOT ADD ANY NEW INTERPOLATIONS BELOW WITHOUT ESCAPING THEM! // Also, keep this code synced with the app code in Embed.tsx. // x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x return `

${escapeHtml(record.text)}${ record.embed ? `

[image or embed]` : '' }

— ${escapeHtml( thread.post.author.displayName || thread.post.author.handle, )} (@${escapeHtml( thread.post.author.handle, )}) ${escapeHtml( niceDate(thread.post.indexedAt), )}
` }, [thread]) return (
{ ref.current?.select() }} />
) } function toShareUrl(path: string) { return `https://bsky.app${path}?ref_src=embed` } /** * Based on a snippet of code from React, which itself was based on the escape-html library. * Copyright (c) Meta Platforms, Inc. and affiliates * Copyright (c) 2012-2013 TJ Holowaychuk * Copyright (c) 2015 Andreas Lubbe * Copyright (c) 2015 Tiancheng "Timothy" Gu * Licensed as MIT. */ const matchHtmlRegExp = /["'&<>]/ function escapeHtml(string: string) { const str = String(string) const match = matchHtmlRegExp.exec(str) if (!match) { return str } let escape let html = '' let index let lastIndex = 0 for (index = match.index; index < str.length; index++) { switch (str.charCodeAt(index)) { case 34: // " escape = '"' break case 38: // & escape = '&' break case 39: // ' escape = ''' break case 60: // < escape = '<' break case 62: // > escape = '>' break default: continue } if (lastIndex !== index) { html += str.slice(lastIndex, index) } lastIndex = index + 1 html += escape } return lastIndex !== index ? html + str.slice(lastIndex, index) : html }