Use post shadow to swap detached quotes
This commit is contained in:
Vendored
+10
-1
@@ -1,5 +1,9 @@
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
} from '@atproto/api'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
@@ -15,6 +19,7 @@ export interface PostShadow {
|
||||
likeUri: string | undefined
|
||||
repostUri: string | undefined
|
||||
isDeleted: boolean
|
||||
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
||||
}
|
||||
|
||||
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
||||
@@ -86,6 +91,10 @@ function mergeShadow(
|
||||
repostCount = Math.max(0, repostCount)
|
||||
}
|
||||
|
||||
if (shadow.embed !== undefined && post.embed) {
|
||||
post.embed = shadow.embed
|
||||
}
|
||||
|
||||
return castAsShadow({
|
||||
...post,
|
||||
likeCount: likeCount,
|
||||
|
||||
@@ -73,6 +73,30 @@ export function useGetPost() {
|
||||
)
|
||||
}
|
||||
|
||||
export function useGetPosts() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
return useCallback(
|
||||
async ({uris}: {uris: string[]}) => {
|
||||
return queryClient.fetchQuery({
|
||||
queryKey: RQKEY(uris.join(',') || ''),
|
||||
async queryFn() {
|
||||
const res = await agent.getPosts({
|
||||
uris,
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
return res.data.posts
|
||||
} else {
|
||||
throw new Error('useGetPosts failed')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
[queryClient, agent],
|
||||
)
|
||||
}
|
||||
|
||||
export function usePostLikeMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
logContext: LogEvents['post:like']['logContext'] &
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {AppBskyFeedPostgate, AtUri, BskyAgent} from '@atproto/api'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {useGetPosts} from '#/state/queries/post'
|
||||
import {
|
||||
createEmbed,
|
||||
createPostgateRecord,
|
||||
mergePostgateRecords,
|
||||
POSTGATE_COLLECTION,
|
||||
@@ -26,6 +29,7 @@ export async function getPostgateRecord({
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO don't retry on 404
|
||||
const {data} = await networkRetry(2, () =>
|
||||
agent.api.com.atproto.repo.getRecord({
|
||||
repo: urip.host,
|
||||
@@ -100,6 +104,8 @@ export async function upsertPostgate(
|
||||
|
||||
export function useToggleQuoteDetachmentMutation() {
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const getPosts = useGetPosts()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -134,5 +140,16 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
}
|
||||
})
|
||||
},
|
||||
async onSuccess(_data, {postUri, quotedUri, action}) {
|
||||
const [post, quotedPost] = await getPosts({uris: [postUri, quotedUri]})
|
||||
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
embed: createEmbed({
|
||||
post,
|
||||
embeddedPost: quotedPost,
|
||||
detached: action === 'detach',
|
||||
}),
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import {AppBskyFeedPostgate} from '@atproto/api'
|
||||
import {
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPostgate,
|
||||
} from '@atproto/api'
|
||||
import {ViewRemoved} from '@atproto/api/dist/client/types/app/bsky/embed/record'
|
||||
|
||||
export const POSTGATE_COLLECTION = 'app.bsky.feed.postgate'
|
||||
@@ -42,3 +47,60 @@ export function createEmbedViewRemovedRecord({uri}: {uri: string}) {
|
||||
record,
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmbed({
|
||||
post,
|
||||
embeddedPost,
|
||||
detached,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
embeddedPost: AppBskyFeedDefs.PostView
|
||||
detached: boolean
|
||||
}) {
|
||||
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||
if (detached) return createEmbedViewRemovedRecord({uri: post.uri})
|
||||
return createEmbedRecordView({post: embeddedPost})
|
||||
} else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||
if (detached)
|
||||
return {
|
||||
...post.embed,
|
||||
record: createEmbedViewRemovedRecord({uri: post.uri}),
|
||||
}
|
||||
return createEmbedRecordWithmediaView({post, embeddedPost})
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmbedRecordView({
|
||||
post,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
}): AppBskyEmbedRecord.View {
|
||||
return {
|
||||
$type: 'app.bsky.embed.record#view',
|
||||
record: {
|
||||
$type: 'app.bsky.embed.record#viewRecord',
|
||||
...post,
|
||||
value: post.record,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmbedRecordWithmediaView({
|
||||
post,
|
||||
embeddedPost,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
embeddedPost: AppBskyFeedDefs.PostView
|
||||
}): AppBskyEmbedRecordWithMedia.View | undefined {
|
||||
if (!AppBskyEmbedRecordWithMedia.isView(post.embed)) return
|
||||
return {
|
||||
...(post.embed || {}),
|
||||
record: {
|
||||
record: {
|
||||
$type: 'app.bsky.embed.record#viewRecord',
|
||||
...embeddedPost,
|
||||
value: embeddedPost.record,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import {
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
usePostDeleteMutation,
|
||||
useThreadMuteMutationQueue,
|
||||
} from '#/state/queries/post'
|
||||
import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate'
|
||||
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
|
||||
import {useThreadgateRecordQuery} from '#/state/queries/threadgate'
|
||||
import {useSession} from '#/state/session'
|
||||
@@ -89,7 +91,6 @@ let PostDropdownBtn = ({
|
||||
timestamp: string
|
||||
rootPostUri?: string
|
||||
}): React.ReactNode => {
|
||||
console.log(record)
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const theme = useTheme()
|
||||
const alf = useAlf()
|
||||
@@ -118,18 +119,38 @@ let PostDropdownBtn = ({
|
||||
const postAuthor = post.author
|
||||
const quoteEmbed = React.useMemo(() => {
|
||||
if (!currentAccount) return
|
||||
const quoteEmbed =
|
||||
record.embed && AppBskyEmbedRecord.isMain(record.embed)
|
||||
? record.embed
|
||||
: undefined
|
||||
if (!quoteEmbed) return
|
||||
const urip = new AtUri(quoteEmbed.record.uri)
|
||||
if (urip.collection !== 'app.bsky.feed.post') return
|
||||
return {
|
||||
uri: quoteEmbed.record.uri,
|
||||
isOwnedByViewer: urip.host === currentAccount.did,
|
||||
if (!post.embed) return
|
||||
|
||||
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||
const isPost = AppBskyEmbedRecord.isViewRecord(post.embed.record)
|
||||
const isDetachedPost = AppBskyEmbedRecord.isViewRemoved(post.embed.record)
|
||||
|
||||
if (isPost || isDetachedPost) {
|
||||
const urip = new AtUri(post.embed.record.uri)
|
||||
|
||||
return {
|
||||
uri: urip.toString(),
|
||||
isOwnedByViewer: urip.host === currentAccount.did,
|
||||
isDetached: isDetachedPost,
|
||||
}
|
||||
}
|
||||
} else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||
const isPost = AppBskyEmbedRecord.isViewRecord(post.embed.record.record)
|
||||
const isDetachedPost = AppBskyEmbedRecord.isViewRemoved(
|
||||
post.embed.record.record,
|
||||
)
|
||||
|
||||
if (isPost || isDetachedPost) {
|
||||
const urip = new AtUri(post.embed.record.record.uri)
|
||||
|
||||
return {
|
||||
uri: urip.toString(),
|
||||
isOwnedByViewer: urip.host === currentAccount.did,
|
||||
isDetached: isDetachedPost,
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [record, currentAccount])
|
||||
}, [post, currentAccount])
|
||||
|
||||
const rootUri = record.reply?.root?.uri || postUri
|
||||
const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue(
|
||||
@@ -144,6 +165,9 @@ let PostDropdownBtn = ({
|
||||
})
|
||||
const isReplyHiddenByThreadgate = threadgate?.hiddenReplies?.includes(postUri)
|
||||
|
||||
const {mutateAsync: toggleQuoteDetachment} =
|
||||
useToggleQuoteDetachmentMutation()
|
||||
|
||||
const href = React.useMemo(() => {
|
||||
const urip = new AtUri(postUri)
|
||||
return makeProfileLink(postAuthor, 'post', urip.rkey)
|
||||
@@ -273,6 +297,7 @@ let PostDropdownBtn = ({
|
||||
const onToggleReplyVisibility = React.useCallback(() => {
|
||||
if (!rootPostUri) return
|
||||
|
||||
// TODO handle failure
|
||||
toggleReplyVisibility({
|
||||
postUri: rootPostUri,
|
||||
replyUri: postUri,
|
||||
@@ -280,6 +305,17 @@ let PostDropdownBtn = ({
|
||||
})
|
||||
}, [isReplyHiddenByThreadgate, rootPostUri, postUri, toggleReplyVisibility])
|
||||
|
||||
const onToggleQuotePostAttachment = React.useCallback(() => {
|
||||
if (!quoteEmbed) return
|
||||
|
||||
// TODO handle failure
|
||||
toggleQuoteDetachment({
|
||||
postUri,
|
||||
quotedUri: quoteEmbed.uri,
|
||||
action: quoteEmbed.isDetached ? 'reattach' : 'detach',
|
||||
})
|
||||
}, [quoteEmbed, postUri, toggleQuoteDetachment])
|
||||
|
||||
const canEmbed = isWeb && gtMobile && !hideInPWI
|
||||
|
||||
return (
|
||||
@@ -454,7 +490,7 @@ let PostDropdownBtn = ({
|
||||
<Menu.Item
|
||||
testID="postDropdownHideBtn"
|
||||
label={_(msg`Detach quote`)}
|
||||
onPress={onToggleReplyVisibility}>
|
||||
onPress={onToggleQuotePostAttachment}>
|
||||
<Menu.ItemText>{_(msg`Detach quote`)}</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={EyeSlash} position="right" />
|
||||
</Menu.Item>
|
||||
|
||||
Reference in New Issue
Block a user