Files
bsky-social-app/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts
T
vineyardbovines 9aea4362d5 Add app.bsky.embed.gallery embed type support
Wires the new gallery embed (atproto PR #4827) through the display and
compose pipelines while the lexicon is unpublished. Gallery posts render
via the existing carousel for >4 items and grid for <=4. Compose now
allows up to 10 images and auto-promotes images -> gallery above 4
(demotes back when count drops to <=4 so old clients still see legacy
embeds when possible). Drafts persist as the new `embedGallery` field.

Local shim types in `src/lib/api/gallery-embed-shim*.ts` mirror the
lexicon shape; delete both files once @atproto/api ships the generated
types.
2026-06-03 13:12:27 -04:00

124 lines
2.9 KiB
TypeScript

import {
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
type AppBskyFeedDefs,
AppBskyFeedPost,
type ModerationCause,
type ModerationUI,
} from '@atproto/api'
import {AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
import {unique} from '#/lib/moderation'
import {type AppModerationCause} from '#/components/Pills'
import {Features, features} from '#/analytics/features'
import * as bsky from '#/types/bsky'
export const POST_META_NO_CONTENT_OFFSET = {paddingTop: 10}
export const POST_EMBED_NO_CONTENT_OFFSET = {paddingTop: 6}
export function maybeApplyGalleryOffsetStyles(
placement: 'meta' | 'embed',
{
post,
modui,
additionalCauses,
}: {
post: AppBskyFeedDefs.PostView
modui: ModerationUI
additionalCauses?: ModerationCause[] | AppModerationCause[]
},
) {
// don't ever check gates like this, except this one time
if (!features.isOn(Features.PostGalleryEmbedEnable)) return
if (
!bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
)
) {
return
}
/*
* First check if we even have images
*/
const embed = post.record.embed
const isImageEmbed =
embed &&
bsky.dangerousIsType<AppBskyEmbedImages.Main>(
embed,
AppBskyEmbedImages.isMain,
)
const isGalleryEmbed =
embed &&
bsky.dangerousIsType<AppBskyEmbedGallery.Main>(
embed,
AppBskyEmbedGallery.isMain,
)
const isRecordWithMedia =
embed &&
bsky.dangerousIsType<AppBskyEmbedRecordWithMedia.Main>(
embed,
AppBskyEmbedRecordWithMedia.isMain,
)
let hasImages = false
if (isImageEmbed) {
// one image, not a gallery
if (embed.images.length === 1) return
hasImages = true
}
if (isGalleryEmbed) {
// one image, not a gallery
if (embed.items.length === 1) return
hasImages = true
}
if (isRecordWithMedia) {
if (
bsky.dangerousIsType<AppBskyEmbedImages.Main>(
embed.media,
AppBskyEmbedImages.isMain,
)
) {
// one image, not a gallery
if (embed.media.images.length === 1) return
}
if (
bsky.dangerousIsType<AppBskyEmbedGallery.Main>(
embed.media,
AppBskyEmbedGallery.isMain,
)
) {
// one image, not a gallery
if (embed.media.items.length === 1) return
}
hasImages = true
}
if (!hasImages) return
/*
* Then check if we have any text
*/
let hasLabels = false
if (modui.alert) {
hasLabels = modui.alerts.filter(unique).length > 0
}
if (modui.inform) {
hasLabels = hasLabels || modui.informs.filter(unique).length > 0
}
if (additionalCauses?.length) {
hasLabels = true
}
/*
* If no text or labels, then we need a lil bump
*/
const shouldApplyOffset = !post.record.text && !hasLabels
return shouldApplyOffset
? placement === 'meta'
? POST_META_NO_CONTENT_OFFSET
: POST_EMBED_NO_CONTENT_OFFSET
: {}
}