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.
This commit is contained in:
vineyardbovines
2026-06-03 13:12:27 -04:00
parent b6650d8d5c
commit 9aea4362d5
15 changed files with 408 additions and 84 deletions
+28
View File
@@ -21,6 +21,7 @@ import {sha256} from 'js-sha256'
import {CID} from 'multiformats/cid'
import * as Hasher from 'multiformats/hashes/hasher'
import {type AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
import {isNetworkError} from '#/lib/strings/errors'
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
import {logger} from '#/logger'
@@ -252,6 +253,7 @@ async function resolveEmbed(
onStateChange: ((state: string) => void) | undefined,
): Promise<
| $Typed<AppBskyEmbedImages.Main>
| $Typed<AppBskyEmbedGallery.Main>
| $Typed<AppBskyEmbedVideo.Main>
| $Typed<AppBskyEmbedExternal.Main>
| $Typed<AppBskyEmbedRecord.Main>
@@ -311,6 +313,7 @@ async function resolveMedia(
): Promise<
| $Typed<AppBskyEmbedExternal.Main>
| $Typed<AppBskyEmbedImages.Main>
| $Typed<AppBskyEmbedGallery.Main>
| $Typed<AppBskyEmbedVideo.Main>
| undefined
> {
@@ -338,6 +341,31 @@ async function resolveMedia(
images,
}
}
if (embedDraft.media?.type === 'gallery') {
const imagesDraft = embedDraft.media.images
logger.debug(`Uploading gallery items`, {
count: imagesDraft.length,
})
onStateChange?.(t`Uploading images...`)
const items: AppBskyEmbedGallery.Image[] = await Promise.all(
imagesDraft.map(async (image, i) => {
logger.debug(`Compressing gallery image #${i}`)
const {path, width, height, mime} = await compressImage(image)
logger.debug(`Uploading gallery image #${i}`)
const res = await uploadBlob(agent, path, mime)
return {
$type: 'app.bsky.embed.gallery#image',
image: res.data.blob,
alt: image.alt,
aspectRatio: {width, height},
}
}),
)
return {
$type: 'app.bsky.embed.gallery',
items,
}
}
if (
embedDraft.media?.type === 'video' &&
embedDraft.media.video.status === 'done'