Pinned posts (#5055)
* add to dropdown menu * use normal profile mutation hook * add pin as reason * request pins * shadow update * rm logs * get prev pinned from getProfile * fix toasts * invalidate after appview ready * don't mutate params * rm log * use checkCommited rather than manual whenAppViewReady * move to mutation * even more optimistic optimistic update * allow pins in `posts_and_author_threads` * update @atproto/api * add reasonPin type * fix strange type error in unrelated query * another missing type
This commit is contained in:
Vendored
+2
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -91,6 +91,7 @@ export interface FeedPostSlice {
|
||||
feedContext: string | undefined
|
||||
reason?:
|
||||
| AppBskyFeedDefs.ReasonRepost
|
||||
| AppBskyFeedDefs.ReasonPin
|
||||
| ReasonFeedSource
|
||||
| {[k: string]: unknown; $type: string}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user