Bluesky app fork with some witchin' additions 💫

[GIFs] Restore default alt text (#3893)

* restore default alt text

* factor out gif alt logic + enable require alt text setting

* rm console.log

* don't prefill input + esc handling

* typo

* Nits

* shorten user alt prefix

* Remove unnecessary condition, rename for clarity

* Add comment

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by samuel.fm

Dan Abramov and committed by
GitHub
7d72dfb1 77e6c75a

+84 -20
+36
src/lib/gif-alt-text.ts
··· 1 + // Kind of a hack. We needed some way to distinguish these. 2 + const USER_ALT_PREFIX = 'Alt: ' 3 + const DEFAULT_ALT_PREFIX = 'ALT: ' 4 + 5 + export function createGIFDescription( 6 + tenorDescription: string, 7 + preferredAlt: string = '', 8 + ) { 9 + preferredAlt = preferredAlt.trim() 10 + if (preferredAlt !== '') { 11 + return USER_ALT_PREFIX + preferredAlt 12 + } else { 13 + return DEFAULT_ALT_PREFIX + tenorDescription 14 + } 15 + } 16 + 17 + export function parseAltFromGIFDescription(description: string): { 18 + isPreferred: boolean 19 + alt: string 20 + } { 21 + if (description.startsWith(USER_ALT_PREFIX)) { 22 + return { 23 + isPreferred: true, 24 + alt: description.replace(USER_ALT_PREFIX, ''), 25 + } 26 + } else if (description.startsWith(DEFAULT_ALT_PREFIX)) { 27 + return { 28 + isPreferred: false, 29 + alt: description.replace(DEFAULT_ALT_PREFIX, ''), 30 + } 31 + } 32 + return { 33 + isPreferred: false, 34 + alt: description, 35 + } 36 + }
+28 -12
src/view/com/composer/Composer.tsx
··· 18 18 import {useLingui} from '@lingui/react' 19 19 import {observer} from 'mobx-react-lite' 20 20 21 + import { 22 + createGIFDescription, 23 + parseAltFromGIFDescription, 24 + } from '#/lib/gif-alt-text' 21 25 import {LikelyType} from '#/lib/link-meta/link-meta' 22 26 import {logEvent} from '#/lib/statsig/statsig' 23 27 import {logger} from '#/logger' ··· 211 215 [gallery, track], 212 216 ) 213 217 218 + const isAltTextRequiredAndMissing = useMemo(() => { 219 + if (!requireAltTextEnabled) return false 220 + 221 + if (gallery.needsAltText) return true 222 + if (extGif) { 223 + if (!extLink?.meta?.description) return true 224 + 225 + const parsedAlt = parseAltFromGIFDescription(extLink.meta.description) 226 + if (!parsedAlt.isPreferred) return true 227 + } 228 + return false 229 + }, [gallery.needsAltText, extLink, extGif, requireAltTextEnabled]) 230 + 214 231 const onPressPublish = async () => { 215 232 if (isProcessing || graphemeLength > MAX_GRAPHEME_LENGTH) { 216 233 return 217 234 } 218 - if (requireAltTextEnabled && gallery.needsAltText) { 235 + 236 + if (isAltTextRequiredAndMissing) { 219 237 return 220 238 } 221 239 ··· 298 316 } 299 317 300 318 const canPost = useMemo( 301 - () => 302 - graphemeLength <= MAX_GRAPHEME_LENGTH && 303 - (!requireAltTextEnabled || !gallery.needsAltText), 304 - [graphemeLength, requireAltTextEnabled, gallery.needsAltText], 319 + () => graphemeLength <= MAX_GRAPHEME_LENGTH && !isAltTextRequiredAndMissing, 320 + [graphemeLength, isAltTextRequiredAndMissing], 305 321 ) 306 322 const selectTextInputPlaceholder = replyTo 307 323 ? _(msg`Write your reply`) ··· 328 344 image: gif.media_formats.preview.url, 329 345 likelyType: LikelyType.HTML, 330 346 title: gif.content_description, 331 - description: '', 347 + description: createGIFDescription(gif.content_description), 332 348 }, 333 349 }) 334 350 setExtGif(gif) ··· 343 359 ? { 344 360 ...ext, 345 361 meta: { 346 - ...ext?.meta, 347 - description: 348 - altText.trim().length === 0 349 - ? '' 350 - : `Alt text: ${altText.trim()}`, 362 + ...ext.meta, 363 + description: createGIFDescription( 364 + ext.meta.title ?? '', 365 + altText, 366 + ), 351 367 }, 352 368 } 353 369 : ext, ··· 433 449 </> 434 450 )} 435 451 </View> 436 - {requireAltTextEnabled && gallery.needsAltText && ( 452 + {isAltTextRequiredAndMissing && ( 437 453 <View style={[styles.reminderLine, pal.viewLight]}> 438 454 <View style={styles.errorIcon}> 439 455 <FontAwesomeIcon
+12 -4
src/view/com/composer/GifAltText.tsx
··· 6 6 7 7 import {ExternalEmbedDraft} from '#/lib/api' 8 8 import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants' 9 + import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' 9 10 import { 10 11 EmbedPlayerParams, 11 12 parseEmbedPlayerFromUrl, ··· 59 60 60 61 if (!gif || !params) return null 61 62 63 + const parsedAlt = parseAltFromGIFDescription(link.description) 62 64 return ( 63 65 <> 64 66 <TouchableOpacity ··· 80 82 a.align_center, 81 83 {backgroundColor: 'rgba(0, 0, 0, 0.75)'}, 82 84 ]}> 83 - {link.description ? ( 85 + {parsedAlt.isPreferred ? ( 84 86 <Check size="xs" fill={t.palette.white} style={a.ml_xs} /> 85 87 ) : ( 86 88 <Plus size="sm" fill={t.palette.white} /> ··· 102 104 onSubmit={onPressSubmit} 103 105 link={link} 104 106 params={params} 105 - initalValue={link.description.replace('Alt text: ', '')} 107 + initialValue={parsedAlt.isPreferred ? parsedAlt.alt : ''} 106 108 key={link.uri} 107 109 /> 108 110 </Dialog.Outer> ··· 114 116 onSubmit, 115 117 link, 116 118 params, 117 - initalValue, 119 + initialValue: initalValue, 118 120 }: { 119 121 onSubmit: (text: string) => void 120 122 link: AppBskyEmbedExternal.ViewExternal 121 123 params: EmbedPlayerParams 122 - initalValue: string 124 + initialValue: string 123 125 }) { 124 126 const {_} = useLingui() 125 127 const [altText, setAltText] = useState(initalValue) 128 + const control = Dialog.useDialogContext() 126 129 127 130 const onPressSubmit = useCallback(() => { 128 131 onSubmit(altText) ··· 147 150 multiline 148 151 numberOfLines={3} 149 152 autoFocus 153 + onKeyPress={({nativeEvent}) => { 154 + if (nativeEvent.key === 'Escape') { 155 + control.close() 156 + } 157 + }} 150 158 /> 151 159 </TextField.Root> 152 160 </View>
+8 -4
src/view/com/util/post-embeds/GifEmbed.tsx
··· 6 6 import {useLingui} from '@lingui/react' 7 7 8 8 import {HITSLOP_10} from '#/lib/constants' 9 + import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' 9 10 import {isWeb} from '#/platform/detection' 10 11 import {EmbedPlayerParams} from 'lib/strings/embed-player' 11 12 import {useAutoplayDisabled} from 'state/preferences' ··· 116 117 playerRef.current?.toggleAsync() 117 118 }, []) 118 119 120 + const parsedAlt = React.useMemo( 121 + () => parseAltFromGIFDescription(link.description), 122 + [link], 123 + ) 124 + 119 125 return ( 120 126 <View 121 127 style={[a.rounded_sm, a.overflow_hidden, a.mt_sm, {maxWidth: '100%'}]}> ··· 140 146 onPlayerStateChange={onPlayerStateChange} 141 147 ref={playerRef} 142 148 accessibilityHint={_(msg`Animated GIF`)} 143 - accessibilityLabel={link.description.replace('Alt text: ', '')} 149 + accessibilityLabel={parsedAlt.alt} 144 150 /> 145 151 146 - {!hideAlt && link.description.startsWith('Alt text: ') && ( 147 - <AltText text={link.description.replace('Alt text: ', '')} /> 148 - )} 152 + {!hideAlt && parsedAlt.isPreferred && <AltText text={parsedAlt.alt} />} 149 153 </View> 150 154 </View> 151 155 )