From 92824b4b3a99e0609ef3778b0e97b37151ceac97 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 16 Jan 2026 16:33:12 +0200 Subject: [PATCH] infinite query, rename file to queries --- src/view/com/composer/Composer.tsx | 22 +++++----- src/view/com/composer/drafts/DraftsButton.tsx | 4 +- .../com/composer/drafts/DraftsListDialog.tsx | 13 ++++-- src/view/com/composer/drafts/state/api.ts | 8 ++-- .../drafts/state/{hooks.ts => queries.ts} | 40 +++++++++---------- 5 files changed, 45 insertions(+), 42 deletions(-) rename src/view/com/composer/drafts/state/{hooks.ts => queries.ts} (87%) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index aab7d4b756..2b1adfe425 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -130,12 +130,8 @@ import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' -import { - draftToComposerPosts, - threadgateToUISettings, - useLoadDraft, - useSaveDraft, -} from './drafts/state/hooks' +import {draftToComposerPosts} from './drafts/state/api' +import {useLoadDraft, useSaveDraftMutation} from './drafts/state/queries' import {type DraftSummary} from './drafts/state/schema' import {PostLanguageSelect} from './select-language/PostLanguageSelect' import { @@ -195,7 +191,8 @@ export const ComposePost = ({ const setLangPrefs = useLanguagePrefsApi() const textInput = useRef(null) const discardPromptControl = Prompt.usePromptControl() - const {mutateAsync: saveDraft, isPending: _isSavingDraft} = useSaveDraft() + const {mutateAsync: saveDraft, isPending: _isSavingDraft} = + useSaveDraftMutation() const loadDraft = useLoadDraft() const {closeAllDialogs} = useDialogStateControlContext() const {closeAllModals} = useModalControls() @@ -338,14 +335,14 @@ export const ComposePost = ({ // Convert server draft to composer posts const posts = draftToComposerPosts(draft, loadedMedia) - const threadgate = threadgateToUISettings(draft.threadgateAllow) // Dispatch restore action (this also sets draftId in state) composerDispatch({ type: 'restore_from_draft', draftId: draftSummary.id, posts, - threadgate, + threadgateAllow: draft.threadgateAllow, + postgateEmbeddingRules: draft.postgateEmbeddingRules, loadedMedia, }) }, @@ -402,8 +399,11 @@ export const ComposePost = ({ // Clear the composer (discard current content) const handleClearComposer = React.useCallback(() => { - composerDispatch({type: 'clear'}) - }, [composerDispatch]) + composerDispatch({ + type: 'clear', + initInteractionSettings: preferences?.postInteractionSettings, + }) + }, [composerDispatch, preferences?.postInteractionSettings]) const insets = useSafeAreaInsets() const viewStyles = useMemo( diff --git a/src/view/com/composer/drafts/DraftsButton.tsx b/src/view/com/composer/drafts/DraftsButton.tsx index efe411184a..3e5425878b 100644 --- a/src/view/com/composer/drafts/DraftsButton.tsx +++ b/src/view/com/composer/drafts/DraftsButton.tsx @@ -6,7 +6,7 @@ import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import {DraftsListDialog} from './DraftsListDialog' -import {useSaveDraft} from './state/hooks' +import {useSaveDraftMutation} from './state/queries' import {type DraftSummary} from './state/schema' export function DraftsButton({ @@ -27,7 +27,7 @@ export function DraftsButton({ const {_} = useLingui() const draftsDialogControl = Dialog.useDialogControl() const savePromptControl = Prompt.usePromptControl() - const {isPending: isSaving} = useSaveDraft() + const {isPending: isSaving} = useSaveDraftMutation() const handlePress = () => { if (isEmpty || !isDirty) { diff --git a/src/view/com/composer/drafts/DraftsListDialog.tsx b/src/view/com/composer/drafts/DraftsListDialog.tsx index 3fb943054d..2ecb8b52a2 100644 --- a/src/view/com/composer/drafts/DraftsListDialog.tsx +++ b/src/view/com/composer/drafts/DraftsListDialog.tsx @@ -11,7 +11,7 @@ import * as Dialog from '#/components/Dialog' import {PageX_Stroke2_Corner0_Rounded_Large as PageXIcon} from '#/components/icons/PageX' import {Loader} from '#/components/Loader' import {DraftItem} from './DraftItem' -import {useDeleteDraft, useDrafts} from './state/hooks' +import {useDeleteDraftMutation, useDraftsQuery} from './state/queries' import {type DraftSummary} from './state/schema' export function DraftsListDialog({ @@ -23,8 +23,13 @@ export function DraftsListDialog({ }) { const {_} = useLingui() const t = useTheme() - const {data: drafts, isLoading} = useDrafts() - const {mutate: deleteDraft} = useDeleteDraft() + const {data, isLoading} = useDraftsQuery() + const {mutate: deleteDraft} = useDeleteDraftMutation() + + const drafts = useMemo( + () => data?.pages.flatMap(page => page.drafts) ?? [], + [data], + ) const handleSelectDraft = useCallback( (summary: DraftSummary) => { @@ -105,7 +110,7 @@ export function DraftsListDialog({ {isNative && header} item.id} ListHeaderComponent={web(header)} diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index 6b602f0ebc..cd42afbb4a 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -1,7 +1,7 @@ /** * Type converters for Draft API - convert between ComposerState and server Draft types. */ -import {type AppBskyDraftDefs} from '@atproto/api' +import {type AppBskyDraftDefs, RichText} from '@atproto/api' import {nanoid} from 'nanoid/non-secure' import {type ComposerImage} from '#/state/gallery' @@ -341,11 +341,9 @@ export function draftToComposerPosts( draft: AppBskyDraftDefs.Draft, loadedMedia: Map, ): PostDraft[] { - // Import these dynamically to avoid circular dependencies - const {RichText} = require('@atproto/api') - return draft.posts.map((post, index) => { const richtext = new RichText({text: post.text || ''}) + richtext.detectFacetsWithoutResolution() const embed: EmbedDraft = { quote: undefined, @@ -434,7 +432,7 @@ export function draftToComposerPosts( // Parse labels const labels: string[] = [] if (post.labels && 'values' in post.labels) { - for (const val of (post.labels as {values: {val: string}[]}).values) { + for (const val of post.labels.values) { labels.push(val.val) } } diff --git a/src/view/com/composer/drafts/state/hooks.ts b/src/view/com/composer/drafts/state/queries.ts similarity index 87% rename from src/view/com/composer/drafts/state/hooks.ts rename to src/view/com/composer/drafts/state/queries.ts index 6c12d91c94..af21ae38fe 100644 --- a/src/view/com/composer/drafts/state/hooks.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -1,18 +1,16 @@ import {useCallback} from 'react' import {AppBskyDraftCreateDraft, type AppBskyDraftDefs} from '@atproto/api' -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' +import { + useInfiniteQuery, + useMutation, + useQueryClient, +} from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' import {useAgent} from '#/state/session' import {type ComposerState} from '#/view/com/composer/state/composer' -import { - composerStateToDraft, - draftToComposerPosts, - draftViewToSummary, - threadgateToUISettings, -} from './api' +import {composerStateToDraft, draftViewToSummary} from './api' import {logger} from './logger' -import {type DraftSummary} from './schema' import * as storage from './storage' const DRAFTS_QUERY_KEY = ['drafts'] @@ -20,19 +18,24 @@ const DRAFTS_QUERY_KEY = ['drafts'] /** * Hook to list all drafts for the current account */ -export function useDrafts() { +export function useDraftsQuery() { const agent = useAgent() - return useQuery({ + return useInfiniteQuery({ queryKey: DRAFTS_QUERY_KEY, - queryFn: async () => { + queryFn: async ({pageParam}) => { // Ensure media cache is populated before checking which media exists await storage.ensureMediaCachePopulated() - const res = await agent.app.bsky.draft.getDrafts({}) - return res.data.drafts.map(view => - draftViewToSummary(view, path => storage.mediaExists(path)), - ) + const res = await agent.app.bsky.draft.getDrafts({cursor: pageParam}) + return { + cursor: res.data.cursor, + drafts: res.data.drafts.map(view => + draftViewToSummary(view, path => storage.mediaExists(path)), + ), + } }, + initialPageParam: undefined as string | undefined, + getNextPageParam: page => page.cursor, }) } @@ -99,7 +102,7 @@ export function useLoadDraft() { /** * Hook to save a draft */ -export function useSaveDraft() { +export function useSaveDraftMutation() { const agent = useAgent() const queryClient = useQueryClient() @@ -157,7 +160,7 @@ export function useSaveDraft() { /** * Hook to delete a draft */ -export function useDeleteDraft() { +export function useDeleteDraftMutation() { const agent = useAgent() const queryClient = useQueryClient() @@ -191,6 +194,3 @@ export function useDeleteDraft() { }, }) } - -// Re-export utilities for use in composer -export {draftToComposerPosts, threadgateToUISettings}