diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/cache/post-shadow.ts | 2 | ||||
-rw-r--r-- | src/state/queries/pinned-post.ts | 87 | ||||
-rw-r--r-- | src/state/queries/post-feed.ts | 1 | ||||
-rw-r--r-- | src/state/queries/profile.ts | 3 | ||||
-rw-r--r-- | src/state/queries/suggested-follows.ts | 11 |
5 files changed, 98 insertions, 6 deletions
diff --git a/src/state/cache/post-shadow.ts b/src/state/cache/post-shadow.ts index 65300a8ef..b456a76d9 100644 --- a/src/state/cache/post-shadow.ts +++ b/src/state/cache/post-shadow.ts @@ -21,6 +21,7 @@ export interface PostShadow { repostUri: string | undefined isDeleted: boolean embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined + pinned: boolean } export const POST_TOMBSTONE = Symbol('PostTombstone') @@ -113,6 +114,7 @@ function mergeShadow( ...(post.viewer || {}), like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like, repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost, + pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned, }, }) } diff --git a/src/state/queries/pinned-post.ts b/src/state/queries/pinned-post.ts new file mode 100644 index 000000000..7e2c8ee79 --- /dev/null +++ b/src/state/queries/pinned-post.ts @@ -0,0 +1,87 @@ +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation, useQueryClient} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' +import * as Toast from '#/view/com/util/Toast' +import {updatePostShadow} from '../cache/post-shadow' +import {useAgent, useSession} from '../session' +import {useProfileUpdateMutation} from './profile' + +export function usePinnedPostMutation() { + const {_} = useLingui() + const {currentAccount} = useSession() + const agent = useAgent() + const queryClient = useQueryClient() + const {mutateAsync: profileUpdateMutate} = useProfileUpdateMutation() + + return useMutation({ + mutationFn: async ({ + postUri, + postCid, + action, + }: { + postUri: string + postCid: string + action: 'pin' | 'unpin' + }) => { + const pinCurrentPost = action === 'pin' + let prevPinnedPost: string | undefined + try { + updatePostShadow(queryClient, postUri, {pinned: pinCurrentPost}) + + // get the currently pinned post so we can optimistically remove the pin from it + if (!currentAccount) throw new Error('Not logged in') + const {data: profile} = await agent.getProfile({ + actor: currentAccount.did, + }) + prevPinnedPost = profile.pinnedPost?.uri + if (prevPinnedPost && prevPinnedPost !== postUri) { + updatePostShadow(queryClient, prevPinnedPost, {pinned: false}) + } + + await profileUpdateMutate({ + profile, + updates: existing => { + existing.pinnedPost = pinCurrentPost + ? {uri: postUri, cid: postCid} + : undefined + return existing + }, + checkCommitted: res => + pinCurrentPost + ? res.data.pinnedPost?.uri === postUri + : !res.data.pinnedPost, + }) + + if (pinCurrentPost) { + Toast.show(_(msg`Post pinned`)) + } else { + Toast.show(_(msg`Post unpinned`)) + } + + queryClient.invalidateQueries({ + queryKey: FEED_RQKEY( + `author|${currentAccount.did}|posts_and_author_threads`, + ), + }) + queryClient.invalidateQueries({ + queryKey: FEED_RQKEY( + `author|${currentAccount.did}|posts_with_replies`, + ), + }) + } catch (e: any) { + Toast.show(_(msg`Failed to pin post`)) + logger.error('Failed to pin post', {message: String(e)}) + // revert optimistic update + updatePostShadow(queryClient, postUri, { + pinned: !pinCurrentPost, + }) + if (prevPinnedPost && prevPinnedPost !== postUri) { + updatePostShadow(queryClient, prevPinnedPost, {pinned: true}) + } + } + }, + }) +} diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 07c5da81b..1785eb445 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -91,6 +91,7 @@ export interface FeedPostSlice { feedContext: string | undefined reason?: | AppBskyFeedDefs.ReasonRepost + | AppBskyFeedDefs.ReasonPin | ReasonFeedSource | {[k: string]: unknown; $type: string} } diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 78a142eea..3059d9efe 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -159,6 +159,9 @@ export function useProfileUpdateMutation() { } else { existing.displayName = updates.displayName existing.description = updates.description + if ('pinnedPost' in updates) { + existing.pinnedPost = updates.pinnedPost + } } if (newUserAvatarPromise) { const res = await newUserAvatarPromise diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 5ae831704..07e16946e 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -105,17 +105,16 @@ export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) { export function useSuggestedFollowsByActorQuery({did}: {did: string}) { const agent = useAgent() - return useQuery<AppBskyGraphGetSuggestedFollowsByActor.OutputSchema, Error>({ + return useQuery({ queryKey: suggestedFollowsByActorQueryKey(did), queryFn: async () => { const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({ actor: did, }) - const data = res.data.isFallback ? {suggestions: []} : res.data - data.suggestions = data.suggestions.filter(profile => { - return !profile.viewer?.following - }) - return data + const suggestions = res.data.isFallback + ? [] + : res.data.suggestions.filter(profile => !profile.viewer?.following) + return {suggestions} }, }) } |