diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index a869596335..751c97122b 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -3,7 +3,6 @@ import { AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, - AppBskyFeedThreadgate, BskyAgent, ComAtprotoLabelDefs, ComAtprotoRepoUploadBlob, @@ -12,7 +11,11 @@ import { import {AtUri} from '@atproto/api' import {logger} from '#/logger' -import {ThreadgateAllowUISetting} from '#/state/queries/threadgate' +import { + createThreadgate as upsertThreadgate, + ThreadgateAllowUISetting, + threadgateAllowUISettingToAllowType, +} from '#/state/queries/threadgate' import {isNetworkError} from 'lib/strings/errors' import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip' import {isNative, isWeb} from 'platform/detection' @@ -279,38 +282,8 @@ export async function createThreadgate( postUri: string, threadgate: ThreadgateAllowUISetting[], ) { - let allow: ( - | AppBskyFeedThreadgate.MentionRule - | AppBskyFeedThreadgate.FollowingRule - | AppBskyFeedThreadgate.ListRule - )[] = [] - if (!threadgate.find(v => v.type === 'nobody')) { - for (const rule of threadgate) { - if (rule.type === 'mention') { - allow.push({$type: 'app.bsky.feed.threadgate#mentionRule'}) - } else if (rule.type === 'following') { - allow.push({$type: 'app.bsky.feed.threadgate#followingRule'}) - } else if (rule.type === 'list') { - allow.push({ - $type: 'app.bsky.feed.threadgate#listRule', - list: rule.list, - }) - } - } - } - - const postUrip = new AtUri(postUri) - await agent.api.com.atproto.repo.putRecord({ - repo: agent.session!.did, - collection: 'app.bsky.feed.threadgate', - rkey: postUrip.rkey, - record: { - $type: 'app.bsky.feed.threadgate', - post: postUri, - allow, - createdAt: new Date().toISOString(), - }, - }) + const allow = threadgateAllowUISettingToAllowType(threadgate) + return upsertThreadgate({agent, postUri, threadgate: {allow}}) } // helpers diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index b581ae3342..71ebc24797 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -1,6 +1,8 @@ import {AppBskyFeedThreadgate, AtUri, BskyAgent} from '@atproto/api' -import {useQuery} from '@tanstack/react-query' +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' +import {networkRetry} from '#/lib/async/retry' +import {mergeThreadgateRecords} from '#/state/queries/threadgate/util' import {useAgent} from '#/state/session' export * from '#/state/queries/threadgate/types' @@ -45,7 +47,7 @@ export function useThreadgateRecordQuery({ }) } -export function createThreadgate({ +export async function createThreadgate({ agent, postUri, threadgate, @@ -54,18 +56,72 @@ export function createThreadgate({ postUri: string threadgate: Partial }) { - const urip = new AtUri(postUri) - const record = { - ...threadgate, - post: postUri, - createdAt: new Date().toISOString(), - } + const postUrip = new AtUri(postUri) - return agent.api.app.bsky.feed.threadgate.create( - { - repo: urip.host, - rkey: urip.rkey, - }, - record, + const {data} = await networkRetry(2, () => + agent.api.com.atproto.repo.getRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + }), ) + + if (data.value && AppBskyFeedThreadgate.isRecord(data.value)) { + // has existing, merge + const prev = data.value + const merged = mergeThreadgateRecords(prev, threadgate) + + await networkRetry(2, () => + agent.api.com.atproto.repo.putRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + record: merged, + }), + ) + } else { + // no existing, create new + const record: AppBskyFeedThreadgate.Record = { + $type: 'app.bsky.feed.threadgate', + post: postUri, + allow: threadgate.allow || [], + hiddenReplies: threadgate.hiddenReplies || [], + createdAt: new Date().toISOString(), + } + + await networkRetry(2, () => + agent.api.com.atproto.repo.putRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + record, + }), + ) + } +} + +export function useCreateThreadgateMutation() { + const agent = useAgent() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + postUri, + threadgate, + }: { + postUri: string + threadgate: Partial + }) => { + return createThreadgate({ + agent, + postUri, + threadgate, + }) + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: [threadgateRecordQueryKeyRoot], + }) + }, + }) } diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index b639372c52..028907d98a 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -31,8 +31,8 @@ import { usePostDeleteMutation, useThreadMuteMutationQueue, } from '#/state/queries/post' -import {createThreadgate} from '#/state/queries/threadgate' -import {useAgent, useSession} from '#/state/session' +import {useCreateThreadgateMutation} from '#/state/queries/threadgate' +import {useSession} from '#/state/session' import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' import {toShareUrl} from 'lib/strings/url-helpers' @@ -88,7 +88,6 @@ let PostDropdownBtn = ({ rootPostUri?: string }): React.ReactNode => { const {hasSession, currentAccount} = useSession() - const agent = useAgent() const theme = useTheme() const alf = useAlf() const {gtMobile} = useBreakpoints() @@ -108,6 +107,8 @@ let PostDropdownBtn = ({ const loggedOutWarningPromptControl = useDialogControl() const embedPostControl = useDialogControl() const sendViaChatControl = useDialogControl() + const {mutateAsync: createThreadgate} = useCreateThreadgateMutation() + const postUri = post.uri const postCid = post.cid const postAuthor = post.author @@ -404,7 +405,6 @@ let PostDropdownBtn = ({ label={_(msg`Hide reply`)} onPress={() => { createThreadgate({ - agent, postUri: rootPostUri, threadgate: { hiddenReplies: [postUri],