Add postgate queries, begin integration
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import {AppBskyFeedPostgate, AtUri, BskyAgent} from '@atproto/api'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {
|
||||
createPostgateRecord,
|
||||
mergePostgateRecords,
|
||||
POSTGATE_COLLECTION,
|
||||
} from '#/state/queries/postgate/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
|
||||
export async function getPostgateRecord({
|
||||
agent,
|
||||
postUri,
|
||||
}: {
|
||||
agent: BskyAgent
|
||||
postUri: string
|
||||
}): Promise<AppBskyFeedPostgate.Record | undefined> {
|
||||
const urip = new AtUri(postUri)
|
||||
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
})
|
||||
urip.host = res.data.did
|
||||
}
|
||||
|
||||
try {
|
||||
const {data} = await networkRetry(2, () =>
|
||||
agent.api.com.atproto.repo.getRecord({
|
||||
repo: urip.host,
|
||||
collection: POSTGATE_COLLECTION,
|
||||
rkey: urip.rkey,
|
||||
}),
|
||||
)
|
||||
|
||||
if (data.value && AppBskyFeedPostgate.isRecord(data.value)) {
|
||||
return data.value
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.message.includes(`Could not locate record:`)) {
|
||||
return undefined
|
||||
} else {
|
||||
throw new Error(`Failed to get postgate record`, {cause: e})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function writePostgateRecord({
|
||||
agent,
|
||||
postUri,
|
||||
postgate,
|
||||
}: {
|
||||
agent: BskyAgent
|
||||
postUri: string
|
||||
postgate: AppBskyFeedPostgate.Record
|
||||
}) {
|
||||
const postUrip = new AtUri(postUri)
|
||||
const record = createPostgateRecord({
|
||||
post: postUri,
|
||||
detachedQuotes: postgate.detachedQuotes,
|
||||
})
|
||||
|
||||
await networkRetry(2, () =>
|
||||
agent.api.com.atproto.repo.putRecord({
|
||||
repo: agent.session!.did,
|
||||
collection: POSTGATE_COLLECTION,
|
||||
rkey: postUrip.rkey,
|
||||
record,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export async function upsertPostgate(
|
||||
{
|
||||
agent,
|
||||
postUri,
|
||||
}: {
|
||||
agent: BskyAgent
|
||||
postUri: string
|
||||
},
|
||||
callback: (
|
||||
postgate: AppBskyFeedPostgate.Record | undefined,
|
||||
) => Promise<AppBskyFeedPostgate.Record | undefined>,
|
||||
) {
|
||||
const prev = await getPostgateRecord({
|
||||
agent,
|
||||
postUri,
|
||||
})
|
||||
const next = await callback(prev)
|
||||
if (!next) return
|
||||
await writePostgateRecord({
|
||||
agent,
|
||||
postUri,
|
||||
postgate: next,
|
||||
})
|
||||
}
|
||||
|
||||
export function useToggleQuoteDetachmentMutation() {
|
||||
const agent = useAgent()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
postUri,
|
||||
quotedUri,
|
||||
action,
|
||||
}: {
|
||||
postUri: string
|
||||
quotedUri: string
|
||||
action: 'detach' | 'reattach'
|
||||
}) => {
|
||||
await upsertPostgate({agent, postUri: quotedUri}, async prev => {
|
||||
if (prev) {
|
||||
if (action === 'detach') {
|
||||
return mergePostgateRecords(prev, {
|
||||
detachedQuotes: [postUri],
|
||||
})
|
||||
} else if (action === 'reattach') {
|
||||
return {
|
||||
...prev,
|
||||
detachedQuotes:
|
||||
prev.detachedQuotes?.filter(uri => uri !== postUri) || [],
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (action === 'detach') {
|
||||
return createPostgateRecord({
|
||||
post: quotedUri,
|
||||
detachedQuotes: [postUri],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {AppBskyFeedPostgate} from '@atproto/api'
|
||||
import {ViewRemoved} from '@atproto/api/dist/client/types/app/bsky/embed/record'
|
||||
|
||||
export const POSTGATE_COLLECTION = 'app.bsky.feed.postgate'
|
||||
|
||||
export function createPostgateRecord(
|
||||
postgate: Partial<AppBskyFeedPostgate.Record>,
|
||||
): AppBskyFeedPostgate.Record {
|
||||
if (!postgate.post) {
|
||||
throw new Error(`Cannot create a postgate record without a post URI`)
|
||||
}
|
||||
|
||||
return {
|
||||
$type: POSTGATE_COLLECTION,
|
||||
createdAt: new Date().toISOString(),
|
||||
post: postgate.post,
|
||||
detachedQuotes: postgate.detachedQuotes || [],
|
||||
}
|
||||
}
|
||||
|
||||
export function mergePostgateRecords(
|
||||
prev: AppBskyFeedPostgate.Record,
|
||||
next: Partial<AppBskyFeedPostgate.Record>,
|
||||
) {
|
||||
const detachedQuotes = Array.from(
|
||||
new Set([...(prev.detachedQuotes || []), ...(next.detachedQuotes || [])]),
|
||||
)
|
||||
return createPostgateRecord({
|
||||
post: prev.post,
|
||||
detachedQuotes,
|
||||
})
|
||||
}
|
||||
|
||||
export function createEmbedViewRemovedRecord({uri}: {uri: string}) {
|
||||
const record: ViewRemoved = {
|
||||
$type: 'app.bsky.embed.record#viewRemoved',
|
||||
uri,
|
||||
removed: true,
|
||||
}
|
||||
return {
|
||||
$type: 'app.bsky.embed.record#view',
|
||||
record,
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from 'react-native'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import {
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
@@ -88,6 +89,7 @@ let PostDropdownBtn = ({
|
||||
timestamp: string
|
||||
rootPostUri?: string
|
||||
}): React.ReactNode => {
|
||||
console.log(record)
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const theme = useTheme()
|
||||
const alf = useAlf()
|
||||
@@ -114,6 +116,20 @@ let PostDropdownBtn = ({
|
||||
const postUri = post.uri
|
||||
const postCid = post.cid
|
||||
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,
|
||||
}
|
||||
}, [record, currentAccount])
|
||||
|
||||
const rootUri = record.reply?.root?.uri || postUri
|
||||
const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue(
|
||||
@@ -433,6 +449,16 @@ let PostDropdownBtn = ({
|
||||
<Menu.ItemIcon icon={EyeSlash} position="right" />
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{quoteEmbed && quoteEmbed.isOwnedByViewer && (
|
||||
<Menu.Item
|
||||
testID="postDropdownHideBtn"
|
||||
label={_(msg`Detach quote`)}
|
||||
onPress={onToggleReplyVisibility}>
|
||||
<Menu.ItemText>{_(msg`Detach quote`)}</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={EyeSlash} position="right" />
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.Group>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user