Initial work for postgen
This commit is contained in:
@@ -4,13 +4,13 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --loader ts-node/esm ./src/bin.ts",
|
"start": " NODE_ENV=production node --loader ts-node/esm ./src/bin.ts",
|
||||||
"dev": "node --watch-path ./src --loader ts-node/esm ./src/bin.ts",
|
"dev": "LOG_ENABLED=true NODE_ENV=development node --watch-path ./src --loader ts-node/esm ./src/bin.ts | pino-pretty",
|
||||||
"build": "tsc && cp -r src/assets dist/",
|
"build": "tsc && cp -r src/assets dist/",
|
||||||
"install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts"
|
"install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.12.19-next.0",
|
"@atproto/api": "^0.13.4",
|
||||||
"@atproto/common": "^0.4.0",
|
"@atproto/common": "^0.4.0",
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"satori": "^0.10.13",
|
"satori": "^0.10.13",
|
||||||
"twemoji": "^14.0.2"
|
"twemoji": "^14.0.2",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.3",
|
"@types/node": "^20.14.3",
|
||||||
|
"pino-pretty": "^11.2.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {atoms as a, style as s} from '../theme/index.js'
|
||||||
|
|
||||||
|
export function Box({
|
||||||
|
children,
|
||||||
|
cx,
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
}) {
|
||||||
|
return <div style={s([a.flex, a.flex_col, ...(cx ?? [])])}>{children}</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {atoms as a} from '../theme/index.js'
|
||||||
|
import {Box} from './Box.js'
|
||||||
|
|
||||||
|
export function Row({
|
||||||
|
children,
|
||||||
|
gutter = 0,
|
||||||
|
cx,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
gutter?: number
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.flex_row,
|
||||||
|
{
|
||||||
|
marginLeft: -gutter,
|
||||||
|
marginRight: -gutter,
|
||||||
|
},
|
||||||
|
...(cx || []),
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Column({
|
||||||
|
children,
|
||||||
|
gutter = 0,
|
||||||
|
cx,
|
||||||
|
width = 1,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
gutter?: number
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
width?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.flex_col,
|
||||||
|
{
|
||||||
|
paddingLeft: gutter,
|
||||||
|
paddingRight: gutter,
|
||||||
|
width: `${width * 100}%`,
|
||||||
|
},
|
||||||
|
...(cx || []),
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {style as s} from '../theme/index.js'
|
||||||
|
import {Image as ImageSource} from '../util/resolvePostData.js'
|
||||||
|
|
||||||
|
export type ImageProps = Omit<
|
||||||
|
React.ImgHTMLAttributes<HTMLImageElement>,
|
||||||
|
'src' | 'style'
|
||||||
|
> & {
|
||||||
|
image: ImageSource
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Image({image, cx, ...rest}: ImageProps) {
|
||||||
|
const {image: src, mime = 'image/jpeg'} = image
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...rest}
|
||||||
|
src={`data:${mime};base64,${src.toString('base64')}`}
|
||||||
|
style={cx ? s(cx) : undefined}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* eslint-disable react-native-a11y/has-valid-accessibility-ignores-invert-colors */
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {atoms as a, theme as t} from '../theme/index.js'
|
||||||
|
import {Image as ImageSource} from '../util/resolvePostData.js'
|
||||||
|
import {toShortUrl} from '../util/toShortUrl.js'
|
||||||
|
import {Box} from './Box.js'
|
||||||
|
import {Image} from './Image.js'
|
||||||
|
import {Text} from './Text.js'
|
||||||
|
|
||||||
|
export function LinkCard({
|
||||||
|
image,
|
||||||
|
uri,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
image?: ImageSource
|
||||||
|
uri?: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.w_full,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
{image && (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.relative,
|
||||||
|
a.w_full,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{paddingTop: (630 / 1200) * 100 + '%'},
|
||||||
|
]}>
|
||||||
|
<Image
|
||||||
|
image={image}
|
||||||
|
cx={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
{
|
||||||
|
objectFit: 'cover',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box cx={[a.p_md, t.atoms.bg]}>
|
||||||
|
{uri && (
|
||||||
|
<Text cx={[a.text_xs, a.pb_sm, t.atoms.text_contrast_medium]}>
|
||||||
|
{toShortUrl(uri)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text cx={[a.text_md, a.font_bold, a.pb_xs]}>{title}</Text>
|
||||||
|
<Text cx={[a.text_sm, a.leading_snug]}>{description}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,484 @@
|
|||||||
|
/* eslint-disable bsky-internal/avoid-unwrapped-text, react-native-a11y/has-valid-accessibility-ignores-invert-colors */
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
AppBskyEmbedExternal,
|
||||||
|
AppBskyEmbedImages,
|
||||||
|
AppBskyEmbedRecord,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
AppBskyFeedDefs,
|
||||||
|
AppBskyFeedPost,
|
||||||
|
AppBskyGraphDefs,
|
||||||
|
AppBskyGraphStarterpack,
|
||||||
|
} from '@atproto/api'
|
||||||
|
|
||||||
|
import {atoms as a, gradient, theme as t} from '../theme/index.js'
|
||||||
|
import {formatCount} from '../util/formatCount.js'
|
||||||
|
import {formatDate} from '../util/formatDate.js'
|
||||||
|
import {getStarterPackImageUri} from '../util/getStarterPackImageUri.js'
|
||||||
|
import {Image as ImageSource, PostData} from '../util/resolvePostData.js'
|
||||||
|
import {Box} from './Box.js'
|
||||||
|
import * as Grid from './Grid.js'
|
||||||
|
import {CircleInfo} from './icons/CircleInfo.js'
|
||||||
|
import {Heart} from './icons/Heart.js'
|
||||||
|
import {Logomark} from './icons/Logomark.js'
|
||||||
|
import {Logotype} from './icons/Logotype.js'
|
||||||
|
import {Repost} from './icons/Repost.js'
|
||||||
|
import {Image} from './Image.js'
|
||||||
|
import {LinkCard} from './LinkCard.js'
|
||||||
|
import {RichText} from './RichText.js'
|
||||||
|
import {Text} from './Text.js'
|
||||||
|
|
||||||
|
export function Post({
|
||||||
|
post,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
post: AppBskyFeedDefs.PostView
|
||||||
|
data: PostData
|
||||||
|
}) {
|
||||||
|
if (AppBskyFeedPost.isRecord(post.record)) {
|
||||||
|
const avatar = data.images.get(post.author.avatar)
|
||||||
|
const text = post.record.text
|
||||||
|
const rt = data.texts.get(text)
|
||||||
|
const hasInteractions = post.likeCount > 0 || post.repostCount > 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.flex,
|
||||||
|
a.align_center,
|
||||||
|
a.w_full,
|
||||||
|
a.h_full,
|
||||||
|
a.p_3xl,
|
||||||
|
{
|
||||||
|
backgroundImage: `linear-gradient(to bottom, ${gradient('sky')})`,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Box
|
||||||
|
cx={[a.flex, a.flex_col, a.w_full, a.p_xl, a.rounded_md, t.atoms.bg]}>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_sm, a.pb_sm]}>
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.rounded_full,
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{
|
||||||
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{avatar && <Image height="100%" width="100%" image={avatar} />}
|
||||||
|
</Box>
|
||||||
|
<Box cx={[a.flex_col]}>
|
||||||
|
<Text cx={[a.text_md, a.font_bold, a.pb_2xs]}>
|
||||||
|
{post.author.displayName || post.author.handle}
|
||||||
|
</Text>
|
||||||
|
<Text cx={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||||
|
@{post.author.handle}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{rt && <RichText value={rt} />}
|
||||||
|
|
||||||
|
{post.embed && (
|
||||||
|
<Box cx={[a.pt_md]}>
|
||||||
|
<Embeds embed={post.embed} data={data} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.justify_between, a.pt_lg]}>
|
||||||
|
<Text cx={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||||
|
{formatDate(post.record.createdAt)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{!hasInteractions && <Logo />}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{hasInteractions && (
|
||||||
|
<Box cx={[a.pt_md]}>
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.w_full,
|
||||||
|
a.pb_md,
|
||||||
|
a.border_t,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.justify_between]}>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_2xl]}>
|
||||||
|
{post.likeCount > 0 && (
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
|
<Heart size={22} fill={t.palette.red_400} />
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.text_sm,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
{formatCount(post.likeCount)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{post.repostCount > 0 && (
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
|
<Repost size={22} fill={t.palette.green_600} />
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.text_sm,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
{formatCount(post.repostCount)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Logo />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Logo() {
|
||||||
|
return (
|
||||||
|
<Box cx={[a.flex_row, a.align_center]}>
|
||||||
|
<Logomark size={20} fill={t.palette.primary_500} />
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
{
|
||||||
|
paddingTop: '3px',
|
||||||
|
paddingLeft: '6px',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Logotype size={64} fill={t.palette.contrast_800} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Embeds({
|
||||||
|
embed,
|
||||||
|
data,
|
||||||
|
hideNestedEmbeds,
|
||||||
|
}: {
|
||||||
|
embed: AppBskyFeedDefs.PostView['embed']
|
||||||
|
data: PostData
|
||||||
|
hideNestedEmbeds?: boolean
|
||||||
|
}) {
|
||||||
|
if (AppBskyEmbedExternal.isView(embed)) {
|
||||||
|
const {title, description, uri, thumb} = embed.external
|
||||||
|
const image = data.images.get(thumb)
|
||||||
|
return (
|
||||||
|
<LinkCard
|
||||||
|
image={image}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
uri={uri}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedImages.isView(embed)) {
|
||||||
|
const {images} = embed
|
||||||
|
|
||||||
|
if (images.length > 0) {
|
||||||
|
const imgs = images.map(({fullsize}) => data.images.get(fullsize))
|
||||||
|
if (imgs.length === 1) {
|
||||||
|
return (
|
||||||
|
<Box cx={[a.rounded_sm, a.w_full, a.overflow_hidden]}>
|
||||||
|
<Image image={imgs[0]} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
} else if (imgs.length === 2) {
|
||||||
|
return (
|
||||||
|
<Grid.Row gutter={a.p_xs.padding}>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={1 / 2}>
|
||||||
|
<SquareImage image={imgs[0]} />
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={1 / 2}>
|
||||||
|
<SquareImage image={imgs[1]} />
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
)
|
||||||
|
} else if (imgs.length === 3) {
|
||||||
|
return (
|
||||||
|
<Grid.Row gutter={a.p_xs.padding}>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={2 / 3}>
|
||||||
|
<SquareImage image={imgs[0]} />
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={1 / 3} cx={[a.gap_sm]}>
|
||||||
|
<SquareImage image={imgs[1]} />
|
||||||
|
<SquareImage image={imgs[2]} />
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Grid.Row gutter={a.p_xs.padding}>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={1 / 2} cx={[a.gap_sm]}>
|
||||||
|
<SquareImage image={imgs[0]} />
|
||||||
|
<SquareImage image={imgs[1]} />
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column gutter={a.p_xs.padding} width={1 / 2} cx={[a.gap_sm]}>
|
||||||
|
<SquareImage image={imgs[2]} />
|
||||||
|
<SquareImage image={imgs[3]} />
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedRecord.isView(embed)) {
|
||||||
|
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
|
||||||
|
return <FeedCard embed={embed.record} data={data} />
|
||||||
|
}
|
||||||
|
if (AppBskyGraphDefs.isListView(embed.record)) {
|
||||||
|
return <ListCard embed={embed.record} data={data} />
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
AppBskyGraphDefs.isStarterPackViewBasic(embed.record) &&
|
||||||
|
AppBskyGraphStarterpack.isRecord(embed.record.record)
|
||||||
|
) {
|
||||||
|
const uri = getStarterPackImageUri(embed.record)
|
||||||
|
const {name, description} = embed.record.record
|
||||||
|
const image = data.images.get(uri)
|
||||||
|
return <LinkCard image={image} title={name} description={description} />
|
||||||
|
}
|
||||||
|
return <QuoteEmbed embed={embed} data={data} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
|
||||||
|
return (
|
||||||
|
<Box cx={[a.gap_md]}>
|
||||||
|
<Embeds embed={embed.media} data={data} />
|
||||||
|
{!hideNestedEmbeds && <QuoteEmbed embed={embed.record} data={data} />}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FeedCard({
|
||||||
|
embed,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
embed: AppBskyFeedDefs.GeneratorView
|
||||||
|
data: PostData
|
||||||
|
}) {
|
||||||
|
const {avatar, displayName, likeCount, creator} = embed
|
||||||
|
const image = data.images.get(avatar)
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.w_full,
|
||||||
|
a.gap_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.p_md,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
|
{image && (
|
||||||
|
<Image
|
||||||
|
image={image}
|
||||||
|
cx={[
|
||||||
|
a.rounded_sm,
|
||||||
|
{
|
||||||
|
width: '40px',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box cx={[a.pt_2xs]}>
|
||||||
|
<Text cx={[a.text_md, a.font_bold, a.pb_2xs]}>{displayName}</Text>
|
||||||
|
<Text cx={[a.text_sm, a.leading_snug]}>By @{creator.handle}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{likeCount > 1 && (
|
||||||
|
<Text cx={[a.text_sm, a.leading_snug]}>
|
||||||
|
Liked by {formatCount(likeCount)} users
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListCard({
|
||||||
|
embed,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
embed: AppBskyGraphDefs.ListView
|
||||||
|
data: PostData
|
||||||
|
}) {
|
||||||
|
const {avatar, name, creator} = embed
|
||||||
|
const image = data.images.get(avatar)
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.w_full,
|
||||||
|
a.gap_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.p_md,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
|
{image && (
|
||||||
|
<Image
|
||||||
|
image={image}
|
||||||
|
cx={[
|
||||||
|
a.rounded_sm,
|
||||||
|
{
|
||||||
|
width: '40px',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box cx={[a.pt_2xs]}>
|
||||||
|
<Text cx={[a.text_md, a.font_bold, a.pb_2xs]}>{name}</Text>
|
||||||
|
<Text cx={[a.text_sm, a.leading_snug]}>By @{creator.handle}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SquareImage({image}: {image: ImageSource}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.relative,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.w_full,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{paddingTop: '100%'},
|
||||||
|
]}>
|
||||||
|
<Image
|
||||||
|
image={image}
|
||||||
|
cx={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
{
|
||||||
|
objectFit: 'cover',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QuoteEmbed({
|
||||||
|
embed,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
embed: AppBskyEmbedRecord.View
|
||||||
|
data: PostData
|
||||||
|
}) {
|
||||||
|
if (
|
||||||
|
AppBskyEmbedRecord.isViewRecord(embed.record) &&
|
||||||
|
AppBskyFeedPost.isRecord(embed.record.value) &&
|
||||||
|
AppBskyFeedPost.validateRecord(embed.record.value).success
|
||||||
|
) {
|
||||||
|
const {author, value: post, embeds} = embed.record
|
||||||
|
const avatar = data.images.get(author.avatar)
|
||||||
|
const rt = data.texts.get(post.text)
|
||||||
|
const notPublic = author.labels.some(l => l.val === `!no-unauthenticated`)
|
||||||
|
|
||||||
|
if (notPublic) {
|
||||||
|
return (
|
||||||
|
<NotQuoteEmbed>
|
||||||
|
The author of the quoted post has requested their posts not be
|
||||||
|
displayed on external sites
|
||||||
|
</NotQuoteEmbed>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.w_full,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_xs, a.pb_sm]}>
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.rounded_full,
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{avatar && <Image height="100%" width="100%" image={avatar} />}
|
||||||
|
</Box>
|
||||||
|
<Box cx={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||||
|
<Text cx={[a.text_sm, a.font_bold]}>
|
||||||
|
{author.displayName || author.handle}
|
||||||
|
</Text>
|
||||||
|
<Text cx={[a.text_xs, t.atoms.text_contrast_medium]}>
|
||||||
|
@{author.handle}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{rt && (
|
||||||
|
<Box>
|
||||||
|
<RichText value={rt} cx={[a.text_sm]} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Boolean(embeds && embeds.length) && (
|
||||||
|
<Box cx={[a.pt_sm]}>
|
||||||
|
<Embeds embed={embeds[0]} data={data} hideNestedEmbeds />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
|
||||||
|
return <NotQuoteEmbed>Quoted post is blocked</NotQuoteEmbed>
|
||||||
|
} else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
|
||||||
|
return (
|
||||||
|
<NotQuoteEmbed>
|
||||||
|
Quoted post not found, it may have been deleted.
|
||||||
|
</NotQuoteEmbed>
|
||||||
|
)
|
||||||
|
} else if (AppBskyEmbedRecord.isViewDetached(embed.record)) {
|
||||||
|
return <NotQuoteEmbed>Quoted post detached by author</NotQuoteEmbed>
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotQuoteEmbed({children}: {children: React.ReactNode}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
cx={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.p_md,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<CircleInfo size={20} fill={t.atoms.text_contrast_low.color} />
|
||||||
|
<Text cx={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {AppBskyRichtextFacet, RichText as RichTextApi} from '@atproto/api'
|
||||||
|
|
||||||
|
import {atoms as a, style as s, theme as t} from '../theme/index.js'
|
||||||
|
import {toShortUrl} from '../util/toShortUrl.js'
|
||||||
|
import {Text} from './Text.js'
|
||||||
|
|
||||||
|
export function RichText({
|
||||||
|
value,
|
||||||
|
disableLinks,
|
||||||
|
cx,
|
||||||
|
}: {
|
||||||
|
value: RichTextApi
|
||||||
|
disableLinks?: boolean
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
}) {
|
||||||
|
const {facets} = value
|
||||||
|
const baseStyles = [
|
||||||
|
a.leading_snug,
|
||||||
|
{
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
},
|
||||||
|
cx ? s(cx) : {},
|
||||||
|
]
|
||||||
|
const linkStyles = [
|
||||||
|
{
|
||||||
|
color: t.palette.primary_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (!facets?.length) {
|
||||||
|
// if (isOnlyEmoji(text)) {
|
||||||
|
// const fontSize =
|
||||||
|
// (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier
|
||||||
|
// return (
|
||||||
|
// <Text
|
||||||
|
// selectable={selectable}
|
||||||
|
// testID={testID}
|
||||||
|
// style={[baseStyles, {fontSize}]}
|
||||||
|
// // @ts-ignore web only -prf
|
||||||
|
// dataSet={WORD_WRAP}>
|
||||||
|
// {text}
|
||||||
|
// </Text>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const els = []
|
||||||
|
let key = 0
|
||||||
|
// N.B. must access segments via `richText.segments`, not via destructuring
|
||||||
|
for (const segment of value.segments()) {
|
||||||
|
const {text, link, mention, tag} = segment
|
||||||
|
if (
|
||||||
|
mention &&
|
||||||
|
AppBskyRichtextFacet.validateMention(mention).success &&
|
||||||
|
!disableLinks
|
||||||
|
) {
|
||||||
|
els.push(
|
||||||
|
<span key={key} style={s(linkStyles)}>
|
||||||
|
{text}
|
||||||
|
</span>,
|
||||||
|
)
|
||||||
|
} else if (link && AppBskyRichtextFacet.validateLink(link).success) {
|
||||||
|
const url = toShortUrl(text)
|
||||||
|
if (disableLinks) {
|
||||||
|
els.push(url)
|
||||||
|
} else {
|
||||||
|
els.push(
|
||||||
|
<span key={key} style={s(linkStyles)}>
|
||||||
|
{url}
|
||||||
|
</span>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
!disableLinks &&
|
||||||
|
tag &&
|
||||||
|
AppBskyRichtextFacet.validateTag(tag).success
|
||||||
|
) {
|
||||||
|
els.push(
|
||||||
|
<span key={key} style={s(linkStyles)}>
|
||||||
|
{segment.text}
|
||||||
|
</span>,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
els.push(segment.text)
|
||||||
|
}
|
||||||
|
key++
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Text cx={baseStyles}>{els}</Text>
|
||||||
|
}
|
||||||
@@ -106,6 +106,7 @@ export function StarterPack(props: {
|
|||||||
color: 'white',
|
color: 'white',
|
||||||
padding: 60,
|
padding: 60,
|
||||||
fontSize: 40,
|
fontSize: 40,
|
||||||
|
fontWeight: '700',
|
||||||
}}>
|
}}>
|
||||||
JOIN THE CONVERSATION
|
JOIN THE CONVERSATION
|
||||||
</div>
|
</div>
|
||||||
@@ -134,6 +135,7 @@ export function StarterPack(props: {
|
|||||||
fontSize: isLongTitle ? 55 : 65,
|
fontSize: isLongTitle ? 55 : 65,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
fontWeight: '700',
|
||||||
}}>
|
}}>
|
||||||
{record?.name || 'Starter Pack'}
|
{record?.name || 'Starter Pack'}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {atoms as a, style as s, theme as t} from '../theme/index.js'
|
||||||
|
|
||||||
|
export function Text({
|
||||||
|
children,
|
||||||
|
cx,
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode
|
||||||
|
cx?: Record<string, any>[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={s([
|
||||||
|
a.flex,
|
||||||
|
a.flex_col,
|
||||||
|
a.font_normal,
|
||||||
|
a.leading_tight,
|
||||||
|
a.tracking_wide,
|
||||||
|
t.atoms.text,
|
||||||
|
...(cx ?? []),
|
||||||
|
])}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function CircleInfo({size, fill}: {size: number; fill?: string}) {
|
||||||
|
return (
|
||||||
|
<svg fill="none" viewBox="0 0 24 24" width={size} height={size}>
|
||||||
|
<path
|
||||||
|
fill={fill}
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8-1a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0v-4a1 1 0 0 1-1-1Zm1-3a1 1 0 1 0 2 0 1 1 0 0 0-2 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function Heart({size, fill}: {size: number; fill?: string}) {
|
||||||
|
return (
|
||||||
|
<svg fill="none" viewBox="0 0 24 24" width={size} height={size}>
|
||||||
|
<path
|
||||||
|
fill={fill}
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M12.489 21.372c8.528-4.78 10.626-10.47 9.022-14.47-.779-1.941-2.414-3.333-4.342-3.763-1.697-.378-3.552.003-5.169 1.287-1.617-1.284-3.472-1.665-5.17-1.287-1.927.43-3.562 1.822-4.34 3.764-1.605 4 .493 9.69 9.021 14.47a1 1 0 0 0 .978 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function Logomark({size, fill}: {size: number; fill?: string}) {
|
||||||
|
return (
|
||||||
|
<svg fill="none" viewBox="0 0 500 441" width={size}>
|
||||||
|
<path
|
||||||
|
fill={fill}
|
||||||
|
d="M108.382 29.634C165.705 72.668 227.36 159.93 250 206.754c22.64-46.824 84.295-134.084 141.618-177.12C432.98-1.42 500-25.447 500 51.008c0 15.269-8.755 128.269-13.889 146.615-17.848 63.779-82.883 80.047-140.735 70.2 101.122 17.211 126.846 74.218 71.291 131.225-105.511 108.268-151.649-27.165-163.471-61.867-2.167-6.361-3.181-9.338-3.196-6.807-.015-2.531-1.029.446-3.196 6.807-11.822 34.702-57.96 170.135-163.47 61.867-55.556-57.007-29.832-114.014 71.29-131.225-57.852 9.847-122.887-6.421-140.735-70.2C8.755 179.278 0 66.278 0 51.009 0-25.446 67.02-1.419 108.382 29.634Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function Logotype({size, fill}: {size: number; fill?: string}) {
|
||||||
|
return (
|
||||||
|
<svg fill="none" viewBox="0 0 398 108" width={size}>
|
||||||
|
<path
|
||||||
|
fill={fill}
|
||||||
|
d="M53.139 40.276c9.306 3.396 14.247 11.24 14.247 20.022 0 14.87-9.766 23.886-28.494 23.886H.632V.818h36.996c17.809 0 26.426 9.25 26.426 21.544 0 8.196-3.677 14.167-10.915 17.914Zm-16.66-26.462H16.143v21.779h20.336c7.928 0 12.179-4.215 12.179-11.24 0-6.44-4.366-10.539-12.179-10.539ZM16.143 71.07h21.945c8.732 0 13.442-4.098 13.442-11.474 0-7.728-4.48-11.592-13.442-11.592H16.143V71.07ZM88.892 84.184H74.415V.818h14.477v83.366ZM136.892 57.605V23.767h14.477v60.417h-14.017v-8.782c-4.481 6.791-10.686 10.187-18.613 10.187-12.524 0-20.681-7.728-20.681-21.778V23.767h14.476v37.585c0 7.61 3.677 11.474 11.145 11.474 7.009 0 13.213-5.268 13.213-15.22ZM217.264 55.03v3.512h-44.35c1.034 10.42 6.664 15.572 15.396 15.572 6.664 0 11.145-2.927 13.558-8.664h13.902c-3.102 12.294-13.443 20.139-27.575 20.139-8.847 0-15.97-2.927-21.37-8.665-5.4-5.737-8.158-13.347-8.158-22.949 0-9.484 2.643-17.094 8.043-22.949 5.4-5.737 12.409-8.664 21.256-8.664 8.961 0 16.085 3.044 21.37 9.016 5.285 5.971 7.928 13.933 7.928 23.651Zm-29.413-21.194c-7.928 0-13.443 4.684-14.822 14.402h29.758c-1.264-8.781-6.549-14.402-14.936-14.402ZM249.35 85.823c-17.234 0-26.311-6.908-27.115-20.841h14.132c.804 7.493 4.481 10.303 13.213 10.303 7.813 0 11.719-2.459 11.719-7.26 0-4.331-2.757-6.439-11.604-7.961l-6.779-1.17c-12.983-2.226-19.417-8.314-19.417-18.267 0-11.357 8.847-18.265 24.587-18.265 16.89 0 25.622 6.79 26.196 20.49H260.61c-.345-7.376-4.596-9.952-12.524-9.952-6.894 0-10.34 2.342-10.34 7.025 0 4.215 2.987 6.089 9.881 7.376l7.468 1.171c14.362 2.693 20.566 8.08 20.566 18.383 0 12.177-9.651 18.968-26.311 18.968ZM339.201 84.184h-16.545l-17.235-28.101-8.961 9.133v18.968h-14.247V.818h14.247v48.006l24.128-25.057h17.234l-22.405 22.832 23.784 37.585ZM378.008 38.988l4.826-15.221H398L375.136 89.1c-2.413 6.674-5.4 11.475-9.192 14.168-3.791 2.693-9.191 3.981-16.315 3.981-2.413 0-4.481-.117-6.319-.351V95.307h5.515c6.549 0 9.766-4.098 9.766-9.718 0-2.81-.919-6.908-2.758-12.177l-17.234-49.645h15.626l4.825 15.104c3.562 11.358 6.664 22.598 9.422 33.721 2.528-9.6 5.745-20.841 9.536-33.604Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function Repost({size, fill}: {size: number; fill?: string}) {
|
||||||
|
return (
|
||||||
|
<svg fill="none" viewBox="0 0 24 24" width={size} height={size}>
|
||||||
|
<path
|
||||||
|
fill={fill}
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -31,9 +31,12 @@ export class AppContext {
|
|||||||
const fontDirectory = path.join(__DIRNAME, 'assets', 'fonts')
|
const fontDirectory = path.join(__DIRNAME, 'assets', 'fonts')
|
||||||
const fontFiles = readdirSync(fontDirectory)
|
const fontFiles = readdirSync(fontDirectory)
|
||||||
const fonts = fontFiles.map(file => {
|
const fonts = fontFiles.map(file => {
|
||||||
|
const basename = path.basename(file, path.extname(file))
|
||||||
|
const [weight, name] = basename.split('-')
|
||||||
return {
|
return {
|
||||||
name: path.basename(file, path.extname(file)),
|
name,
|
||||||
data: readFileSync(path.join(fontDirectory, file)),
|
data: readFileSync(path.join(fontDirectory, file)),
|
||||||
|
weight: parseInt(weight),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return new AppContext({
|
return new AppContext({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Express} from 'express'
|
|||||||
|
|
||||||
import {AppContext} from '../context.js'
|
import {AppContext} from '../context.js'
|
||||||
import {default as health} from './health.js'
|
import {default as health} from './health.js'
|
||||||
|
import {default as post} from './post.js'
|
||||||
import {default as starterPack} from './starter-pack.js'
|
import {default as starterPack} from './starter-pack.js'
|
||||||
|
|
||||||
export * from './util.js'
|
export * from './util.js'
|
||||||
@@ -9,5 +10,6 @@ export * from './util.js'
|
|||||||
export default function (ctx: AppContext, app: Express) {
|
export default function (ctx: AppContext, app: Express) {
|
||||||
app = health(ctx, app) // GET /_health
|
app = health(ctx, app) // GET /_health
|
||||||
app = starterPack(ctx, app) // GET /start/:actor/:rkey
|
app = starterPack(ctx, app) // GET /start/:actor/:rkey
|
||||||
|
app = post(ctx, app) // POST /post
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api'
|
||||||
|
import resvg from '@resvg/resvg-js'
|
||||||
|
import {Express} from 'express'
|
||||||
|
import satori from 'satori'
|
||||||
|
|
||||||
|
import {Post} from '../components/Post.js'
|
||||||
|
import {AppContext} from '../context.js'
|
||||||
|
import {httpLogger} from '../logger.js'
|
||||||
|
import {loadEmojiAsSvg} from '../util.js'
|
||||||
|
import {resolvePostData} from '../util/resolvePostData.js'
|
||||||
|
import {handler, originVerifyMiddleware} from './util.js'
|
||||||
|
|
||||||
|
const WIDTH = 600
|
||||||
|
|
||||||
|
export default function (ctx: AppContext, app: Express) {
|
||||||
|
return app.get(
|
||||||
|
'/:actor/post/:rkey',
|
||||||
|
originVerifyMiddleware(ctx),
|
||||||
|
handler(async (req, res) => {
|
||||||
|
let {actor, rkey} = req.params
|
||||||
|
let uri = AtUri.make(actor, 'app.bsky.feed.post', rkey)
|
||||||
|
|
||||||
|
let post: AppBskyFeedDefs.PostView
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!actor.startsWith('did:')) {
|
||||||
|
const res = await ctx.appviewAgent.resolveHandle({
|
||||||
|
handle: actor,
|
||||||
|
})
|
||||||
|
actor = res.data.did
|
||||||
|
}
|
||||||
|
uri = AtUri.make(actor, 'app.bsky.feed.post', rkey)
|
||||||
|
const {data} = await ctx.appviewAgent.getPosts({
|
||||||
|
uris: [uri.toString()],
|
||||||
|
})
|
||||||
|
post = data.posts.at(0)
|
||||||
|
|
||||||
|
const notPublic = post.author.labels.some(
|
||||||
|
l => l.val === `!no-unauthenticated`,
|
||||||
|
)
|
||||||
|
if (notPublic) {
|
||||||
|
return res.status(404).end('not found')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
httpLogger.warn({err, uri: uri.toString()}, 'could not fetch post')
|
||||||
|
return res.status(404).end('not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AppBskyFeedPost.isRecord(post.record)) {
|
||||||
|
return res.status(404).end('not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await resolvePostData(post, ctx.appviewAgent)
|
||||||
|
const svg = await satori(<Post post={post} data={data} />, {
|
||||||
|
fonts: ctx.fonts,
|
||||||
|
width: WIDTH,
|
||||||
|
loadAdditionalAsset: async (code, text) => {
|
||||||
|
if (code === 'emoji') {
|
||||||
|
return await loadEmojiAsSvg(text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const output = await resvg.renderAsync(svg, {
|
||||||
|
fitTo: {
|
||||||
|
mode: 'width',
|
||||||
|
value: WIDTH * 2,
|
||||||
|
},
|
||||||
|
logLevel: 'trace',
|
||||||
|
})
|
||||||
|
res.statusCode = 200
|
||||||
|
res.setHeader('content-type', 'image/png')
|
||||||
|
res.setHeader('cdn-tag', [...data.images.keys()].join(','))
|
||||||
|
return res.end(output.asPng())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,566 @@
|
|||||||
|
import * as tokens from './tokens.js'
|
||||||
|
|
||||||
|
export const atoms = {
|
||||||
|
block: {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
inline_block: {
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
display: 'inline',
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Positioning
|
||||||
|
*/
|
||||||
|
fixed: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
absolute: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
relative: {
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
inset_0: {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
overflow_hidden: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Width
|
||||||
|
*/
|
||||||
|
w_full: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
h_full: {
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
h_full_vh: {
|
||||||
|
height: '100vh',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Theme-independent bg colors
|
||||||
|
*/
|
||||||
|
bg_transparent: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Border radius
|
||||||
|
*/
|
||||||
|
rounded_2xs: {
|
||||||
|
borderRadius: tokens.borderRadius._2xs,
|
||||||
|
},
|
||||||
|
rounded_xs: {
|
||||||
|
borderRadius: tokens.borderRadius.xs,
|
||||||
|
},
|
||||||
|
rounded_sm: {
|
||||||
|
borderRadius: tokens.borderRadius.sm,
|
||||||
|
},
|
||||||
|
rounded_md: {
|
||||||
|
borderRadius: tokens.borderRadius.md,
|
||||||
|
},
|
||||||
|
rounded_full: {
|
||||||
|
borderRadius: tokens.borderRadius.full,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flex
|
||||||
|
*/
|
||||||
|
gap_2xs: {
|
||||||
|
gap: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
gap_xs: {
|
||||||
|
gap: tokens.space.xs,
|
||||||
|
},
|
||||||
|
gap_sm: {
|
||||||
|
gap: tokens.space.sm,
|
||||||
|
},
|
||||||
|
gap_md: {
|
||||||
|
gap: tokens.space.md,
|
||||||
|
},
|
||||||
|
gap_lg: {
|
||||||
|
gap: tokens.space.lg,
|
||||||
|
},
|
||||||
|
gap_xl: {
|
||||||
|
gap: tokens.space.xl,
|
||||||
|
},
|
||||||
|
gap_2xl: {
|
||||||
|
gap: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
gap_3xl: {
|
||||||
|
gap: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
gap_4xl: {
|
||||||
|
gap: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
gap_5xl: {
|
||||||
|
gap: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
flex: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
flex_col: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
flex_row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
flex_col_reverse: {
|
||||||
|
flexDirection: 'column-reverse',
|
||||||
|
},
|
||||||
|
flex_row_reverse: {
|
||||||
|
flexDirection: 'row-reverse',
|
||||||
|
},
|
||||||
|
flex_wrap: {
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
flex_0: {
|
||||||
|
flex: '0 0 auto',
|
||||||
|
},
|
||||||
|
flex_1: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
flex_grow: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
flex_shrink: {
|
||||||
|
flexShrink: 1,
|
||||||
|
},
|
||||||
|
flex_shrink_0: {
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
justify_start: {
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
},
|
||||||
|
justify_center: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
justify_between: {
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
justify_end: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
align_center: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
align_start: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
align_end: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
align_baseline: {
|
||||||
|
alignItems: 'baseline',
|
||||||
|
},
|
||||||
|
align_stretch: {
|
||||||
|
alignItems: 'stretch',
|
||||||
|
},
|
||||||
|
self_auto: {
|
||||||
|
alignSelf: 'auto',
|
||||||
|
},
|
||||||
|
self_start: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
self_end: {
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
},
|
||||||
|
self_center: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
self_stretch: {
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
},
|
||||||
|
self_baseline: {
|
||||||
|
alignSelf: 'baseline',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Text
|
||||||
|
*/
|
||||||
|
text_left: {
|
||||||
|
textAlign: 'left',
|
||||||
|
},
|
||||||
|
text_center: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
text_right: {
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
text_2xs: {
|
||||||
|
fontSize: tokens.fontSize._2xs,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_xs: {
|
||||||
|
fontSize: tokens.fontSize.xs,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_sm: {
|
||||||
|
fontSize: tokens.fontSize.sm,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_md: {
|
||||||
|
fontSize: tokens.fontSize.md,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_lg: {
|
||||||
|
fontSize: tokens.fontSize.lg,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_xl: {
|
||||||
|
fontSize: tokens.fontSize.xl,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_2xl: {
|
||||||
|
fontSize: tokens.fontSize._2xl,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_3xl: {
|
||||||
|
fontSize: tokens.fontSize._3xl,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_4xl: {
|
||||||
|
fontSize: tokens.fontSize._4xl,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
text_5xl: {
|
||||||
|
fontSize: tokens.fontSize._5xl,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
leading_tight: {
|
||||||
|
lineHeight: '1.15',
|
||||||
|
},
|
||||||
|
leading_snug: {
|
||||||
|
lineHeight: '1.3',
|
||||||
|
},
|
||||||
|
leading_normal: {
|
||||||
|
lineHeight: '1.5',
|
||||||
|
},
|
||||||
|
tracking_normal: {
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
tracking_wide: {
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
},
|
||||||
|
font_normal: {
|
||||||
|
fontWeight: tokens.fontWeight.normal,
|
||||||
|
},
|
||||||
|
font_semibold: {
|
||||||
|
fontWeight: tokens.fontWeight.semibold,
|
||||||
|
},
|
||||||
|
font_bold: {
|
||||||
|
fontWeight: tokens.fontWeight.bold,
|
||||||
|
},
|
||||||
|
font_heavy: {
|
||||||
|
fontWeight: tokens.fontWeight.heavy,
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
mono: {
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Border
|
||||||
|
*/
|
||||||
|
border_0: {
|
||||||
|
borderWidth: '0px',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
},
|
||||||
|
border_t: {
|
||||||
|
borderTopWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
},
|
||||||
|
border_b: {
|
||||||
|
borderBottomWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
},
|
||||||
|
border_l: {
|
||||||
|
borderLeftWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
},
|
||||||
|
border_r: {
|
||||||
|
borderRightWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Padding
|
||||||
|
*/
|
||||||
|
p_0: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
p_2xs: {
|
||||||
|
padding: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
p_xs: {
|
||||||
|
padding: tokens.space.xs,
|
||||||
|
},
|
||||||
|
p_sm: {
|
||||||
|
padding: tokens.space.sm,
|
||||||
|
},
|
||||||
|
p_md: {
|
||||||
|
padding: tokens.space.md,
|
||||||
|
},
|
||||||
|
p_lg: {
|
||||||
|
padding: tokens.space.lg,
|
||||||
|
},
|
||||||
|
p_xl: {
|
||||||
|
padding: tokens.space.xl,
|
||||||
|
},
|
||||||
|
p_2xl: {
|
||||||
|
padding: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
p_3xl: {
|
||||||
|
padding: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
p_4xl: {
|
||||||
|
padding: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
p_5xl: {
|
||||||
|
padding: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
px_0: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 0,
|
||||||
|
},
|
||||||
|
px_2xs: {
|
||||||
|
paddingLeft: tokens.space._2xs,
|
||||||
|
paddingRight: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
px_xs: {
|
||||||
|
paddingLeft: tokens.space.xs,
|
||||||
|
paddingRight: tokens.space.xs,
|
||||||
|
},
|
||||||
|
px_sm: {
|
||||||
|
paddingLeft: tokens.space.sm,
|
||||||
|
paddingRight: tokens.space.sm,
|
||||||
|
},
|
||||||
|
px_md: {
|
||||||
|
paddingLeft: tokens.space.md,
|
||||||
|
paddingRight: tokens.space.md,
|
||||||
|
},
|
||||||
|
px_lg: {
|
||||||
|
paddingLeft: tokens.space.lg,
|
||||||
|
paddingRight: tokens.space.lg,
|
||||||
|
},
|
||||||
|
px_xl: {
|
||||||
|
paddingLeft: tokens.space.xl,
|
||||||
|
paddingRight: tokens.space.xl,
|
||||||
|
},
|
||||||
|
px_2xl: {
|
||||||
|
paddingLeft: tokens.space._2xl,
|
||||||
|
paddingRight: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
px_3xl: {
|
||||||
|
paddingLeft: tokens.space._3xl,
|
||||||
|
paddingRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
px_4xl: {
|
||||||
|
paddingLeft: tokens.space._4xl,
|
||||||
|
paddingRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
px_5xl: {
|
||||||
|
paddingLeft: tokens.space._5xl,
|
||||||
|
paddingRight: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
py_0: {
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
},
|
||||||
|
py_2xs: {
|
||||||
|
paddingTop: tokens.space._2xs,
|
||||||
|
paddingBottom: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
py_xs: {
|
||||||
|
paddingTop: tokens.space.xs,
|
||||||
|
paddingBottom: tokens.space.xs,
|
||||||
|
},
|
||||||
|
py_sm: {
|
||||||
|
paddingTop: tokens.space.sm,
|
||||||
|
paddingBottom: tokens.space.sm,
|
||||||
|
},
|
||||||
|
py_md: {
|
||||||
|
paddingTop: tokens.space.md,
|
||||||
|
paddingBottom: tokens.space.md,
|
||||||
|
},
|
||||||
|
py_lg: {
|
||||||
|
paddingTop: tokens.space.lg,
|
||||||
|
paddingBottom: tokens.space.lg,
|
||||||
|
},
|
||||||
|
py_xl: {
|
||||||
|
paddingTop: tokens.space.xl,
|
||||||
|
paddingBottom: tokens.space.xl,
|
||||||
|
},
|
||||||
|
py_2xl: {
|
||||||
|
paddingTop: tokens.space._2xl,
|
||||||
|
paddingBottom: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
py_3xl: {
|
||||||
|
paddingTop: tokens.space._3xl,
|
||||||
|
paddingBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
py_4xl: {
|
||||||
|
paddingTop: tokens.space._4xl,
|
||||||
|
paddingBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
py_5xl: {
|
||||||
|
paddingTop: tokens.space._5xl,
|
||||||
|
paddingBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pt_0: {
|
||||||
|
paddingTop: 0,
|
||||||
|
},
|
||||||
|
pt_2xs: {
|
||||||
|
paddingTop: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
pt_xs: {
|
||||||
|
paddingTop: tokens.space.xs,
|
||||||
|
},
|
||||||
|
pt_sm: {
|
||||||
|
paddingTop: tokens.space.sm,
|
||||||
|
},
|
||||||
|
pt_md: {
|
||||||
|
paddingTop: tokens.space.md,
|
||||||
|
},
|
||||||
|
pt_lg: {
|
||||||
|
paddingTop: tokens.space.lg,
|
||||||
|
},
|
||||||
|
pt_xl: {
|
||||||
|
paddingTop: tokens.space.xl,
|
||||||
|
},
|
||||||
|
pt_2xl: {
|
||||||
|
paddingTop: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
pt_3xl: {
|
||||||
|
paddingTop: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pt_4xl: {
|
||||||
|
paddingTop: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pt_5xl: {
|
||||||
|
paddingTop: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pb_0: {
|
||||||
|
paddingBottom: 0,
|
||||||
|
},
|
||||||
|
pb_2xs: {
|
||||||
|
paddingBottom: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
pb_xs: {
|
||||||
|
paddingBottom: tokens.space.xs,
|
||||||
|
},
|
||||||
|
pb_sm: {
|
||||||
|
paddingBottom: tokens.space.sm,
|
||||||
|
},
|
||||||
|
pb_md: {
|
||||||
|
paddingBottom: tokens.space.md,
|
||||||
|
},
|
||||||
|
pb_lg: {
|
||||||
|
paddingBottom: tokens.space.lg,
|
||||||
|
},
|
||||||
|
pb_xl: {
|
||||||
|
paddingBottom: tokens.space.xl,
|
||||||
|
},
|
||||||
|
pb_2xl: {
|
||||||
|
paddingBottom: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
pb_3xl: {
|
||||||
|
paddingBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pb_4xl: {
|
||||||
|
paddingBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pb_5xl: {
|
||||||
|
paddingBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pl_0: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
},
|
||||||
|
pl_2xs: {
|
||||||
|
paddingLeft: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
pl_xs: {
|
||||||
|
paddingLeft: tokens.space.xs,
|
||||||
|
},
|
||||||
|
pl_sm: {
|
||||||
|
paddingLeft: tokens.space.sm,
|
||||||
|
},
|
||||||
|
pl_md: {
|
||||||
|
paddingLeft: tokens.space.md,
|
||||||
|
},
|
||||||
|
pl_lg: {
|
||||||
|
paddingLeft: tokens.space.lg,
|
||||||
|
},
|
||||||
|
pl_xl: {
|
||||||
|
paddingLeft: tokens.space.xl,
|
||||||
|
},
|
||||||
|
pl_2xl: {
|
||||||
|
paddingLeft: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
pl_3xl: {
|
||||||
|
paddingLeft: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pl_4xl: {
|
||||||
|
paddingLeft: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pl_5xl: {
|
||||||
|
paddingLeft: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pr_0: {
|
||||||
|
paddingRight: 0,
|
||||||
|
},
|
||||||
|
pr_2xs: {
|
||||||
|
paddingRight: tokens.space._2xs,
|
||||||
|
},
|
||||||
|
pr_xs: {
|
||||||
|
paddingRight: tokens.space.xs,
|
||||||
|
},
|
||||||
|
pr_sm: {
|
||||||
|
paddingRight: tokens.space.sm,
|
||||||
|
},
|
||||||
|
pr_md: {
|
||||||
|
paddingRight: tokens.space.md,
|
||||||
|
},
|
||||||
|
pr_lg: {
|
||||||
|
paddingRight: tokens.space.lg,
|
||||||
|
},
|
||||||
|
pr_xl: {
|
||||||
|
paddingRight: tokens.space.xl,
|
||||||
|
},
|
||||||
|
pr_2xl: {
|
||||||
|
paddingRight: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
pr_3xl: {
|
||||||
|
paddingRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pr_4xl: {
|
||||||
|
paddingRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pr_5xl: {
|
||||||
|
paddingRight: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Text decoration
|
||||||
|
*/
|
||||||
|
underline: {
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
},
|
||||||
|
strike_through: {
|
||||||
|
textDecorationLine: 'line-through',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
export const BLUE_HUE = 211
|
||||||
|
export const RED_HUE = 346
|
||||||
|
export const GREEN_HUE = 152
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smooth progression of lightness "stops" for generating HSL colors.
|
||||||
|
*/
|
||||||
|
export const COLOR_STOPS = [
|
||||||
|
0, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 1,
|
||||||
|
]
|
||||||
|
|
||||||
|
export function generateScale(start: number, end: number) {
|
||||||
|
const range = end - start
|
||||||
|
return COLOR_STOPS.map(stop => {
|
||||||
|
return start + range * stop
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultScale = generateScale(6, 100)
|
||||||
|
// dim shifted 6% lighter
|
||||||
|
export const dimScale = generateScale(12, 100)
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
white: '#FFFFFF',
|
||||||
|
black: '#000000',
|
||||||
|
|
||||||
|
contrast_0: `hsl(${BLUE_HUE}, 20%, ${defaultScale[14]}%)`,
|
||||||
|
contrast_25: `hsl(${BLUE_HUE}, 20%, ${defaultScale[13]}%)`,
|
||||||
|
contrast_50: `hsl(${BLUE_HUE}, 20%, ${defaultScale[12]}%)`,
|
||||||
|
contrast_100: `hsl(${BLUE_HUE}, 20%, ${defaultScale[11]}%)`,
|
||||||
|
contrast_200: `hsl(${BLUE_HUE}, 20%, ${defaultScale[10]}%)`,
|
||||||
|
contrast_300: `hsl(${BLUE_HUE}, 20%, ${defaultScale[9]}%)`,
|
||||||
|
contrast_400: `hsl(${BLUE_HUE}, 20%, ${defaultScale[8]}%)`,
|
||||||
|
contrast_500: `hsl(${BLUE_HUE}, 20%, ${defaultScale[7]}%)`,
|
||||||
|
contrast_600: `hsl(${BLUE_HUE}, 24%, ${defaultScale[6]}%)`,
|
||||||
|
contrast_700: `hsl(${BLUE_HUE}, 24%, ${defaultScale[5]}%)`,
|
||||||
|
contrast_800: `hsl(${BLUE_HUE}, 28%, ${defaultScale[4]}%)`,
|
||||||
|
contrast_900: `hsl(${BLUE_HUE}, 28%, ${defaultScale[3]}%)`,
|
||||||
|
contrast_950: `hsl(${BLUE_HUE}, 28%, ${defaultScale[2]}%)`,
|
||||||
|
contrast_975: `hsl(${BLUE_HUE}, 28%, ${defaultScale[1]}%)`,
|
||||||
|
contrast_1000: `hsl(${BLUE_HUE}, 28%, ${defaultScale[0]}%)`,
|
||||||
|
|
||||||
|
primary_25: `hsl(${BLUE_HUE}, 99%, 97%)`,
|
||||||
|
primary_50: `hsl(${BLUE_HUE}, 99%, 95%)`,
|
||||||
|
primary_100: `hsl(${BLUE_HUE}, 99%, 90%)`,
|
||||||
|
primary_200: `hsl(${BLUE_HUE}, 99%, 80%)`,
|
||||||
|
primary_300: `hsl(${BLUE_HUE}, 99%, 70%)`,
|
||||||
|
primary_400: `hsl(${BLUE_HUE}, 99%, 60%)`,
|
||||||
|
primary_500: `hsl(${BLUE_HUE}, 99%, 53%)`,
|
||||||
|
primary_600: `hsl(${BLUE_HUE}, 99%, 42%)`,
|
||||||
|
primary_700: `hsl(${BLUE_HUE}, 99%, 34%)`,
|
||||||
|
primary_800: `hsl(${BLUE_HUE}, 99%, 26%)`,
|
||||||
|
primary_900: `hsl(${BLUE_HUE}, 99%, 18%)`,
|
||||||
|
primary_950: `hsl(${BLUE_HUE}, 99%, 10%)`,
|
||||||
|
primary_975: `hsl(${BLUE_HUE}, 99%, 7%)`,
|
||||||
|
|
||||||
|
green_25: `hsl(${GREEN_HUE}, 82%, 97%)`,
|
||||||
|
green_50: `hsl(${GREEN_HUE}, 82%, 95%)`,
|
||||||
|
green_100: `hsl(${GREEN_HUE}, 82%, 90%)`,
|
||||||
|
green_200: `hsl(${GREEN_HUE}, 82%, 80%)`,
|
||||||
|
green_300: `hsl(${GREEN_HUE}, 82%, 70%)`,
|
||||||
|
green_400: `hsl(${GREEN_HUE}, 82%, 60%)`,
|
||||||
|
green_500: `hsl(${GREEN_HUE}, 82%, 50%)`,
|
||||||
|
green_600: `hsl(${GREEN_HUE}, 82%, 42%)`,
|
||||||
|
green_700: `hsl(${GREEN_HUE}, 82%, 34%)`,
|
||||||
|
green_800: `hsl(${GREEN_HUE}, 82%, 26%)`,
|
||||||
|
green_900: `hsl(${GREEN_HUE}, 82%, 18%)`,
|
||||||
|
green_950: `hsl(${GREEN_HUE}, 82%, 10%)`,
|
||||||
|
green_975: `hsl(${GREEN_HUE}, 82%, 7%)`,
|
||||||
|
|
||||||
|
red_25: `hsl(${RED_HUE}, 91%, 97%)`,
|
||||||
|
red_50: `hsl(${RED_HUE}, 91%, 95%)`,
|
||||||
|
red_100: `hsl(${RED_HUE}, 91%, 90%)`,
|
||||||
|
red_200: `hsl(${RED_HUE}, 91%, 80%)`,
|
||||||
|
red_300: `hsl(${RED_HUE}, 91%, 70%)`,
|
||||||
|
red_400: `hsl(${RED_HUE}, 91%, 60%)`,
|
||||||
|
red_500: `hsl(${RED_HUE}, 91%, 50%)`,
|
||||||
|
red_600: `hsl(${RED_HUE}, 91%, 42%)`,
|
||||||
|
red_700: `hsl(${RED_HUE}, 91%, 34%)`,
|
||||||
|
red_800: `hsl(${RED_HUE}, 91%, 26%)`,
|
||||||
|
red_900: `hsl(${RED_HUE}, 91%, 18%)`,
|
||||||
|
red_950: `hsl(${RED_HUE}, 91%, 10%)`,
|
||||||
|
red_975: `hsl(${RED_HUE}, 91%, 7%)`,
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import {colors} from './colors.js'
|
||||||
|
import * as tokens from './tokens.js'
|
||||||
|
|
||||||
|
export {atoms} from './atoms.js'
|
||||||
|
export * as tokens from './tokens.js'
|
||||||
|
|
||||||
|
const fontFamily = 'Inter'
|
||||||
|
|
||||||
|
export const theme = {
|
||||||
|
palette: colors,
|
||||||
|
atoms: {
|
||||||
|
text: {
|
||||||
|
color: colors.black,
|
||||||
|
fontFamily,
|
||||||
|
},
|
||||||
|
text_contrast_low: {
|
||||||
|
color: colors.contrast_500,
|
||||||
|
fontFamily,
|
||||||
|
},
|
||||||
|
text_contrast_medium: {
|
||||||
|
color: colors.contrast_700,
|
||||||
|
fontFamily,
|
||||||
|
},
|
||||||
|
text_contrast_high: {
|
||||||
|
color: colors.contrast_900,
|
||||||
|
fontFamily,
|
||||||
|
},
|
||||||
|
text_inverted: {
|
||||||
|
color: colors.white,
|
||||||
|
fontFamily,
|
||||||
|
},
|
||||||
|
bg: {
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
},
|
||||||
|
bg_contrast_25: {
|
||||||
|
backgroundColor: colors.contrast_25,
|
||||||
|
},
|
||||||
|
bg_contrast_50: {
|
||||||
|
backgroundColor: colors.contrast_50,
|
||||||
|
},
|
||||||
|
bg_contrast_100: {
|
||||||
|
backgroundColor: colors.contrast_100,
|
||||||
|
},
|
||||||
|
bg_contrast_200: {
|
||||||
|
backgroundColor: colors.contrast_200,
|
||||||
|
},
|
||||||
|
bg_contrast_300: {
|
||||||
|
backgroundColor: colors.contrast_300,
|
||||||
|
},
|
||||||
|
bg_contrast_400: {
|
||||||
|
backgroundColor: colors.contrast_400,
|
||||||
|
},
|
||||||
|
bg_contrast_500: {
|
||||||
|
backgroundColor: colors.contrast_500,
|
||||||
|
},
|
||||||
|
bg_contrast_600: {
|
||||||
|
backgroundColor: colors.contrast_600,
|
||||||
|
},
|
||||||
|
bg_contrast_700: {
|
||||||
|
backgroundColor: colors.contrast_700,
|
||||||
|
},
|
||||||
|
bg_contrast_800: {
|
||||||
|
backgroundColor: colors.contrast_800,
|
||||||
|
},
|
||||||
|
bg_contrast_900: {
|
||||||
|
backgroundColor: colors.contrast_900,
|
||||||
|
},
|
||||||
|
bg_contrast_950: {
|
||||||
|
backgroundColor: colors.contrast_950,
|
||||||
|
},
|
||||||
|
bg_contrast_975: {
|
||||||
|
backgroundColor: colors.contrast_975,
|
||||||
|
},
|
||||||
|
bg_primary_500: {
|
||||||
|
backgroundColor: colors.primary_500,
|
||||||
|
},
|
||||||
|
border_contrast_low: {
|
||||||
|
borderColor: colors.contrast_100,
|
||||||
|
},
|
||||||
|
border_contrast_medium: {
|
||||||
|
borderColor: colors.contrast_200,
|
||||||
|
},
|
||||||
|
border_contrast_high: {
|
||||||
|
borderColor: colors.contrast_300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function style(styleObjects: Record<string, any>[]) {
|
||||||
|
return Object.assign({}, ...styleObjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gradient(color: keyof typeof tokens.gradients) {
|
||||||
|
const {values} = tokens.gradients[color]
|
||||||
|
return values
|
||||||
|
.map(([pos, color]) => {
|
||||||
|
return `${color} ${pos * 100}%`
|
||||||
|
})
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
export const space = {
|
||||||
|
_2xs: 2,
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
lg: 16,
|
||||||
|
xl: 20,
|
||||||
|
_2xl: 24,
|
||||||
|
_3xl: 28,
|
||||||
|
_4xl: 32,
|
||||||
|
_5xl: 40,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const fontSize = {
|
||||||
|
_2xs: 10,
|
||||||
|
xs: 12,
|
||||||
|
sm: 14,
|
||||||
|
md: 16,
|
||||||
|
lg: 18,
|
||||||
|
xl: 20,
|
||||||
|
_2xl: 22,
|
||||||
|
_3xl: 26,
|
||||||
|
_4xl: 32,
|
||||||
|
_5xl: 40,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const lineHeight = {
|
||||||
|
none: 1,
|
||||||
|
normal: 1.5,
|
||||||
|
relaxed: 1.625,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const borderRadius = {
|
||||||
|
_2xs: 2,
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
full: 999,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const fontWeight = {
|
||||||
|
normal: '400',
|
||||||
|
semibold: '500',
|
||||||
|
bold: '600',
|
||||||
|
heavy: '700',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const gradients = {
|
||||||
|
sky: {
|
||||||
|
values: [
|
||||||
|
[0, '#0A7AFF'],
|
||||||
|
[1, '#59B9FF'],
|
||||||
|
],
|
||||||
|
hover_value: '#0A7AFF',
|
||||||
|
},
|
||||||
|
midnight: {
|
||||||
|
values: [
|
||||||
|
[0, '#022C5E'],
|
||||||
|
[1, '#4079BC'],
|
||||||
|
],
|
||||||
|
hover_value: '#022C5E',
|
||||||
|
},
|
||||||
|
sunrise: {
|
||||||
|
values: [
|
||||||
|
[0, '#4E90AE'],
|
||||||
|
[0.4, '#AEA3AB'],
|
||||||
|
[0.8, '#E6A98F'],
|
||||||
|
[1, '#F3A84C'],
|
||||||
|
],
|
||||||
|
hover_value: '#AEA3AB',
|
||||||
|
},
|
||||||
|
sunset: {
|
||||||
|
values: [
|
||||||
|
[0, '#6772AF'],
|
||||||
|
[0.6, '#B88BB6'],
|
||||||
|
[1, '#FFA6AC'],
|
||||||
|
],
|
||||||
|
hover_value: '#B88BB6',
|
||||||
|
},
|
||||||
|
summer: {
|
||||||
|
values: [
|
||||||
|
[0, '#FF6A56'],
|
||||||
|
[0.3, '#FF9156'],
|
||||||
|
[1, '#FFDD87'],
|
||||||
|
],
|
||||||
|
hover_value: '#FF9156',
|
||||||
|
},
|
||||||
|
nordic: {
|
||||||
|
values: [
|
||||||
|
[0, '#083367'],
|
||||||
|
[1, '#9EE8C1'],
|
||||||
|
],
|
||||||
|
hover_value: '#3A7085',
|
||||||
|
},
|
||||||
|
bonfire: {
|
||||||
|
values: [
|
||||||
|
[0, '#203E4E'],
|
||||||
|
[0.4, '#755B62'],
|
||||||
|
[0.8, '#CD7765'],
|
||||||
|
[1, '#EF956E'],
|
||||||
|
],
|
||||||
|
hover_value: '#755B62',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export function formatCount(count: number) {
|
||||||
|
return Intl.NumberFormat('en-US', {
|
||||||
|
notation: 'compact',
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
// `1,953` shouldn't be rounded up to 2k, it should be truncated.
|
||||||
|
// @ts-ignore
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode
|
||||||
|
roundingMode: 'trunc',
|
||||||
|
}).format(count)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export function formatDate(date: number | string | Date) {
|
||||||
|
const d = new Date(date)
|
||||||
|
return `${d.toLocaleDateString('en-us', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
})} at ${d.toLocaleTimeString(undefined, {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import MIMEType from 'whatwg-mimetype'
|
||||||
|
|
||||||
|
export async function getImage(url: string) {
|
||||||
|
const response = await fetch(url)
|
||||||
|
const mimeType = new MIMEType(response.headers.get('content-type') ?? '')
|
||||||
|
const arrayBuf = await response.arrayBuffer() // must drain body even if it will be discarded
|
||||||
|
if (response.status !== 200) return null
|
||||||
|
return {
|
||||||
|
mime: mimeType.essence as string | undefined,
|
||||||
|
image: Buffer.from(arrayBuf),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import {AppBskyGraphDefs, AtUri} from '@atproto/api'
|
||||||
|
|
||||||
|
const DEV = process.env.NODE_ENV === 'development'
|
||||||
|
const HOST = DEV
|
||||||
|
? `http://localhost:3000/start`
|
||||||
|
: `https://ogcard.cdn.bsky.app/start`
|
||||||
|
|
||||||
|
export function getStarterPackImageUri(
|
||||||
|
record: AppBskyGraphDefs.StarterPackViewBasic,
|
||||||
|
) {
|
||||||
|
const urip = new AtUri(record.uri)
|
||||||
|
return `${HOST}/${record.creator.did}/${urip.rkey}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
import {
|
||||||
|
AppBskyEmbedExternal,
|
||||||
|
AppBskyEmbedImages,
|
||||||
|
AppBskyEmbedRecord,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
AppBskyFeedDefs,
|
||||||
|
AppBskyFeedPost,
|
||||||
|
AppBskyGraphDefs,
|
||||||
|
AtpAgent,
|
||||||
|
RichText,
|
||||||
|
} from '@atproto/api'
|
||||||
|
|
||||||
|
import {httpLogger} from '../logger.js'
|
||||||
|
import {getImage} from './getImage.js'
|
||||||
|
import {getStarterPackImageUri} from './getStarterPackImageUri.js'
|
||||||
|
|
||||||
|
export type Metadata = {
|
||||||
|
aspectRatio: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Image = Metadata & {
|
||||||
|
mime: 'image/jpeg' | 'image/png' | string | undefined
|
||||||
|
image: Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostData = {
|
||||||
|
images: Map<string, Image>
|
||||||
|
texts: Map<string, RichText>
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAspectRatio(aspectRatio?: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}): Metadata['aspectRatio'] {
|
||||||
|
if (!aspectRatio)
|
||||||
|
return {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
}
|
||||||
|
return aspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolvePostData(
|
||||||
|
post: AppBskyFeedDefs.PostView,
|
||||||
|
agent: AtpAgent,
|
||||||
|
): Promise<PostData> {
|
||||||
|
const images: Map<string, Metadata> = new Map()
|
||||||
|
const texts: Map<string, RichText> = new Map()
|
||||||
|
|
||||||
|
// console.log(JSON.stringify(post, null, 2))
|
||||||
|
|
||||||
|
if (post.author.avatar) {
|
||||||
|
images.set(post.author.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyFeedPost.isRecord(post.record) && post.record.text) {
|
||||||
|
texts.set(post.record.text, new RichText({text: post.record.text}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.embed) {
|
||||||
|
if (AppBskyEmbedImages.isView(post.embed)) {
|
||||||
|
// get OPs media
|
||||||
|
for (const image of post.embed.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedExternal.isView(post.embed)) {
|
||||||
|
if (post.embed.external.thumb) {
|
||||||
|
images.set(post.embed.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||||
|
if (AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
|
||||||
|
if (post.embed.record.author.avatar) {
|
||||||
|
images.set(post.embed.record.author.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const embed of post.embed.record.embeds) {
|
||||||
|
if (AppBskyEmbedImages.isView(embed)) {
|
||||||
|
for (const image of embed.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedExternal.isView(embed)) {
|
||||||
|
if (embed.external.thumb) {
|
||||||
|
images.set(embed.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
|
||||||
|
if (AppBskyEmbedImages.isView(embed.media)) {
|
||||||
|
for (const image of embed.media.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (AppBskyEmbedExternal.isView(embed.media)) {
|
||||||
|
if (embed.media.external.thumb) {
|
||||||
|
images.set(embed.media.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyGraphDefs.isListView(embed.record)) {
|
||||||
|
if (embed.record.avatar) {
|
||||||
|
images.set(embed.record.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
|
||||||
|
if (embed.record.avatar) {
|
||||||
|
images.set(embed.record.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) {
|
||||||
|
const uri = getStarterPackImageUri(embed.record)
|
||||||
|
images.set(uri, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
AppBskyFeedPost.isRecord(post.embed.record.value) &&
|
||||||
|
post.embed.record.value.text
|
||||||
|
) {
|
||||||
|
texts.set(
|
||||||
|
post.embed.record.value.text,
|
||||||
|
new RichText({text: post.embed.record.value.text}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyGraphDefs.isListView(post.embed.record)) {
|
||||||
|
if (post.embed.record.avatar) {
|
||||||
|
images.set(post.embed.record.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyFeedDefs.isGeneratorView(post.embed.record)) {
|
||||||
|
if (post.embed.record.avatar) {
|
||||||
|
images.set(post.embed.record.avatar, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyGraphDefs.isStarterPackViewBasic(post.embed.record)) {
|
||||||
|
const uri = getStarterPackImageUri(post.embed.record)
|
||||||
|
images.set(uri, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||||
|
// get OPs media
|
||||||
|
if (AppBskyEmbedImages.isView(post.embed.media)) {
|
||||||
|
for (const image of post.embed.media.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppBskyEmbedExternal.isView(post.embed.media)) {
|
||||||
|
if (post.embed.media.external.thumb) {
|
||||||
|
images.set(post.embed.media.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get media from embedded post
|
||||||
|
if (AppBskyEmbedRecord.isViewRecord(post.embed.record.record)) {
|
||||||
|
for (const embed of post.embed.record.record.embeds) {
|
||||||
|
if (AppBskyEmbedImages.isView(embed)) {
|
||||||
|
for (const image of embed.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedExternal.isView(embed)) {
|
||||||
|
if (embed.external.thumb) {
|
||||||
|
images.set(embed.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
|
||||||
|
if (AppBskyEmbedImages.isView(embed.media)) {
|
||||||
|
for (const image of embed.media.images) {
|
||||||
|
images.set(image.fullsize, {
|
||||||
|
aspectRatio: normalizeAspectRatio(image.aspectRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (AppBskyEmbedExternal.isView(embed.media)) {
|
||||||
|
if (embed.media.external.thumb) {
|
||||||
|
images.set(embed.media.external.thumb, {
|
||||||
|
aspectRatio: {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
AppBskyFeedPost.isRecord(post.embed.record.record.value) &&
|
||||||
|
post.embed.record.record.value.text
|
||||||
|
) {
|
||||||
|
texts.set(
|
||||||
|
post.embed.record.record.value.text,
|
||||||
|
new RichText({text: post.embed.record.record.value.text}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deduped = Array.from(images.entries())
|
||||||
|
const resolved = await Promise.all(
|
||||||
|
deduped.map(async ([url, meta]) => {
|
||||||
|
try {
|
||||||
|
const image = await getImage(url)
|
||||||
|
return [
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
...meta,
|
||||||
|
...image,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
} catch (err) {
|
||||||
|
httpLogger.warn({err, uri: url}, 'could not fetch image')
|
||||||
|
return [
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
...meta,
|
||||||
|
image: null,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await Promise.all(Array.from(texts.values()).map(r => r.detectFacets(agent)))
|
||||||
|
const extracted = resolved.filter(([, i]) => i.image !== null) as [
|
||||||
|
string,
|
||||||
|
Image,
|
||||||
|
][]
|
||||||
|
|
||||||
|
return {
|
||||||
|
images: new Map(extracted),
|
||||||
|
texts,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export function toShortUrl(url: string): string {
|
||||||
|
try {
|
||||||
|
const urlp = new URL(url)
|
||||||
|
if (urlp.protocol !== 'http:' && urlp.protocol !== 'https:') {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
const path =
|
||||||
|
(urlp.pathname === '/' ? '' : urlp.pathname) + urlp.search + urlp.hash
|
||||||
|
if (path.length > 15) {
|
||||||
|
return urlp.host + path.slice(0, 13) + '...'
|
||||||
|
}
|
||||||
|
return urlp.host + path
|
||||||
|
} catch (e) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
+122
-19
@@ -2,15 +2,16 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@atproto/api@0.12.19-next.0":
|
"@atproto/api@^0.13.4":
|
||||||
version "0.12.19-next.0"
|
version "0.13.4"
|
||||||
resolved "https://registry.npmjs.org/@atproto/api/-/api-0.12.19-next.0.tgz"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.4.tgz#47631e622760b2a18e4aee75b728e9cf8952e221"
|
||||||
integrity sha512-wyWr4uIabTgDTBY99y3QyrFxcIx1Mh4DkURgSv8sd/b+w0lfrZAJh0Gg9BXdg/iIjcf/M2lCTL04r0vASfkMVg==
|
integrity sha512-Fwn37hP+Xr9YjA/hadvn7ZKbUPQhJiUus1+govgNKF3/jNdyk2ICoEe0z+hxaO3xX8LCU5yARbgt3SRoXbIwrg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.3.0"
|
"@atproto/common-web" "^0.3.0"
|
||||||
"@atproto/lexicon" "^0.4.0"
|
"@atproto/lexicon" "^0.4.1"
|
||||||
"@atproto/syntax" "^0.3.0"
|
"@atproto/syntax" "^0.3.0"
|
||||||
"@atproto/xrpc" "^0.5.0"
|
"@atproto/xrpc" "^0.6.1"
|
||||||
|
await-lock "^2.2.2"
|
||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
|
|
||||||
@@ -36,29 +37,29 @@
|
|||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
pino "^8.15.0"
|
pino "^8.15.0"
|
||||||
|
|
||||||
"@atproto/lexicon@^0.4.0":
|
"@atproto/lexicon@^0.4.1":
|
||||||
version "0.4.0"
|
version "0.4.1"
|
||||||
resolved "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.0.tgz"
|
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.1.tgz#19155210570a2fafbcc7d4f655d9b813948e72a0"
|
||||||
integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ==
|
integrity sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.3.0"
|
"@atproto/common-web" "^0.3.0"
|
||||||
"@atproto/syntax" "^0.3.0"
|
"@atproto/syntax" "^0.3.0"
|
||||||
iso-datestring-validator "^2.2.2"
|
iso-datestring-validator "^2.2.2"
|
||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
zod "^3.21.4"
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/syntax@^0.3.0":
|
"@atproto/syntax@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz"
|
resolved "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz"
|
||||||
integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
|
integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
|
||||||
|
|
||||||
"@atproto/xrpc@^0.5.0":
|
"@atproto/xrpc@^0.6.1":
|
||||||
version "0.5.0"
|
version "0.6.1"
|
||||||
resolved "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.5.0.tgz"
|
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.1.tgz#dcd1315c8c60eef5af2db7fa4e35a38ebc6d79d5"
|
||||||
integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog==
|
integrity sha512-Zy5ydXEdk6sY7FDUZcEVfCL1jvbL4tXu5CcdPqbEaW6LQtk9GLds/DK1bCX9kswTGaBC88EMuqQMfkxOhp2t4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/lexicon" "^0.4.0"
|
"@atproto/lexicon" "^0.4.1"
|
||||||
zod "^3.21.4"
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@cbor-extract/cbor-extract-darwin-arm64@2.2.0":
|
"@cbor-extract/cbor-extract-darwin-arm64@2.2.0":
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
@@ -278,6 +279,11 @@ atomic-sleep@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz"
|
||||||
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
|
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
|
||||||
|
|
||||||
|
await-lock@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
|
||||||
|
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
|
||||||
|
|
||||||
base64-js@0.0.8:
|
base64-js@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz"
|
resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz"
|
||||||
@@ -371,6 +377,11 @@ color-name@^1.1.4:
|
|||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
colorette@^2.0.7:
|
||||||
|
version "2.0.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||||
|
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
|
||||||
|
|
||||||
content-disposition@0.5.4:
|
content-disposition@0.5.4:
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz"
|
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz"
|
||||||
@@ -422,6 +433,11 @@ css-to-react-native@^3.0.0:
|
|||||||
css-color-keywords "^1.0.0"
|
css-color-keywords "^1.0.0"
|
||||||
postcss-value-parser "^4.0.2"
|
postcss-value-parser "^4.0.2"
|
||||||
|
|
||||||
|
dateformat@^4.6.3:
|
||||||
|
version "4.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
|
||||||
|
integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||||
@@ -478,6 +494,13 @@ encodeurl@~1.0.2:
|
|||||||
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
|
||||||
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
||||||
|
|
||||||
|
end-of-stream@^1.1.0:
|
||||||
|
version "1.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||||
|
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||||
|
dependencies:
|
||||||
|
once "^1.4.0"
|
||||||
|
|
||||||
es-define-property@^1.0.0:
|
es-define-property@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz"
|
||||||
@@ -547,6 +570,11 @@ express@^4.19.2:
|
|||||||
utils-merge "1.0.1"
|
utils-merge "1.0.1"
|
||||||
vary "~1.1.2"
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
fast-copy@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35"
|
||||||
|
integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==
|
||||||
|
|
||||||
fast-printf@^1.6.9:
|
fast-printf@^1.6.9:
|
||||||
version "1.6.9"
|
version "1.6.9"
|
||||||
resolved "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz"
|
resolved "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz"
|
||||||
@@ -559,6 +587,11 @@ fast-redact@^3.1.1:
|
|||||||
resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz"
|
resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz"
|
||||||
integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==
|
integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==
|
||||||
|
|
||||||
|
fast-safe-stringify@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||||
|
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||||
|
|
||||||
fflate@^0.7.3:
|
fflate@^0.7.3:
|
||||||
version "0.7.4"
|
version "0.7.4"
|
||||||
resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz"
|
resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz"
|
||||||
@@ -653,6 +686,11 @@ hasown@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
help-me@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6"
|
||||||
|
integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==
|
||||||
|
|
||||||
hex-rgb@^4.1.0:
|
hex-rgb@^4.1.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz"
|
resolved "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz"
|
||||||
@@ -706,6 +744,11 @@ iso-datestring-validator@^2.2.2:
|
|||||||
resolved "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz"
|
resolved "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz"
|
||||||
integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==
|
integrity sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==
|
||||||
|
|
||||||
|
joycon@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||||
|
integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0":
|
"js-tokens@^3.0.0 || ^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||||
@@ -779,6 +822,11 @@ mime@1.6.0:
|
|||||||
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
|
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
minimist@^1.2.6:
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
|
||||||
@@ -823,6 +871,13 @@ on-finished@2.4.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ee-first "1.1.1"
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
once@^1.3.1, once@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||||
|
dependencies:
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
p-finally@^1.0.0:
|
p-finally@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz"
|
||||||
@@ -865,7 +920,7 @@ path-to-regexp@0.1.7:
|
|||||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
|
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
|
||||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||||
|
|
||||||
pino-abstract-transport@^1.2.0:
|
pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz"
|
||||||
integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==
|
integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==
|
||||||
@@ -873,6 +928,26 @@ pino-abstract-transport@^1.2.0:
|
|||||||
readable-stream "^4.0.0"
|
readable-stream "^4.0.0"
|
||||||
split2 "^4.0.0"
|
split2 "^4.0.0"
|
||||||
|
|
||||||
|
pino-pretty@^11.2.2:
|
||||||
|
version "11.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.2.2.tgz#5e8ec69b31e90eb187715af07b1d29a544e60d39"
|
||||||
|
integrity sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==
|
||||||
|
dependencies:
|
||||||
|
colorette "^2.0.7"
|
||||||
|
dateformat "^4.6.3"
|
||||||
|
fast-copy "^3.0.2"
|
||||||
|
fast-safe-stringify "^2.1.1"
|
||||||
|
help-me "^5.0.0"
|
||||||
|
joycon "^3.1.1"
|
||||||
|
minimist "^1.2.6"
|
||||||
|
on-exit-leak-free "^2.1.0"
|
||||||
|
pino-abstract-transport "^1.0.0"
|
||||||
|
pump "^3.0.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
secure-json-parse "^2.4.0"
|
||||||
|
sonic-boom "^4.0.1"
|
||||||
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
pino-std-serializers@^6.0.0:
|
pino-std-serializers@^6.0.0:
|
||||||
version "6.2.2"
|
version "6.2.2"
|
||||||
resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz"
|
resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz"
|
||||||
@@ -940,6 +1015,14 @@ proxy-addr@~2.0.7:
|
|||||||
forwarded "0.2.0"
|
forwarded "0.2.0"
|
||||||
ipaddr.js "1.9.1"
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
pump@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||||
|
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||||
|
dependencies:
|
||||||
|
end-of-stream "^1.1.0"
|
||||||
|
once "^1.3.1"
|
||||||
|
|
||||||
qs@6.11.0:
|
qs@6.11.0:
|
||||||
version "6.11.0"
|
version "6.11.0"
|
||||||
resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz"
|
resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz"
|
||||||
@@ -1030,6 +1113,11 @@ satori@^0.10.13:
|
|||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
yoga-wasm-web "^0.3.3"
|
yoga-wasm-web "^0.3.3"
|
||||||
|
|
||||||
|
secure-json-parse@^2.4.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
|
||||||
|
integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
semver-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
|
||||||
@@ -1127,6 +1215,11 @@ string_decoder@^1.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.2.0"
|
safe-buffer "~5.2.0"
|
||||||
|
|
||||||
|
strip-json-comments@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||||
|
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||||
|
|
||||||
thread-stream@^2.6.0:
|
thread-stream@^2.6.0:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz"
|
resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz"
|
||||||
@@ -1253,6 +1346,16 @@ vary@~1.1.2:
|
|||||||
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
||||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
|
|
||||||
|
whatwg-mimetype@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a"
|
||||||
|
integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==
|
||||||
|
|
||||||
|
wrappy@1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||||
|
|
||||||
yn@3.1.1:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
@@ -1263,7 +1366,7 @@ yoga-wasm-web@^0.3.3:
|
|||||||
resolved "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz"
|
resolved "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz"
|
||||||
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
|
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
|
||||||
|
|
||||||
zod@^3.21.4:
|
zod@^3.21.4, zod@^3.23.8:
|
||||||
version "3.23.8"
|
version "3.23.8"
|
||||||
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
||||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||||
|
|||||||
Reference in New Issue
Block a user