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'
|
} from 'react-native'
|
||||||
import * as Clipboard from 'expo-clipboard'
|
import * as Clipboard from 'expo-clipboard'
|
||||||
import {
|
import {
|
||||||
|
AppBskyEmbedRecord,
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AtUri,
|
AtUri,
|
||||||
@@ -88,6 +89,7 @@ let PostDropdownBtn = ({
|
|||||||
timestamp: string
|
timestamp: string
|
||||||
rootPostUri?: string
|
rootPostUri?: string
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
|
console.log(record)
|
||||||
const {hasSession, currentAccount} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const alf = useAlf()
|
const alf = useAlf()
|
||||||
@@ -114,6 +116,20 @@ let PostDropdownBtn = ({
|
|||||||
const postUri = post.uri
|
const postUri = post.uri
|
||||||
const postCid = post.cid
|
const postCid = post.cid
|
||||||
const postAuthor = post.author
|
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 rootUri = record.reply?.root?.uri || postUri
|
||||||
const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue(
|
const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue(
|
||||||
@@ -433,6 +449,16 @@ let PostDropdownBtn = ({
|
|||||||
<Menu.ItemIcon icon={EyeSlash} position="right" />
|
<Menu.ItemIcon icon={EyeSlash} position="right" />
|
||||||
</Menu.Item>
|
</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>
|
</Menu.Group>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user