From c06040cc209338fc37980648b31d4d64cc0c5c09 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 8 Oct 2024 09:02:58 +0900 Subject: [PATCH] Fetch link previews from RQ (#5608) Co-authored-by: Mary Co-authored-by: Hailey --- src/App.native.tsx | 61 +++--- src/App.web.tsx | 57 +++--- src/lib/api/index.ts | 47 ++++- src/lib/api/resolve.ts | 49 ++++- src/state/queries/resolve-link.ts | 70 +++++++ src/state/shell/composer/index.tsx | 23 +++ src/state/shell/index.tsx | 16 +- src/view/com/composer/Composer.tsx | 176 +++++++---------- src/view/com/composer/ExternalEmbed.tsx | 119 +++++++---- src/view/com/composer/GifAltText.tsx | 95 +++++---- src/view/com/composer/state/composer.ts | 3 +- .../com/composer/useExternalLinkFetch.e2e.ts | 47 ----- src/view/com/composer/useExternalLinkFetch.ts | 187 ------------------ src/view/com/util/post-ctrls/PostCtrls.tsx | 4 +- .../util/post-embeds/ExternalLinkEmbed.tsx | 12 +- src/view/com/util/post-embeds/GifEmbed.tsx | 21 +- src/view/com/util/post-embeds/QuoteEmbed.tsx | 19 ++ 17 files changed, 490 insertions(+), 516 deletions(-) create mode 100644 src/state/queries/resolve-link.ts delete mode 100644 src/view/com/composer/useExternalLinkFetch.e2e.ts delete mode 100644 src/view/com/composer/useExternalLinkFetch.ts diff --git a/src/App.native.tsx b/src/App.native.tsx index c6334379f7..96b493af41 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -51,6 +51,7 @@ import { } from '#/state/session' import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' +import {Provider as ComposerProvider} from '#/state/shell/composer' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' @@ -125,35 +126,37 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 1664812d08..0d500908fd 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -41,6 +41,7 @@ import { } from '#/state/session' import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' +import {Provider as ComposerProvider} from '#/state/shell/composer' import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' @@ -116,33 +117,35 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index e6e8eea3da..6edb111e6b 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -12,12 +12,17 @@ import { ComAtprotoRepoStrongRef, RichText, } from '@atproto/api' +import {QueryClient} from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import {logger} from '#/logger' import {ComposerImage, compressImage} from '#/state/gallery' import {writePostgateRecord} from '#/state/queries/postgate' +import { + fetchResolveGifQuery, + fetchResolveLinkQuery, +} from '#/state/queries/resolve-link' import { createThreadgateRecord, ThreadgateAllowUISetting, @@ -27,7 +32,6 @@ import { import {ComposerState, EmbedDraft} from '#/view/com/composer/state/composer' import {createGIFDescription} from '../gif-alt-text' import {LinkMeta} from '../link-meta/link-meta' -import {resolveGif, resolveLink} from './resolve' import {uploadBlob} from './upload-blob' export {uploadBlob} @@ -51,7 +55,11 @@ interface PostOpts { langs?: string[] } -export async function post(agent: BskyAgent, opts: PostOpts) { +export async function post( + agent: BskyAgent, + queryClient: QueryClient, + opts: PostOpts, +) { let reply let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true}) @@ -64,6 +72,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) { const embed = await resolveEmbed( agent, + queryClient, opts.composerState, opts.onStateChange, ) @@ -178,6 +187,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) { async function resolveEmbed( agent: BskyAgent, + queryClient: QueryClient, draft: ComposerState, onStateChange: ((state: string) => void) | undefined, ): Promise< @@ -190,8 +200,8 @@ async function resolveEmbed( > { if (draft.embed.quote) { const [resolvedMedia, resolvedQuote] = await Promise.all([ - resolveMedia(agent, draft.embed, onStateChange), - resolveRecord(agent, draft.embed.quote.uri), + resolveMedia(agent, queryClient, draft.embed, onStateChange), + resolveRecord(agent, queryClient, draft.embed.quote.uri), ]) if (resolvedMedia) { return { @@ -208,12 +218,21 @@ async function resolveEmbed( record: resolvedQuote, } } - const resolvedMedia = await resolveMedia(agent, draft.embed, onStateChange) + const resolvedMedia = await resolveMedia( + agent, + queryClient, + draft.embed, + onStateChange, + ) if (resolvedMedia) { return resolvedMedia } if (draft.embed.link) { - const resolvedLink = await resolveLink(agent, draft.embed.link.uri) + const resolvedLink = await fetchResolveLinkQuery( + queryClient, + agent, + draft.embed.link.uri, + ) if (resolvedLink.type === 'record') { return { $type: 'app.bsky.embed.record', @@ -226,6 +245,7 @@ async function resolveEmbed( async function resolveMedia( agent: BskyAgent, + queryClient: QueryClient, embedDraft: EmbedDraft, onStateChange: ((state: string) => void) | undefined, ): Promise< @@ -286,7 +306,11 @@ async function resolveMedia( } if (embedDraft.media?.type === 'gif') { const gifDraft = embedDraft.media - const resolvedGif = await resolveGif(agent, gifDraft.gif) + const resolvedGif = await fetchResolveGifQuery( + queryClient, + agent, + gifDraft.gif, + ) let blob: BlobRef | undefined if (resolvedGif.thumb) { onStateChange?.('Uploading link thumbnail...') @@ -305,7 +329,11 @@ async function resolveMedia( } } if (embedDraft.link) { - const resolvedLink = await resolveLink(agent, embedDraft.link.uri) + const resolvedLink = await fetchResolveLinkQuery( + queryClient, + agent, + embedDraft.link.uri, + ) if (resolvedLink.type === 'external') { let blob: BlobRef | undefined if (resolvedLink.thumb) { @@ -330,9 +358,10 @@ async function resolveMedia( async function resolveRecord( agent: BskyAgent, + queryClient: QueryClient, uri: string, ): Promise { - const resolvedLink = await resolveLink(agent, uri) + const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri) if (resolvedLink.type !== 'record') { throw Error('Expected uri to resolve to a record') } diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index a97a3f31c4..4f409e100f 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,4 +1,4 @@ -import {ComAtprotoRepoStrongRef} from '@atproto/api' +import {AppBskyActorDefs, ComAtprotoRepoStrongRef} from '@atproto/api' import {AtUri} from '@atproto/api' import {BskyAgent} from '@atproto/api' @@ -33,12 +33,32 @@ type ResolvedExternalLink = { thumb: ComposerImage | undefined } -type ResolvedRecord = { +type ResolvedPostRecord = { type: 'record' record: ComAtprotoRepoStrongRef.Main + kind: 'post' + meta: { + text: string + indexedAt: string + author: AppBskyActorDefs.ProfileViewBasic + } } -type ResolvedLink = ResolvedExternalLink | ResolvedRecord +type ResolvedOtherRecord = { + type: 'record' + record: ComAtprotoRepoStrongRef.Main + kind: 'other' + meta: { + // We should replace this with a hydrated record (e.g. feed, list, starter pack) + // and change the composer preview to use the actual post embed components: + title: string + } +} + +export type ResolvedLink = + | ResolvedExternalLink + | ResolvedPostRecord + | ResolvedOtherRecord export async function resolveLink( agent: BskyAgent, @@ -57,6 +77,8 @@ export async function resolveLink( cid: result.cid, uri: result.uri, }, + kind: 'post', + meta: result, } } if (isBskyCustomFeedUrl(uri)) { @@ -64,7 +86,12 @@ export async function resolveLink( const result = await getFeedAsEmbed(agent, fetchDid, uri) return { type: 'record', - record: result.embed!.record, // TODO: Fix types. + record: result.embed!.record, + kind: 'other', + meta: { + // TODO: Include hydrated content instead. + title: result.meta!.title!, + }, } } if (isBskyListUrl(uri)) { @@ -72,7 +99,12 @@ export async function resolveLink( const result = await getListAsEmbed(agent, fetchDid, uri) return { type: 'record', - record: result.embed!.record, // TODO: Fix types. + record: result.embed!.record, + kind: 'other', + meta: { + // TODO: Include hydrated content instead. + title: result.meta!.title!, + }, } } if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) { @@ -80,7 +112,12 @@ export async function resolveLink( const result = await getStarterPackAsEmbed(agent, fetchDid, uri) return { type: 'record', - record: result.embed!.record, // TODO: Fix types. + record: result.embed!.record, + kind: 'other', + meta: { + // TODO: Include hydrated content instead. + title: result.meta!.title!, + }, } } return resolveExternal(agent, uri) diff --git a/src/state/queries/resolve-link.ts b/src/state/queries/resolve-link.ts new file mode 100644 index 0000000000..5856cfb5f7 --- /dev/null +++ b/src/state/queries/resolve-link.ts @@ -0,0 +1,70 @@ +import {QueryClient, useQuery} from '@tanstack/react-query' + +import {STALE} from '#/state/queries/index' +import {useAgent} from '../session' + +const RQKEY_LINK_ROOT = 'resolve-link' +export const RQKEY_LINK = (url: string) => [RQKEY_LINK_ROOT, url] + +const RQKEY_GIF_ROOT = 'resolve-gif' +export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url] + +import {BskyAgent} from '@atproto/api' + +import {ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' +import {Gif} from './tenor' + +export function useResolveLinkQuery(url: string) { + const agent = useAgent() + return useQuery({ + staleTime: STALE.HOURS.ONE, + queryKey: RQKEY_LINK(url), + queryFn: async () => { + return await resolveLink(agent, url) + }, + }) +} +export function fetchResolveLinkQuery( + queryClient: QueryClient, + agent: BskyAgent, + url: string, +) { + return queryClient.fetchQuery({ + staleTime: STALE.HOURS.ONE, + queryKey: RQKEY_LINK(url), + queryFn: async () => { + return await resolveLink(agent, url) + }, + }) +} +export function precacheResolveLinkQuery( + queryClient: QueryClient, + url: string, + resolvedLink: ResolvedLink, +) { + queryClient.setQueryData(RQKEY_LINK(url), resolvedLink) +} + +export function useResolveGifQuery(gif: Gif) { + const agent = useAgent() + return useQuery({ + staleTime: STALE.HOURS.ONE, + queryKey: RQKEY_GIF(gif.url), + queryFn: async () => { + return await resolveGif(agent, gif) + }, + }) +} +export function fetchResolveGifQuery( + queryClient: QueryClient, + agent: BskyAgent, + gif: Gif, +) { + return queryClient.fetchQuery({ + staleTime: STALE.HOURS.ONE, + queryKey: RQKEY_GIF(gif.url), + queryFn: async () => { + return await resolveGif(agent, gif) + }, + }) +} diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index 770b0789ed..0969485064 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -7,9 +7,12 @@ import { } from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {postUriToRelativePath, toBskyAppUrl} from '#/lib/strings/url-helpers' import {purgeTemporaryImageFiles} from '#/state/gallery' +import {precacheResolveLinkQuery} from '#/state/queries/resolve-link' import * as Toast from '#/view/com/util/Toast' export interface ComposerOptsPostRef { @@ -58,8 +61,28 @@ const controlsContext = React.createContext({ export function Provider({children}: React.PropsWithChildren<{}>) { const {_} = useLingui() const [state, setState] = React.useState() + const queryClient = useQueryClient() const openComposer = useNonReactiveCallback((opts: ComposerOpts) => { + if (opts.quote) { + const path = postUriToRelativePath(opts.quote.uri) + if (path) { + const appUrl = toBskyAppUrl(path) + precacheResolveLinkQuery(queryClient, appUrl, { + type: 'record', + kind: 'post', + record: { + cid: opts.quote.cid, + uri: opts.quote.uri, + }, + meta: { + author: opts.quote.author, + indexedAt: opts.quote.indexedAt, + text: opts.quote.text, + }, + }) + } + } const author = opts.replyTo?.author || opts.quote?.author const isBlocked = Boolean( author && diff --git a/src/state/shell/index.tsx b/src/state/shell/index.tsx index 07909c0001..f61dc3c410 100644 --- a/src/state/shell/index.tsx +++ b/src/state/shell/index.tsx @@ -1,22 +1,22 @@ import React from 'react' -import {Provider as ShellLayoutProvder} from './shell-layout' + +import {Provider as ColorModeProvider} from './color-mode' import {Provider as DrawerOpenProvider} from './drawer-open' import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled' import {Provider as MinimalModeProvider} from './minimal-mode' -import {Provider as ColorModeProvider} from './color-mode' import {Provider as OnboardingProvider} from './onboarding' -import {Provider as ComposerProvider} from './composer' +import {Provider as ShellLayoutProvder} from './shell-layout' import {Provider as TickEveryMinuteProvider} from './tick-every-minute' +export {useSetThemePrefs, useThemePrefs} from './color-mode' +export {useComposerControls, useComposerState} from './composer' export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open' export { useIsDrawerSwipeDisabled, useSetDrawerSwipeDisabled, } from './drawer-swipe-disabled' export {useMinimalShellMode, useSetMinimalShellMode} from './minimal-mode' -export {useThemePrefs, useSetThemePrefs} from './color-mode' -export {useOnboardingState, useOnboardingDispatch} from './onboarding' -export {useComposerState, useComposerControls} from './composer' +export {useOnboardingDispatch, useOnboardingState} from './onboarding' export {useTickEveryMinute} from './tick-every-minute' export function Provider({children}: React.PropsWithChildren<{}>) { @@ -27,9 +27,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - - {children} - + {children} diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index a1c4e7656e..ecafea500d 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -46,19 +46,15 @@ import {RichText} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import * as apilib from '#/lib/api/index' import {until} from '#/lib/async/until' import {MAX_GRAPHEME_LENGTH} from '#/lib/constants' -import { - createGIFDescription, - parseAltFromGIFDescription, -} from '#/lib/gif-alt-text' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {LikelyType} from '#/lib/link-meta/link-meta' import {logEvent} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {insertMentionAt} from '#/lib/strings/mention-manip' @@ -87,8 +83,11 @@ import {useComposerControls} from '#/state/shell/composer' import {ComposerOpts} from '#/state/shell/composer' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {ComposerReplyTo} from '#/view/com/composer/ComposerReplyTo' -import {ExternalEmbed} from '#/view/com/composer/ExternalEmbed' -import {GifAltText} from '#/view/com/composer/GifAltText' +import { + ExternalEmbedGif, + ExternalEmbedLink, +} from '#/view/com/composer/ExternalEmbed' +import {GifAltTextDialog} from '#/view/com/composer/GifAltText' import {LabelsBtn} from '#/view/com/composer/labels/LabelsBtn' import {Gallery} from '#/view/com/composer/photos/Gallery' import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn' @@ -100,12 +99,11 @@ import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLa // due to linting false positives import {TextInput, TextInputRef} from '#/view/com/composer/text-input/TextInput' import {ThreadgateBtn} from '#/view/com/composer/threadgate/ThreadgateBtn' -import {useExternalLinkFetch} from '#/view/com/composer/useExternalLinkFetch' import {SelectVideoBtn} from '#/view/com/composer/videos/SelectVideoBtn' import {SubtitleDialogBtn} from '#/view/com/composer/videos/SubtitleDialog' import {VideoPreview} from '#/view/com/composer/videos/VideoPreview' import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress' -import {QuoteEmbed, QuoteX} from '#/view/com/util/post-embeds/QuoteEmbed' +import {LazyQuoteEmbed, QuoteX} from '#/view/com/util/post-embeds/QuoteEmbed' import {Text} from '#/view/com/util/text/Text' import * as Toast from '#/view/com/util/Toast' import {UserAvatar} from '#/view/com/util/UserAvatar' @@ -117,13 +115,15 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {createPortalGroup} from '#/components/Portal' import * as Prompt from '#/components/Prompt' import {Text as NewText} from '#/components/Typography' -import {composerReducer, createComposerState} from './state/composer' +import { + composerReducer, + createComposerState, + MAX_IMAGES, +} from './state/composer' import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video' const Portal = createPortalGroup() -const MAX_IMAGES = 4 - type CancelRef = { onPressCancel: () => void } @@ -135,7 +135,7 @@ export const ComposePost = ({ replyTo, onPost, quote: initQuote, - quoteCount, + quoteCount: initQuoteCount, mention: initMention, openEmojiPicker, text: initText, @@ -147,6 +147,7 @@ export const ComposePost = ({ }) => { const {currentAccount} = useSession() const agent = useAgent() + const queryClient = useQueryClient() const currentDid = currentAccount!.did const {data: currentProfile} = useProfileQuery({did: currentDid}) const {isModalActive} = useModals() @@ -183,9 +184,6 @@ export const ComposePost = ({ const graphemeLength = useMemo(() => { return shortenLinks(richtext).graphemeLength }, [richtext]) - const [quote, setQuote] = useState( - initQuote, - ) // TODO: Move more state here. const [composerState, dispatch] = useReducer( @@ -246,8 +244,6 @@ export const ComposePost = ({ const [publishOnUpload, setPublishOnUpload] = useState(false) - const {extLink, setExtLink} = useExternalLinkFetch({setQuote, setError}) - const [extGif, setExtGif] = useState() const [labels, setLabels] = useState([]) const [threadgateAllowUISettings, onChangeThreadgateAllowUISettings] = useState( @@ -255,10 +251,24 @@ export const ComposePost = ({ ) const [postgate, setPostgate] = useState(createPostgateRecord({post: ''})) + let quote: string | undefined + if (composerState.embed.quote) { + quote = composerState.embed.quote.uri + } let images = NO_IMAGES if (composerState.embed.media?.type === 'images') { images = composerState.embed.media.images } + let extGif: Gif | undefined + let extGifAlt: string | undefined + if (composerState.embed.media?.type === 'gif') { + extGif = composerState.embed.media.gif + extGifAlt = composerState.embed.media.alt + } + let extLink: string | undefined + if (composerState.embed.link) { + extLink = composerState.embed.link.uri + } const onClose = useCallback(() => { closeComposer() @@ -335,14 +345,9 @@ export const ComposePost = ({ } }, [onEscape, isModalActive]) - const onNewLink = useCallback( - (uri: string) => { - dispatch({type: 'embed_add_uri', uri}) - if (extLink != null) return - setExtLink({uri, isLoading: true}) - }, - [extLink, setExtLink], - ) + const onNewLink = useCallback((uri: string) => { + dispatch({type: 'embed_add_uri', uri}) + }, []) const onImageAdd = useCallback( (next: ComposerImage[]) => { @@ -371,14 +376,10 @@ export const ComposePost = ({ if (images.some(img => img.alt === '')) return true - if (extGif) { - if (!extLink?.meta?.description) return true + if (extGif && !extGifAlt) return true - const parsedAlt = parseAltFromGIFDescription(extLink.meta.description) - if (!parsedAlt.isPreferred) return true - } return false - }, [images, extLink, extGif, requireAltTextEnabled]) + }, [images, extGifAlt, extGif, requireAltTextEnabled]) const onPressPublish = React.useCallback( async (finishedUploading?: boolean) => { @@ -411,17 +412,13 @@ export const ComposePost = ({ setError(_(msg`Did you want to say anything?`)) return } - if (extLink?.isLoading) { - setError(_(msg`Please wait for your link card to finish loading`)) - return - } setIsProcessing(true) let postUri try { postUri = ( - await apilib.post(agent, { + await apilib.post(agent, queryClient, { composerState, // TODO: move more state here. rawText: richtext.text, replyTo: replyTo?.uri, @@ -449,13 +446,6 @@ export const ComposePost = ({ hasImages: images.length > 0, }) - if (extLink) { - setExtLink({ - ...extLink, - isLoading: true, - localThumb: undefined, - } as apilib.ExternalEmbedDraft) - } let err = cleanError(e.message) if (err.includes('not locate record')) { err = _( @@ -481,13 +471,13 @@ export const ComposePost = ({ emitPostCreated() } setLangPrefs.savePostLanguageToHistory() - if (quote) { + if (initQuote && initQuoteCount !== undefined) { // We want to wait for the quote count to update before we call `onPost`, which will refetch data - whenAppViewReady(agent, quote.uri, res => { + whenAppViewReady(agent, initQuote.uri, res => { const thread = res.data.thread if ( AppBskyFeedDefs.isThreadViewPost(thread) && - thread.post.quoteCount !== quoteCount + thread.post.quoteCount !== initQuoteCount ) { onPost?.(postUri) return true @@ -519,14 +509,15 @@ export const ComposePost = ({ onPost, postgate, quote, - quoteCount, + initQuote, + initQuoteCount, replyTo, richtext.text, - setExtLink, setLangPrefs, threadgateAllowUISettings, videoState.asset, videoState.status, + queryClient, ], ) @@ -549,11 +540,9 @@ export const ComposePost = ({ const canSelectImages = images.length < MAX_IMAGES && - !extLink && videoState.status === 'idle' && !videoState.video - const hasMedia = - images.length > 0 || Boolean(extLink) || Boolean(videoState.video) + const hasMedia = images.length > 0 || Boolean(videoState.video) const onEmojiButtonPress = useCallback(() => { openEmojiPicker?.(textInput.current?.getCursorPosition()) @@ -563,45 +552,13 @@ export const ComposePost = ({ textInput.current?.focus() }, []) - const onSelectGif = useCallback( - (gif: Gif) => { - dispatch({type: 'embed_add_gif', gif}) - setExtLink({ - uri: `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`, - isLoading: true, - meta: { - url: gif.media_formats.gif.url, - image: gif.media_formats.preview.url, - likelyType: LikelyType.HTML, - title: gif.content_description, - description: createGIFDescription(gif.content_description), - }, - }) - setExtGif(gif) - }, - [setExtLink], - ) + const onSelectGif = useCallback((gif: Gif) => { + dispatch({type: 'embed_add_gif', gif}) + }, []) - const handleChangeGifAltText = useCallback( - (altText: string) => { - dispatch({type: 'embed_update_gif', alt: altText}) - setExtLink(ext => - ext && ext.meta - ? { - ...ext, - meta: { - ...ext.meta, - description: createGIFDescription( - ext.meta.title ?? '', - altText, - ), - }, - } - : ext, - ) - }, - [setExtLink], - ) + const handleChangeGifAltText = useCallback((altText: string) => { + dispatch({type: 'embed_update_gif', alt: altText}) + }, []) const { scrollHandler, @@ -660,7 +617,7 @@ export const ComposePost = ({ {canPost ? (