diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-04-13 03:58:40 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-13 03:58:40 +0100 |
commit | 4b3ec5573241b9c71504dfd0bd5f181cbde19a49 (patch) | |
tree | 698c2463b389cdf6e14536610e8f96f200ddaaa3 /bskyembed | |
parent | 8e29b1f63309ef9ac2da21f62e03b66d477244e9 (diff) | |
download | voidsky-4b3ec5573241b9c71504dfd0bd5f181cbde19a49.tar.zst |
[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 <dan.abramov@gmail.com>
Diffstat (limited to 'bskyembed')
-rw-r--r-- | bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg | 1 | ||||
-rw-r--r-- | bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg | 2 | ||||
-rw-r--r-- | bskyembed/index.html | 2 | ||||
-rw-r--r-- | bskyembed/package.json | 1 | ||||
-rw-r--r-- | bskyembed/post.html | 19 | ||||
-rw-r--r-- | bskyembed/snippet/embed.ts | 90 | ||||
-rw-r--r-- | bskyembed/src/components/container.tsx | 55 | ||||
-rw-r--r-- | bskyembed/src/components/embed.tsx (renamed from bskyembed/src/embed.tsx) | 4 | ||||
-rw-r--r-- | bskyembed/src/components/link.tsx (renamed from bskyembed/src/link.tsx) | 0 | ||||
-rw-r--r-- | bskyembed/src/components/post.tsx (renamed from bskyembed/src/post.tsx) | 14 | ||||
-rw-r--r-- | bskyembed/src/container.tsx | 32 | ||||
-rw-r--r-- | bskyembed/src/screens/landing.tsx | 266 | ||||
-rw-r--r-- | bskyembed/src/screens/post.tsx (renamed from bskyembed/src/main.tsx) | 20 | ||||
-rw-r--r-- | bskyembed/src/utils.ts | 5 | ||||
-rw-r--r-- | bskyembed/tsconfig.snippet.json | 10 | ||||
-rw-r--r-- | bskyembed/vite.config.ts | 10 |
16 files changed, 476 insertions, 55 deletions
diff --git a/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg b/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg new file mode 100644 index 000000000..afb8f245f --- /dev/null +++ b/bskyembed/assets/arrowBottom_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#000" fill-rule="evenodd" d="M12 21a1 1 0 0 1-.707-.293l-6-6a1 1 0 1 1 1.414-1.414L11 17.586V4a1 1 0 1 1 2 0v13.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-6 6A1 1 0 0 1 12 21Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg b/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg index 36db9a88a..9962a20bd 100644 --- a/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg +++ b/bskyembed/assets/bubble_filled_stroke2_corner2_rounded.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="rgb(10,122,255)" d="M19.002 3a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h14Z"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="rgb(10,122,255)" d="M19.002 3a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h14Z"/></svg> diff --git a/bskyembed/index.html b/bskyembed/index.html index aa9335e8c..61d0c7d17 100644 --- a/bskyembed/index.html +++ b/bskyembed/index.html @@ -14,6 +14,6 @@ </head> <body> <div id="app"></div> - <script type="module" src="/src/main.tsx"></script> + <script type="module" src="/src/screens/landing.tsx"></script> </body> </html> diff --git a/bskyembed/package.json b/bskyembed/package.json index 6fb919c9e..f610e8c06 100644 --- a/bskyembed/package.json +++ b/bskyembed/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", + "build-snippet": "tsc --project tsconfig.snippet.json", "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src" }, "dependencies": { diff --git a/bskyembed/post.html b/bskyembed/post.html new file mode 100644 index 000000000..5f550495f --- /dev/null +++ b/bskyembed/post.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Bluesky Embed</title> + <link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png"> + <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png"> + <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png"> + <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#1185fe"> + <meta name="theme-color"> + <meta name="application-name" content="Bluesky"> + <meta name="generator" content="bskyweb"> +</head> +<body> + <div id="app"></div> + <script type="module" src="/src/screens/post.tsx"></script> +</body> +</html> diff --git a/bskyembed/snippet/embed.ts b/bskyembed/snippet/embed.ts new file mode 100644 index 000000000..f2b9b442e --- /dev/null +++ b/bskyembed/snippet/embed.ts @@ -0,0 +1,90 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +interface Window { + bluesky: { + scan: (element?: Pick<Element, 'querySelectorAll'>) => void + } +} + +const EMBED_URL = 'https://embed.bsky.app' + +window.bluesky = window.bluesky || { + scan, +} + +/** + * Listen for messages from the Bluesky embed iframe and adjust the height of + * the iframe accordingly. + */ +window.addEventListener('message', event => { + if (event.origin !== EMBED_URL) { + return + } + + const id = (event.data as {id: string}).id + if (!id) { + return + } + + const embed = document.querySelector<HTMLIFrameElement>( + `[data-bluesky-id="${id}"]`, + ) + + if (!embed) { + return + } + + const height = (event.data as {height: number}).height + if (height) { + embed.style.height = `${height}px` + } +}) + +/** + * Scan the document for all elements with the data-bluesky-aturi attribute, + * and initialize them as Bluesky embeds. + * + * @param element Only scan this specific element @default document @optional + * @returns + */ +function scan(node = document) { + const embeds = node.querySelectorAll('[data-bluesky-uri]') + + for (let i = 0; i < embeds.length; i++) { + const id = String(Math.random()).slice(2) + + const embed = embeds[i] + const aturi = embed.getAttribute('data-bluesky-uri') + + if (!aturi) { + continue + } + + const iframe = document.createElement('iframe') + iframe.setAttribute('data-bluesky-id', id) + iframe.src = `${EMBED_URL}/embed/${aturi.slice('at://'.length)}?id=${id}` + iframe.width = '100%' + iframe.style.border = 'none' + iframe.style.display = 'block' + iframe.style.flexGrow = '1' + iframe.frameBorder = '0' + iframe.scrolling = 'no' + + const container = document.createElement('div') + container.style.maxWidth = '600px' + container.style.width = '100%' + container.style.marginTop = '10px' + container.style.marginBottom = '10px' + container.style.display = 'flex' + container.className = 'bluesky-embed' + + container.appendChild(iframe) + + embed.replaceWith(container) + } +} + +if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { + scan() +} else { + document.addEventListener('DOMContentLoaded', () => scan()) +} diff --git a/bskyembed/src/components/container.tsx b/bskyembed/src/components/container.tsx new file mode 100644 index 000000000..a96addc8c --- /dev/null +++ b/bskyembed/src/components/container.tsx @@ -0,0 +1,55 @@ +import {ComponentChildren, h} from 'preact' +import {useEffect, useRef} from 'preact/hooks' + +import {Link} from './link' + +export function Container({ + children, + href, +}: { + children: ComponentChildren + href: string +}) { + const ref = useRef<HTMLDivElement>(null) + const prevHeight = useRef(0) + + useEffect(() => { + if (ref.current) { + const observer = new ResizeObserver(entries => { + const entry = entries[0] + if (!entry) return + + let {height} = entry.contentRect + height += 2 // border top and bottom + if (height !== prevHeight.current) { + prevHeight.current = height + window.parent.postMessage( + {height, id: new URLSearchParams(window.location.search).get('id')}, + '*', + ) + } + }) + observer.observe(ref.current) + return () => observer.disconnect() + } + }, []) + + return ( + <div + ref={ref} + className="w-full bg-white hover:bg-neutral-50 relative transition-colors max-w-[600px] min-w-[300px] flex border rounded-xl" + onClick={() => { + if (ref.current) { + // forwardRef requires preact/compat - let's keep it simple + // to keep the bundle size down + const anchor = ref.current.querySelector('a') + if (anchor) { + anchor.click() + } + } + }}> + <Link href={href} /> + <div className="flex-1 px-4 pt-3 pb-2.5">{children}</div> + </div> + ) +} diff --git a/bskyembed/src/embed.tsx b/bskyembed/src/components/embed.tsx index 0980c5e7f..2f9f6b3cd 100644 --- a/bskyembed/src/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -10,9 +10,9 @@ import { } from '@atproto/api' import {ComponentChildren, h} from 'preact' -import infoIcon from '../assets/circleInfo_stroke2_corner0_rounded.svg' +import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' +import {getRkey} from '../utils' import {Link} from './link' -import {getRkey} from './utils' export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { if (!content) return null diff --git a/bskyembed/src/link.tsx b/bskyembed/src/components/link.tsx index 7226ecf3d..7226ecf3d 100644 --- a/bskyembed/src/link.tsx +++ b/bskyembed/src/components/link.tsx diff --git a/bskyembed/src/post.tsx b/bskyembed/src/components/post.tsx index e10a502d2..dcbf3e336 100644 --- a/bskyembed/src/post.tsx +++ b/bskyembed/src/components/post.tsx @@ -1,14 +1,14 @@ import {AppBskyFeedDefs, AppBskyFeedPost, RichText} from '@atproto/api' import {h} from 'preact' -import replyIcon from '../assets/bubble_filled_stroke2_corner2_rounded.svg' -import likeIcon from '../assets/heart2_filled_stroke2_corner0_rounded.svg' -import logo from '../assets/logo.svg' -import repostIcon from '../assets/repost_stroke2_corner2_rounded.svg' +import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg' +import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg' +import logo from '../../assets/logo.svg' +import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg' +import {getRkey, niceDate} from '../utils' import {Container} from './container' import {Embed} from './embed' import {Link} from './link' -import {getRkey, niceDate} from './utils' interface Props { thread: AppBskyFeedDefs.ThreadViewPost @@ -25,7 +25,7 @@ export function Post({thread}: Props) { const href = `/profile/${post.author.did}/post/${getRkey(post)}` return ( <Container href={href}> - <div className="flex-1 flex-col flex gap-2"> + <div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}> <div className="flex gap-2.5 items-center"> <Link href={`/profile/${post.author.did}`} className="rounded-full"> <img @@ -143,7 +143,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) { } return ( - <p className="text-lg leading-6 break-word break-words whitespace-pre-wrap"> + <p className="min-[300px]:text-lg leading-6 break-word break-words whitespace-pre-wrap"> {richText} </p> ) diff --git a/bskyembed/src/container.tsx b/bskyembed/src/container.tsx deleted file mode 100644 index 0d120e1b7..000000000 --- a/bskyembed/src/container.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import {ComponentChildren, h} from 'preact' -import {useRef} from 'preact/hooks' - -import {Link} from './link' - -export function Container({ - children, - href, -}: { - children: ComponentChildren - href: string -}) { - const ref = useRef<HTMLDivElement>(null) - return ( - <div - ref={ref} - className="w-full bg-white hover:bg-neutral-50 relative transition-colors max-w-[550px] min-w-[300px] flex border rounded-xl px-4 pt-3 pb-2.5" - onClick={() => { - if (ref.current) { - // forwardRef requires preact/compat - let's keep it simple - // to keep the bundle size down - const anchor = ref.current.querySelector('a') - if (anchor) { - anchor.click() - } - } - }}> - <Link href={href} /> - {children} - </div> - ) -} diff --git a/bskyembed/src/screens/landing.tsx b/bskyembed/src/screens/landing.tsx new file mode 100644 index 000000000..88e84ffb6 --- /dev/null +++ b/bskyembed/src/screens/landing.tsx @@ -0,0 +1,266 @@ +import '../index.css' + +import {AppBskyFeedDefs, AppBskyFeedPost, AtUri, BskyAgent} from '@atproto/api' +import {Fragment, 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 {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') + +const agent = new BskyAgent({ + service: 'https://public.api.bsky.app', +}) + +render(<LandingPage />, root) + +function LandingPage() { + const [uri, setUri] = useState('') + const [error, setError] = useState<string | null>(null) + const [thread, setThread] = useState<AppBskyFeedDefs.ThreadViewPost | null>( + null, + ) + + useEffect(() => { + void (async () => { + setError(null) + 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') + } + + setThread(data.thread) + } catch (err) { + console.error(err) + setError(err instanceof Error ? err.message : 'Invalid Bluesky URL') + } + })() + }, [uri]) + + return ( + <main className="w-full min-h-screen flex flex-col items-center gap-8 py-14 px-4 md:pt-32"> + <Link + href="https://bsky.social/about" + className="transition-transform hover:scale-110"> + <img src={logo as string} className="h-10" /> + </Link> + + <h1 className="text-4xl font-bold">Embed a Bluesky Post</h1> + + <div className="w-full max-w-[600px] flex flex-col gap-2"> + <input + type="text" + value={uri} + onInput={e => setUri(e.currentTarget.value)} + className="border rounded-lg py-3 w-full max-w-[600px] px-4" + placeholder={DEFAULT_POST} + /> + <p className={`text-red-500 ${error ? '' : 'invisible'}`}>{error}</p> + </div> + + <img src={arrowBottom as string} className="w-6" /> + + <div className="w-full max-w-[600px] gap-8 flex flex-col"> + {uri && !error && thread && <Snippet thread={thread} />} + + {thread ? ( + <Post thread={thread} key={thread.post.uri} /> + ) : ( + <Container href="https://bsky.social/about"> + <Link + href="https://bsky.social/about" + className="transition-transform hover:scale-110 absolute top-4 right-4"> + <img src={logo as string} className="h-8" /> + </Link> + <div className="h-32" /> + </Container> + )} + </div> + </main> + ) +} + +function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { + const ref = useRef<HTMLInputElement>(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 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('/'), + ) + + const lang = record.langs ? record.langs[0] : '' + + // 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 INTERPOLATIOONS BELOW WITHOUT ESCAPING THEM! + // 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 `<blockquote class="bluesky-embed" data-bluesky-uri="${escapeHtml( + thread.post.uri, + )}" data-bluesky-cid="${escapeHtml(thread.post.cid)}"><p lang="${escapeHtml( + lang, + )}">${escapeHtml(record.text)}${ + record.embed + ? `<br><br><a href="${escapeHtml(href)}">[image or embed]</a>` + : '' + }</p>— ${escapeHtml( + thread.post.author.displayName || thread.post.author.handle, + )} (<a href="${escapeHtml(profileHref)}">@${escapeHtml( + thread.post.author.handle, + )}</a>) <a href="${escapeHtml(href)}">${escapeHtml( + niceDate(thread.post.indexedAt), + )}</a></blockquote><script async src="${EMBED_SCRIPT}" charset="utf-8"></script>` + }, [thread]) + + return ( + <div className="flex gap-2 w-full"> + <input + ref={ref} + type="text" + value={snippet} + className="border rounded-lg py-3 w-full px-4" + readOnly + autoFocus + /> + <button + className="rounded-lg bg-brand text-white color-white py-3 px-4 whitespace-nowrap min-w-28" + onClick={() => { + ref.current?.focus() + ref.current?.select() + void navigator.clipboard.writeText(snippet) + setCopied(true) + }}> + {copied ? 'Copied!' : 'Copy code'} + </button> + </div> + ) +} + +function toShareUrl(path: string) { + return `https://bsky.app${path}` +} + +/** + * 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 +} diff --git a/bskyembed/src/main.tsx b/bskyembed/src/screens/post.tsx index 895675434..76c921540 100644 --- a/bskyembed/src/main.tsx +++ b/bskyembed/src/screens/post.tsx @@ -1,27 +1,27 @@ -import './index.css' +import '../index.css' import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' import {h, render} from 'preact' -import logo from '../assets/logo.svg' -import {Container} from './container' -import {Link} from './link' -import {Post} from './post' -import {getRkey} from './utils' +import logo from '../../assets/logo.svg' +import {Container} from '../components/container' +import {Link} from '../components/link' +import {Post} from '../components/post' +import {getRkey} from '../utils' const root = document.getElementById('app') if (!root) throw new Error('No root element') -const searchParams = new URLSearchParams(window.location.search) - const agent = new BskyAgent({ service: 'https://public.api.bsky.app', }) -const uri = searchParams.get('uri') +const uri = `at://${window.location.pathname.slice('/embed/'.length)}` + +console.log(uri) if (!uri) { - throw new Error('No uri in query string') + throw new Error('No uri in path') } agent diff --git a/bskyembed/src/utils.ts b/bskyembed/src/utils.ts index 3408fcd97..1f6fd5061 100644 --- a/bskyembed/src/utils.ts +++ b/bskyembed/src/utils.ts @@ -1,3 +1,5 @@ +import {AtUri} from '@atproto/api' + export function niceDate(date: number | string | Date) { const d = new Date(date) return `${d.toLocaleDateString('en-us', { @@ -11,5 +13,6 @@ export function niceDate(date: number | string | Date) { } export function getRkey({uri}: {uri: string}): string { - return uri.split('/').pop() as string + const at = new AtUri(uri) + return at.rkey } diff --git a/bskyembed/tsconfig.snippet.json b/bskyembed/tsconfig.snippet.json new file mode 100644 index 000000000..a6b6071dd --- /dev/null +++ b/bskyembed/tsconfig.snippet.json @@ -0,0 +1,10 @@ + +{ + "compilerOptions": { + "target": "ES5", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "strict": true, + "outDir": "dist" + }, + "include": ["snippet"], +} diff --git a/bskyembed/vite.config.ts b/bskyembed/vite.config.ts index 8d0b92071..9acc9d5ee 100644 --- a/bskyembed/vite.config.ts +++ b/bskyembed/vite.config.ts @@ -1,3 +1,5 @@ +import {resolve} from 'node:path' + import preact from '@preact/preset-vite' import legacy from '@vitejs/plugin-legacy' import type {UserConfig} from 'vite' @@ -12,7 +14,13 @@ const config: UserConfig = { }), ], build: { - assetsDir: 'static/embed/assets', + assetsDir: 'static', + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + post: resolve(__dirname, 'post.html'), + }, + }, }, } |