Handle images
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.3",
|
||||
"pino-pretty": "^11.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export function Box({
|
||||
cx = [],
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
cx?: Record<string, any>[]
|
||||
cx?: (Record<string, any> | undefined)[]
|
||||
}) {
|
||||
return <div style={s([a.flex, a.flex_col, ...cx])}>{children}</div>
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function Row({
|
||||
|
||||
export function Column({
|
||||
children,
|
||||
gutter = 0,
|
||||
gutter,
|
||||
cx,
|
||||
width = 1,
|
||||
}: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
import {Image as ImageSource} from '../data/getPostData.js'
|
||||
import {atoms as a, style as s, theme as t} from '../theme/index.js'
|
||||
import {atoms as a, style as s, StyleProp,theme as t} from '../theme/index.js'
|
||||
import {Box} from './Box.js'
|
||||
import {MediaInsetBorder} from './MediaInsetBorder.js'
|
||||
|
||||
export type ImageProps = Omit<
|
||||
React.ImgHTMLAttributes<HTMLImageElement>,
|
||||
@@ -23,16 +24,24 @@ export function Image({image, cx, ...rest}: ImageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SquareImage({image}: {image: ImageSource}) {
|
||||
export function SquareImage({
|
||||
image,
|
||||
style,
|
||||
insetBorderStyle,
|
||||
}: {
|
||||
image: ImageSource
|
||||
insetBorderStyle?: StyleProp['style']
|
||||
} & StyleProp) {
|
||||
return (
|
||||
<Box
|
||||
cx={[
|
||||
a.relative,
|
||||
a.rounded_sm,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
a.w_full,
|
||||
t.atoms.bg_contrast_25,
|
||||
{paddingTop: '100%'},
|
||||
style,
|
||||
]}>
|
||||
<Image
|
||||
image={image}
|
||||
@@ -44,6 +53,7 @@ export function SquareImage({image}: {image: ImageSource}) {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<MediaInsetBorder style={insetBorderStyle} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
|
||||
import {atoms as a, StyleProp,theme as t} from '../theme/index.js'
|
||||
import {Box} from './Box.js'
|
||||
|
||||
/**
|
||||
* Applies and thin border within a bounding box. Used to contrast media from
|
||||
* bg of the container.
|
||||
*/
|
||||
export function MediaInsetBorder({
|
||||
children,
|
||||
style,
|
||||
opaque,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
/**
|
||||
* Used where this border needs to match adjacent borders, such as in
|
||||
* external link previews
|
||||
*/
|
||||
opaque?: boolean
|
||||
} & StyleProp) {
|
||||
const isLight = t.name === 'light'
|
||||
return (
|
||||
<Box
|
||||
cx={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
opaque
|
||||
? [t.atoms.border_contrast_low]
|
||||
: [
|
||||
isLight
|
||||
? t.atoms.border_contrast_low
|
||||
: t.atoms.border_contrast_high,
|
||||
{opacity: 0.6},
|
||||
],
|
||||
{
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {atoms as a, theme as t} from '../../theme/index.js'
|
||||
import {formatCount} from '../../util/formatCount.js'
|
||||
import {formatDate} from '../../util/formatDate.js'
|
||||
import {moderatePost} from '../../util/moderatePost.js'
|
||||
import {sanitizeHandle} from '../../util/sanitizeHandle.js'
|
||||
import {Avatar} from '../Avatar.js'
|
||||
import {Box} from '../Box.js'
|
||||
import {Heart} from '../icons/Heart.js'
|
||||
@@ -85,7 +86,7 @@ export function Post({
|
||||
{post.author.displayName || post.author.handle}
|
||||
</Text>
|
||||
<Text cx={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||
@{post.author.handle}
|
||||
{sanitizeHandle(post.author.handle, '@')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -8,14 +8,14 @@ import {ModeratorData} from '../data/getModeratorData.js'
|
||||
import {Image as ImageSource, PostData} from '../data/getPostData.js'
|
||||
import {atoms as a} from '../theme/index.js'
|
||||
import {getStarterPackImageUri} from '../util/getStarterPackImageUri.js'
|
||||
import {Embed, EmbedType,parseEmbed} from '../util/parseEmbed.js'
|
||||
import {Embed, EmbedType, parseEmbed} from '../util/parseEmbed.js'
|
||||
import {Box} from './Box.js'
|
||||
import {FeedCard} from './FeedCard.js'
|
||||
import * as Grid from './Grid.js'
|
||||
import {Image, SquareImage} from './Image.js'
|
||||
import {LinkCard} from './LinkCard.js'
|
||||
import {ListCard} from './ListCard.js'
|
||||
import {NotQuotePost,QuotePost} from './PostEmbed/QuotePost.js'
|
||||
import {NotQuotePost, QuotePost} from './PostEmbed/QuotePost.js'
|
||||
|
||||
type CommonProps = {
|
||||
data: PostData
|
||||
@@ -188,6 +188,7 @@ export function ImagesEmbed({
|
||||
embed: EmbedType<'images'>
|
||||
}) {
|
||||
const {images} = embed.view
|
||||
const gutter = a.p_2xs.padding
|
||||
|
||||
if (images.length > 0) {
|
||||
const imgs = images
|
||||
@@ -201,37 +202,139 @@ export function ImagesEmbed({
|
||||
)
|
||||
} 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.Row gutter={gutter}>
|
||||
<Grid.Column width={1 / 2} gutter={gutter}>
|
||||
<SquareImage
|
||||
image={imgs[0]}
|
||||
style={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</Grid.Column>
|
||||
<Grid.Column gutter={a.p_xs.padding} width={1 / 2}>
|
||||
<SquareImage image={imgs[1]} />
|
||||
<Grid.Column width={1 / 2} gutter={gutter}>
|
||||
<SquareImage
|
||||
image={imgs[1]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</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.Row gutter={gutter}>
|
||||
<Grid.Column gutter={gutter} width={2 / 3}>
|
||||
<SquareImage
|
||||
image={imgs[0]}
|
||||
style={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 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 gutter={gutter} width={1 / 3} cx={[a.gap_xs]}>
|
||||
<SquareImage
|
||||
image={imgs[1]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
<SquareImage
|
||||
image={imgs[2]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</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.Row gutter={gutter}>
|
||||
<Grid.Column gutter={gutter} width={1 / 2} cx={[a.gap_xs]}>
|
||||
<SquareImage
|
||||
image={imgs[0]}
|
||||
style={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
/>
|
||||
<SquareImage
|
||||
image={imgs[2]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</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 gutter={gutter} width={1 / 2} cx={[a.gap_xs]}>
|
||||
<SquareImage
|
||||
image={imgs[1]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
<SquareImage
|
||||
image={imgs[3]}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
insetBorderStyle={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
}}
|
||||
/>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {PostData} from '../../data/getPostData.js'
|
||||
import {atoms as a, theme as t} from '../../theme/index.js'
|
||||
import {getModerationCauseInfo} from '../../util/getModerationCauseInfo.js'
|
||||
import {moderatePost} from '../../util/moderatePost.js'
|
||||
import {sanitizeHandle} from '../../util/sanitizeHandle.js'
|
||||
import {viewRecordToPostView} from '../../util/viewRecordToPostView.js'
|
||||
import {Avatar} from '../Avatar.js'
|
||||
import {Box} from '../Box.js'
|
||||
@@ -71,7 +72,7 @@ export function QuotePost({
|
||||
{author.displayName || author.handle}
|
||||
</Text>
|
||||
<Text cx={[a.text_xs, t.atoms.text_contrast_medium]}>
|
||||
@{author.handle}
|
||||
{sanitizeHandle(author.handle, '@')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -7,6 +7,7 @@ export * as tokens from './tokens.js'
|
||||
const fontFamily = 'Inter'
|
||||
|
||||
export const theme = {
|
||||
name: 'light',
|
||||
palette: colors,
|
||||
atoms: {
|
||||
text: {
|
||||
@@ -86,7 +87,15 @@ export const theme = {
|
||||
},
|
||||
}
|
||||
|
||||
export function style(styleObjects: Record<string, any>[]) {
|
||||
export type StyleObject = Record<string, any>
|
||||
|
||||
export type StyleProp = {
|
||||
style?: StyleObject
|
||||
}
|
||||
|
||||
export function style(
|
||||
styleObjects: (StyleObject | boolean | null | undefined)[],
|
||||
) {
|
||||
return Object.assign({}, ...styleObjects.filter(Boolean).flat())
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function isInvalidHandle(handle: string): boolean {
|
||||
return handle === 'handle.invalid'
|
||||
}
|
||||
|
||||
export function sanitizeHandle(handle: string, prefix = ''): string {
|
||||
return isInvalidHandle(handle) ? '⚠Invalid Handle' : `${prefix}${handle}`
|
||||
}
|
||||
@@ -1309,6 +1309,11 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
prettier@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
|
||||
integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
|
||||
|
||||
process-warning@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user