fix: complete Gif object reconstruction for draft rehydration
Fix "Cannot read property 'url' of undefined" error when rehydrating drafts with GIFs. The Gif object was missing required properties like url, content_description, and media_formats.preview that are needed by useResolveGifQuery and other components. Also preserve alt text through serialization by storing it in URL query params alongside dimensions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+30
-12
@@ -181,7 +181,7 @@ function serializeVideo(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize GIF to server format as external embed.
|
* Serialize GIF to server format as external embed.
|
||||||
* URL format: https://media.tenor.com/{id}/{filename}.gif?hh=HEIGHT&ww=WIDTH
|
* URL format: https://media.tenor.com/{id}/{filename}.gif?hh=HEIGHT&ww=WIDTH&alt=ALT_TEXT
|
||||||
*/
|
*/
|
||||||
function serializeGif(gifMedia: {
|
function serializeGif(gifMedia: {
|
||||||
type: 'gif'
|
type: 'gif'
|
||||||
@@ -195,12 +195,16 @@ function serializeGif(gifMedia: {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build URL with dimensions in query params
|
// Build URL with dimensions and alt text in query params
|
||||||
const url = new URL(gifFormat.url)
|
const url = new URL(gifFormat.url)
|
||||||
if (gifFormat.dims) {
|
if (gifFormat.dims) {
|
||||||
url.searchParams.set('ww', String(gifFormat.dims[0]))
|
url.searchParams.set('ww', String(gifFormat.dims[0]))
|
||||||
url.searchParams.set('hh', String(gifFormat.dims[1]))
|
url.searchParams.set('hh', String(gifFormat.dims[1]))
|
||||||
}
|
}
|
||||||
|
// Store alt text if present
|
||||||
|
if (gifMedia.alt) {
|
||||||
|
url.searchParams.set('alt', gifMedia.alt)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.draft.defs#draftEmbedExternal',
|
$type: 'app.bsky.draft.defs#draftEmbedExternal',
|
||||||
@@ -298,11 +302,11 @@ export function draftViewToSummary(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse GIF data from a Tenor URL.
|
* Parse GIF data from a Tenor URL.
|
||||||
* URL format: https://media.tenor.com/{id}/{filename}.gif?hh=HEIGHT&ww=WIDTH
|
* URL format: https://media.tenor.com/{id}/{filename}.gif?hh=HEIGHT&ww=WIDTH&alt=ALT_TEXT
|
||||||
*/
|
*/
|
||||||
function parseGifFromUrl(
|
function parseGifFromUrl(
|
||||||
uri: string,
|
uri: string,
|
||||||
): {url: string; width: number; height: number} | undefined {
|
): {url: string; width: number; height: number; alt: string} | undefined {
|
||||||
try {
|
try {
|
||||||
const url = new URL(uri)
|
const url = new URL(uri)
|
||||||
if (url.hostname !== TENOR_HOSTNAME) {
|
if (url.hostname !== TENOR_HOSTNAME) {
|
||||||
@@ -311,12 +315,13 @@ function parseGifFromUrl(
|
|||||||
|
|
||||||
const height = parseInt(url.searchParams.get('hh') || '', 10)
|
const height = parseInt(url.searchParams.get('hh') || '', 10)
|
||||||
const width = parseInt(url.searchParams.get('ww') || '', 10)
|
const width = parseInt(url.searchParams.get('ww') || '', 10)
|
||||||
|
const alt = url.searchParams.get('alt') || ''
|
||||||
|
|
||||||
if (!height || !width) {
|
if (!height || !width) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return {url: uri, width, height}
|
return {url: uri, width, height, alt}
|
||||||
} catch {
|
} catch {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -370,20 +375,33 @@ export function draftToComposerPosts(
|
|||||||
for (const ext of post.embedExternals) {
|
for (const ext of post.embedExternals) {
|
||||||
const gifData = parseGifFromUrl(ext.uri)
|
const gifData = parseGifFromUrl(ext.uri)
|
||||||
if (gifData) {
|
if (gifData) {
|
||||||
// Reconstruct a minimal Gif object
|
// Reconstruct a Gif object with all required properties
|
||||||
// The full Gif object will need to be re-fetched from Tenor if needed
|
const mediaObject = {
|
||||||
|
url: gifData.url,
|
||||||
|
dims: [gifData.width, gifData.height] as [number, number],
|
||||||
|
duration: 0,
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
embed.media = {
|
embed.media = {
|
||||||
type: 'gif',
|
type: 'gif',
|
||||||
gif: {
|
gif: {
|
||||||
id: '',
|
id: '',
|
||||||
|
created: 0,
|
||||||
|
hasaudio: false,
|
||||||
|
hascaption: false,
|
||||||
|
flags: '',
|
||||||
|
tags: [],
|
||||||
|
title: '',
|
||||||
|
content_description: gifData.alt || '',
|
||||||
|
itemurl: '',
|
||||||
|
url: gifData.url, // Required for useResolveGifQuery
|
||||||
media_formats: {
|
media_formats: {
|
||||||
gif: {
|
gif: mediaObject,
|
||||||
url: gifData.url,
|
tinygif: mediaObject,
|
||||||
dims: [gifData.width, gifData.height],
|
preview: mediaObject,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as Gif,
|
} as Gif,
|
||||||
alt: '',
|
alt: gifData.alt,
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user