migrate runtime call sites to lex clients and sdk actions
Phase 3 tasks 3-7 (parallel wave): composer/post pipeline on pdsClient/appviewClient with structural+instance blob guards and a golden CID fixture test; chat Convo/EventBus/queries on the dedicated chat client; preferences sugar to SDK actions on the PDS client; remaining state/queries producers (usePostThread unspecced flip, video scoped-token clients, notifications, starter packs, lists) to client.call; UI runtime sweep (AtUri from @atproto/syntax, moderation fns from @bsky.app/sdk/moderation, SDK RichText, ozone reason tokens, guard rewrites via #/types/bsky). Intermediate checkpoint (hooks skipped): ~114 typecheck errors remain in cross-boundary consumer files, resolved by the type-only codemod next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+167
-142
@@ -1,23 +1,15 @@
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyEmbedRecord,
|
||||
type ChatBskyActorDefs,
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoGetLog,
|
||||
type ChatBskyConvoSendMessage,
|
||||
type ChatBskyEmbedJoinLink,
|
||||
type ChatBskyGroupDefs,
|
||||
} from '@atproto/api'
|
||||
import {XRPCError} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {type DatetimeString, type DidString} from '@atproto/syntax'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {
|
||||
isErrorMaybeAppPasswordPermissions,
|
||||
isNetworkError,
|
||||
} from '#/lib/strings/errors'
|
||||
import {getErrorStatus, isXrpcError} from '#/lib/xrpc-error'
|
||||
import {Logger} from '#/logger'
|
||||
import {
|
||||
isProfileShadowApplied,
|
||||
@@ -45,13 +37,14 @@ import {
|
||||
} from '#/state/messages/convo/types'
|
||||
import {type MessagesEventBus} from '#/state/messages/events/agent'
|
||||
import {type MessagesEventBusError} from '#/state/messages/events/types'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {
|
||||
type ConvoWithDetails,
|
||||
type GroupConvoMember,
|
||||
parseConvoView,
|
||||
} from '#/components/dms/util'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {app, chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const logger = Logger.create(Logger.Context.ConversationAgent)
|
||||
|
||||
@@ -67,23 +60,32 @@ export function isConvoItemMessage(
|
||||
}
|
||||
|
||||
function toSystemMessageView(
|
||||
ev: ChatBskyConvoGetLog.OutputSchema['logs'][number],
|
||||
): ChatBskyConvoDefs.SystemMessageView | null {
|
||||
const isSystem =
|
||||
ChatBskyConvoDefs.isLogAddMember(ev) ||
|
||||
ChatBskyConvoDefs.isLogRemoveMember(ev) ||
|
||||
ChatBskyConvoDefs.isLogMemberJoin(ev) ||
|
||||
ChatBskyConvoDefs.isLogMemberLeave(ev) ||
|
||||
ChatBskyConvoDefs.isLogLockConvo(ev) ||
|
||||
ChatBskyConvoDefs.isLogUnlockConvo(ev) ||
|
||||
ChatBskyConvoDefs.isLogLockConvoPermanently(ev) ||
|
||||
ChatBskyConvoDefs.isLogEditGroup(ev) ||
|
||||
ChatBskyConvoDefs.isLogCreateJoinLink(ev) ||
|
||||
ChatBskyConvoDefs.isLogEditJoinLink(ev) ||
|
||||
ChatBskyConvoDefs.isLogEnableJoinLink(ev) ||
|
||||
ChatBskyConvoDefs.isLogDisableJoinLink(ev)
|
||||
if (!isSystem) return null
|
||||
return ev.message
|
||||
ev: chat.bsky.convo.getLog.$OutputBody['logs'][number],
|
||||
): chat.bsky.convo.defs.SystemMessageView | null {
|
||||
/*
|
||||
* The guard disjunction is kept inline in the `if` (rather than a separate
|
||||
* boolean) so TS narrows `ev` to the union of system-message log types, all
|
||||
* of which carry a `message: SystemMessageView`. The generated lexicon types
|
||||
* are stricter than the old `@atproto/api` ones (no `[k: string]: unknown`
|
||||
* index signature), so a separate boolean would not narrow the access.
|
||||
*/
|
||||
if (
|
||||
bsky.isType(chat.bsky.convo.defs.logAddMember, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logRemoveMember, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logMemberJoin, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logMemberLeave, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logLockConvo, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logUnlockConvo, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logLockConvoPermanently, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logEditGroup, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logCreateJoinLink, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logEditJoinLink, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logEnableJoinLink, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logDisableJoinLink, ev)
|
||||
) {
|
||||
return ev.message
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,8 +93,8 @@ function toSystemMessageView(
|
||||
* the fields the deleted view carries so a reply can render it as deleted.
|
||||
*/
|
||||
function toDeletedMessageView(
|
||||
m: ChatBskyConvoDefs.MessageView,
|
||||
): $Typed<ChatBskyConvoDefs.DeletedMessageView> {
|
||||
m: chat.bsky.convo.defs.MessageView,
|
||||
): $Typed<chat.bsky.convo.defs.DeletedMessageView> {
|
||||
return {
|
||||
$type: 'chat.bsky.convo.defs#deletedMessageView',
|
||||
id: m.id,
|
||||
@@ -105,9 +107,9 @@ function toDeletedMessageView(
|
||||
export class Convo {
|
||||
private id: string
|
||||
|
||||
private agent: SessionAgent
|
||||
private chatClient: Client
|
||||
private events: MessagesEventBus
|
||||
private senderUserDid: string
|
||||
private senderUserDid: DidString
|
||||
|
||||
private status: ConvoStatus = ConvoStatus.Uninitialized
|
||||
private error: ConvoError | undefined
|
||||
@@ -117,29 +119,29 @@ export class Convo {
|
||||
|
||||
private pastMessages: Map<
|
||||
string,
|
||||
| ChatBskyConvoDefs.MessageView
|
||||
| ChatBskyConvoDefs.DeletedMessageView
|
||||
| ChatBskyConvoDefs.SystemMessageView
|
||||
| chat.bsky.convo.defs.MessageView
|
||||
| chat.bsky.convo.defs.DeletedMessageView
|
||||
| chat.bsky.convo.defs.SystemMessageView
|
||||
> = new Map()
|
||||
private newMessages: Map<
|
||||
string,
|
||||
| ChatBskyConvoDefs.MessageView
|
||||
| ChatBskyConvoDefs.DeletedMessageView
|
||||
| ChatBskyConvoDefs.SystemMessageView
|
||||
| chat.bsky.convo.defs.MessageView
|
||||
| chat.bsky.convo.defs.DeletedMessageView
|
||||
| chat.bsky.convo.defs.SystemMessageView
|
||||
> = new Map()
|
||||
private pendingMessages: Map<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
message: ChatBskyConvoSendMessage.InputSchema['message']
|
||||
message: chat.bsky.convo.sendMessage.$InputBody['message']
|
||||
optimisticEmbedView?:
|
||||
| $Typed<AppBskyEmbedRecord.View>
|
||||
| $Typed<ChatBskyEmbedJoinLink.View>
|
||||
optimisticReplyTo?: $Typed<ChatBskyConvoDefs.MessageView>
|
||||
| $Typed<app.bsky.embed.record.View>
|
||||
| $Typed<chat.bsky.embed.joinLink.View>
|
||||
optimisticReplyTo?: $Typed<chat.bsky.convo.defs.MessageView>
|
||||
}
|
||||
> = new Map()
|
||||
private deletedMessages: Set<string> = new Set()
|
||||
private relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> =
|
||||
private relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic> =
|
||||
new Map()
|
||||
/**
|
||||
* Accumulated profile shadow state, keyed by did. The profiles this agent
|
||||
@@ -159,16 +161,16 @@ export class Convo {
|
||||
|
||||
convoId: string
|
||||
convo: ConvoWithDetails | undefined
|
||||
sender: ChatBskyActorDefs.ProfileViewBasic | undefined
|
||||
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
||||
sender: chat.bsky.actor.defs.ProfileViewBasic | undefined
|
||||
recipients: chat.bsky.actor.defs.ProfileViewBasic[] | undefined
|
||||
snapshot: ConvoState | undefined
|
||||
|
||||
constructor(params: ConvoParams) {
|
||||
this.id = nanoid(3)
|
||||
this.convoId = params.convoId
|
||||
this.agent = params.agent
|
||||
this.chatClient = params.chatClient
|
||||
this.events = params.events
|
||||
this.senderUserDid = params.agent.assertDid
|
||||
this.senderUserDid = params.chatClient.assertDid
|
||||
|
||||
if (params.placeholderData) {
|
||||
this.setupPlaceholderData(params.placeholderData)
|
||||
@@ -589,23 +591,46 @@ export class Convo {
|
||||
}
|
||||
}
|
||||
|
||||
private setConvo(convo: ChatBskyConvoDefs.ConvoView) {
|
||||
private setConvo(convo: chat.bsky.convo.defs.ConvoView) {
|
||||
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
|
||||
if (this.convo) {
|
||||
for (const member of this.convo.members) {
|
||||
this.relatedProfiles.set(member.did, member)
|
||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
||||
// toLex once dms/util migrates.
|
||||
this.relatedProfiles.set(
|
||||
member.did,
|
||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
||||
)
|
||||
}
|
||||
}
|
||||
this.applyProfileShadows()
|
||||
}
|
||||
|
||||
private updateConvo(convo: Partial<ChatBskyConvoDefs.ConvoView>) {
|
||||
/*
|
||||
* The partial merges into `this.convo.view` and is re-parsed by the
|
||||
* still-old-typed `parseConvoView` (`#/components/dms/util`, migrates in a
|
||||
* later task), and its callers build it from old-typed `this.convo.details` /
|
||||
* members. So this boundary stays in the old view world - typing the param
|
||||
* off `ConvoWithDetails['view']` keeps it internally consistent without a
|
||||
* per-call `toLex`. TODO(phase4): flip to `chat.bsky.convo.defs.ConvoView`
|
||||
* once dms/util migrates.
|
||||
*/
|
||||
private updateConvo(convo: Partial<ConvoWithDetails['view']>) {
|
||||
if (this.convo) {
|
||||
this.convo =
|
||||
parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ??
|
||||
this.convo
|
||||
for (const member of this.convo.members) {
|
||||
this.relatedProfiles.set(member.did, member)
|
||||
// `this.convo` comes from `parseConvoView` in the still-old-typed
|
||||
// `#/components/dms/util` (migrates in a later task); bridge its member
|
||||
// shape to the lexicon `ProfileViewBasic` we store. TODO(phase4): drop
|
||||
// toLex once dms/util migrates.
|
||||
this.relatedProfiles.set(
|
||||
member.did,
|
||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>(member),
|
||||
)
|
||||
}
|
||||
this.applyProfileShadows()
|
||||
}
|
||||
@@ -709,7 +734,7 @@ export class Convo {
|
||||
}
|
||||
|
||||
private pendingFetchConvo:
|
||||
| Promise<{convo: ChatBskyConvoDefs.ConvoView}>
|
||||
| Promise<{convo: chat.bsky.convo.defs.ConvoView}>
|
||||
| undefined
|
||||
async fetchConvo() {
|
||||
if (this.pendingFetchConvo) return this.pendingFetchConvo
|
||||
@@ -720,13 +745,12 @@ export class Convo {
|
||||
this.pendingFetchConvo = (async () => {
|
||||
try {
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.getConvo(
|
||||
{convoId: this.convoId},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.getConvo, {
|
||||
convoId: this.convoId,
|
||||
})
|
||||
})
|
||||
|
||||
const convo = response.data.convo
|
||||
const convo = response.convo
|
||||
|
||||
return {
|
||||
convo,
|
||||
@@ -763,18 +787,15 @@ export class Convo {
|
||||
let cursor: string | undefined
|
||||
do {
|
||||
const result = await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.getConvoMembers(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
limit: 50,
|
||||
cursor,
|
||||
},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.getConvoMembers, {
|
||||
convoId: this.convoId,
|
||||
limit: 50,
|
||||
cursor,
|
||||
})
|
||||
})
|
||||
cursor = result.data.cursor
|
||||
cursor = result.cursor
|
||||
|
||||
for (const member of result.data.members) {
|
||||
for (const member of result.members) {
|
||||
this.relatedProfiles.set(member.did, member)
|
||||
}
|
||||
} while (cursor)
|
||||
@@ -808,16 +829,13 @@ export class Convo {
|
||||
|
||||
const nextCursor = this.oldestRev // for TS
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.getMessages(
|
||||
{
|
||||
cursor: nextCursor,
|
||||
convoId: this.convoId,
|
||||
limit: IS_NATIVE ? 30 : 60,
|
||||
},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.getMessages, {
|
||||
cursor: nextCursor,
|
||||
convoId: this.convoId,
|
||||
limit: IS_NATIVE ? 30 : 60,
|
||||
})
|
||||
})
|
||||
const {cursor, messages, relatedProfiles} = response.data
|
||||
const {cursor, messages, relatedProfiles} = response
|
||||
|
||||
// Trust the cursor for pagination. We can't infer "no more pages" from a
|
||||
// short page: the server pages by raw rows but strips deleted messages
|
||||
@@ -836,9 +854,9 @@ export class Convo {
|
||||
|
||||
for (const message of messages) {
|
||||
if (
|
||||
ChatBskyConvoDefs.isMessageView(message) ||
|
||||
ChatBskyConvoDefs.isDeletedMessageView(message) ||
|
||||
ChatBskyConvoDefs.isSystemMessageView(message)
|
||||
bsky.isType(chat.bsky.convo.defs.messageView, message) ||
|
||||
bsky.isType(chat.bsky.convo.defs.deletedMessageView, message) ||
|
||||
bsky.isType(chat.bsky.convo.defs.systemMessageView, message)
|
||||
) {
|
||||
/*
|
||||
* If this message is already in new messages, it was added by the
|
||||
@@ -913,7 +931,7 @@ export class Convo {
|
||||
this.commit()
|
||||
}
|
||||
|
||||
ingestFirehose(events: ChatBskyConvoGetLog.OutputSchema['logs']) {
|
||||
ingestFirehose(events: chat.bsky.convo.getLog.$OutputBody['logs']) {
|
||||
let needsCommit = false
|
||||
|
||||
for (const ev of events) {
|
||||
@@ -950,8 +968,8 @@ export class Convo {
|
||||
}
|
||||
|
||||
if (
|
||||
ChatBskyConvoDefs.isLogCreateMessage(ev) &&
|
||||
ChatBskyConvoDefs.isMessageView(ev.message)
|
||||
bsky.isType(chat.bsky.convo.defs.logCreateMessage, ev) &&
|
||||
bsky.isType(chat.bsky.convo.defs.messageView, ev.message)
|
||||
) {
|
||||
/*
|
||||
* If this message is already in past messages, the initial
|
||||
@@ -976,8 +994,8 @@ export class Convo {
|
||||
}
|
||||
needsCommit = true
|
||||
} else if (
|
||||
ChatBskyConvoDefs.isLogDeleteMessage(ev) &&
|
||||
ChatBskyConvoDefs.isDeletedMessageView(ev.message)
|
||||
bsky.isType(chat.bsky.convo.defs.logDeleteMessage, ev) &&
|
||||
bsky.isType(chat.bsky.convo.defs.deletedMessageView, ev.message)
|
||||
) {
|
||||
/*
|
||||
* Remove the message itself, and keep its id in `deletedMessages`
|
||||
@@ -992,9 +1010,9 @@ export class Convo {
|
||||
this.deletedMessages.add(ev.message.id)
|
||||
needsCommit = true
|
||||
} else if (
|
||||
(ChatBskyConvoDefs.isLogAddReaction(ev) ||
|
||||
ChatBskyConvoDefs.isLogRemoveReaction(ev)) &&
|
||||
ChatBskyConvoDefs.isMessageView(ev.message)
|
||||
(bsky.isType(chat.bsky.convo.defs.logAddReaction, ev) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logRemoveReaction, ev)) &&
|
||||
bsky.isType(chat.bsky.convo.defs.messageView, ev.message)
|
||||
) {
|
||||
/*
|
||||
* Update if we have this in state - replace message wholesale. If we don't, don't worry about it.
|
||||
@@ -1031,11 +1049,11 @@ export class Convo {
|
||||
private pendingMessageFailure: 'recoverable' | 'unrecoverable' | null = null
|
||||
|
||||
sendMessage(
|
||||
message: ChatBskyConvoSendMessage.InputSchema['message'],
|
||||
message: chat.bsky.convo.sendMessage.$InputBody['message'],
|
||||
optimisticEmbedView?:
|
||||
| $Typed<AppBskyEmbedRecord.View>
|
||||
| $Typed<ChatBskyEmbedJoinLink.View>,
|
||||
optimisticReplyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||
| $Typed<app.bsky.embed.record.View>
|
||||
| $Typed<chat.bsky.embed.joinLink.View>,
|
||||
optimisticReplyTo?: $Typed<chat.bsky.convo.defs.MessageView>,
|
||||
) {
|
||||
// Ignore empty messages for now since they have no other purpose atm
|
||||
if (!message.text.trim() && !message.embed) return
|
||||
@@ -1110,7 +1128,7 @@ export class Convo {
|
||||
this.commit()
|
||||
}
|
||||
|
||||
updateJoinLink(joinLink: ChatBskyGroupDefs.JoinLinkView | undefined) {
|
||||
updateJoinLink(joinLink: chat.bsky.group.defs.JoinLinkView | undefined) {
|
||||
if (this.convo?.kind !== 'group') {
|
||||
throw new Error('updateJoinLink can only be called on group convo')
|
||||
}
|
||||
@@ -1126,7 +1144,7 @@ export class Convo {
|
||||
}
|
||||
|
||||
updateLockStatus(
|
||||
lockStatus: ChatBskyConvoDefs.ConvoLockStatus,
|
||||
lockStatus: chat.bsky.convo.defs.ConvoLockStatus,
|
||||
lockStatusModerationOverride: boolean,
|
||||
) {
|
||||
if (this.convo?.kind !== 'group') {
|
||||
@@ -1165,14 +1183,10 @@ export class Convo {
|
||||
|
||||
const {id, message} = pendingMessage
|
||||
|
||||
const response = await this.agent.chat.bsky.convo.sendMessage(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
message,
|
||||
},
|
||||
{encoding: 'application/json', headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const res = response.data
|
||||
const res = await this.chatClient.call(chat.bsky.convo.sendMessage, {
|
||||
convoId: this.convoId,
|
||||
message,
|
||||
})
|
||||
|
||||
// remove from queue
|
||||
this.pendingMessages.delete(id)
|
||||
@@ -1197,9 +1211,18 @@ export class Convo {
|
||||
}
|
||||
}
|
||||
|
||||
private handleSendMessageFailure(e: Error | XRPCError) {
|
||||
if (e instanceof XRPCError) {
|
||||
if (NETWORK_FAILURE_STATUSES.includes(e.status)) {
|
||||
private handleSendMessageFailure(e: Error) {
|
||||
const status = getErrorStatus(e)
|
||||
if (isXrpcError(e)) {
|
||||
/*
|
||||
* A status-less xrpc error is a network/transport failure (lex throws
|
||||
* `XrpcInternalError`, which carries no HTTP status). The old bridge
|
||||
* represented the same case with a sentinel `status` of `1`, which is a
|
||||
* member of `NETWORK_FAILURE_STATUSES` - so a network failure was
|
||||
* `recoverable`. Preserve that by treating `undefined` status the same
|
||||
* as a network-failure status here.
|
||||
*/
|
||||
if (status === undefined || NETWORK_FAILURE_STATUSES.includes(status)) {
|
||||
this.pendingMessageFailure = 'recoverable'
|
||||
} else {
|
||||
this.pendingMessageFailure = 'unrecoverable'
|
||||
@@ -1226,7 +1249,7 @@ export class Convo {
|
||||
default:
|
||||
if (!isNetworkError(e)) {
|
||||
logger.warn(`handleSendMessageFailure could not handle error`, {
|
||||
status: e.status,
|
||||
status,
|
||||
message: e.message,
|
||||
})
|
||||
}
|
||||
@@ -1261,16 +1284,15 @@ export class Convo {
|
||||
)
|
||||
|
||||
try {
|
||||
const {data} = await this.agent.chat.bsky.convo.sendMessageBatch(
|
||||
const {items} = await this.chatClient.call(
|
||||
chat.bsky.convo.sendMessageBatch,
|
||||
{
|
||||
items: messageArray.map(({message}) => ({
|
||||
convoId: this.convoId,
|
||||
message,
|
||||
})),
|
||||
},
|
||||
{encoding: 'application/json', headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const {items} = data
|
||||
|
||||
/*
|
||||
* Insert into `newMessages` as soon as we have a real ID. That way, when
|
||||
@@ -1304,13 +1326,10 @@ export class Convo {
|
||||
|
||||
try {
|
||||
await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.deleteMessageForSelf(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
messageId,
|
||||
},
|
||||
{encoding: 'application/json', headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.deleteMessageForSelf, {
|
||||
convoId: this.convoId,
|
||||
messageId,
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
const e = err as Error
|
||||
@@ -1341,11 +1360,11 @@ export class Convo {
|
||||
* matching what the server returns on refresh.
|
||||
*/
|
||||
private tombstoneDeletedReplyTo(
|
||||
m: ChatBskyConvoDefs.MessageView,
|
||||
): ChatBskyConvoDefs.MessageView {
|
||||
m: chat.bsky.convo.defs.MessageView,
|
||||
): chat.bsky.convo.defs.MessageView {
|
||||
const {replyTo} = m
|
||||
if (
|
||||
!ChatBskyConvoDefs.isMessageView(replyTo) ||
|
||||
!bsky.isType(chat.bsky.convo.defs.messageView, replyTo) ||
|
||||
!this.deletedMessages.has(replyTo.id)
|
||||
) {
|
||||
return m
|
||||
@@ -1360,19 +1379,19 @@ export class Convo {
|
||||
const items: ConvoItem[] = []
|
||||
|
||||
this.pastMessages.forEach(m => {
|
||||
if (ChatBskyConvoDefs.isMessageView(m)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.messageView, m)) {
|
||||
items.unshift({
|
||||
type: 'message',
|
||||
key: m.id,
|
||||
message: this.tombstoneDeletedReplyTo(m),
|
||||
})
|
||||
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.deletedMessageView, m)) {
|
||||
items.unshift({
|
||||
type: 'deleted-message',
|
||||
key: m.id,
|
||||
message: m,
|
||||
})
|
||||
} else if (ChatBskyConvoDefs.isSystemMessageView(m)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.systemMessageView, m)) {
|
||||
items.unshift({
|
||||
type: 'system-message',
|
||||
key: m.id,
|
||||
@@ -1393,19 +1412,19 @@ export class Convo {
|
||||
}
|
||||
|
||||
this.newMessages.forEach(m => {
|
||||
if (ChatBskyConvoDefs.isMessageView(m)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.messageView, m)) {
|
||||
items.push({
|
||||
type: 'message',
|
||||
key: m.id,
|
||||
message: this.tombstoneDeletedReplyTo(m),
|
||||
})
|
||||
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.deletedMessageView, m)) {
|
||||
items.push({
|
||||
type: 'deleted-message',
|
||||
key: m.id,
|
||||
message: m,
|
||||
})
|
||||
} else if (ChatBskyConvoDefs.isSystemMessageView(m)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.systemMessageView, m)) {
|
||||
items.push({
|
||||
type: 'system-message',
|
||||
key: m.id,
|
||||
@@ -1429,7 +1448,9 @@ export class Convo {
|
||||
$type: 'chat.bsky.convo.defs#messageView',
|
||||
id: nanoid(),
|
||||
rev: '__fake__',
|
||||
sentAt: new Date().toISOString(),
|
||||
// ISO string is a valid datetime; assert the branded type the
|
||||
// generated MessageView expects for this optimistic-only value.
|
||||
sentAt: new Date().toISOString() as DatetimeString,
|
||||
sender: {
|
||||
$type: 'chat.bsky.convo.defs#messageViewSender',
|
||||
did: this.senderUserDid,
|
||||
@@ -1471,16 +1492,18 @@ export class Convo {
|
||||
* @param emoji - must be one grapheme
|
||||
*/
|
||||
async addReaction(messageId: string, emoji: string) {
|
||||
const optimisticReaction = {
|
||||
const optimisticReaction: chat.bsky.convo.defs.ReactionView = {
|
||||
value: emoji,
|
||||
sender: {did: this.senderUserDid},
|
||||
createdAt: new Date().toISOString(),
|
||||
// ISO string is a valid datetime; assert the branded type the generated
|
||||
// ReactionView expects for this optimistic-only value.
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
}
|
||||
let restore: null | (() => void) = null
|
||||
if (this.pastMessages.has(messageId)) {
|
||||
const prevMessage = this.pastMessages.get(messageId)
|
||||
if (
|
||||
ChatBskyConvoDefs.isMessageView(prevMessage) &&
|
||||
bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) &&
|
||||
// skip optimistic update if reaction already exists
|
||||
!prevMessage.reactions?.find(
|
||||
reaction =>
|
||||
@@ -1510,7 +1533,7 @@ export class Convo {
|
||||
} else if (this.newMessages.has(messageId)) {
|
||||
const prevMessage = this.newMessages.get(messageId)
|
||||
if (
|
||||
ChatBskyConvoDefs.isMessageView(prevMessage) &&
|
||||
bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) &&
|
||||
!prevMessage.reactions?.find(reaction => reaction.value === emoji)
|
||||
) {
|
||||
if (prevMessage.reactions && prevMessage.reactions.length >= 5)
|
||||
@@ -1529,11 +1552,12 @@ export class Convo {
|
||||
|
||||
try {
|
||||
logger.debug(`Adding reaction ${emoji} to message ${messageId}`)
|
||||
const {data} = await this.agent.chat.bsky.convo.addReaction(
|
||||
{messageId, value: emoji, convoId: this.convoId},
|
||||
{encoding: 'application/json', headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
if (ChatBskyConvoDefs.isMessageView(data.message)) {
|
||||
const data = await this.chatClient.call(chat.bsky.convo.addReaction, {
|
||||
messageId,
|
||||
value: emoji,
|
||||
convoId: this.convoId,
|
||||
})
|
||||
if (bsky.isType(chat.bsky.convo.defs.messageView, data.message)) {
|
||||
if (this.pastMessages.has(messageId)) {
|
||||
this.pastMessages.set(messageId, data.message)
|
||||
this.commit()
|
||||
@@ -1558,7 +1582,7 @@ export class Convo {
|
||||
let restore: null | (() => void) = null
|
||||
if (this.pastMessages.has(messageId)) {
|
||||
const prevMessage = this.pastMessages.get(messageId)
|
||||
if (ChatBskyConvoDefs.isMessageView(prevMessage)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.messageView, prevMessage)) {
|
||||
this.pastMessages.set(messageId, {
|
||||
...prevMessage,
|
||||
reactions: prevMessage.reactions?.filter(
|
||||
@@ -1575,7 +1599,7 @@ export class Convo {
|
||||
}
|
||||
} else if (this.newMessages.has(messageId)) {
|
||||
const prevMessage = this.newMessages.get(messageId)
|
||||
if (ChatBskyConvoDefs.isMessageView(prevMessage)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.messageView, prevMessage)) {
|
||||
this.newMessages.set(messageId, {
|
||||
...prevMessage,
|
||||
reactions: prevMessage.reactions?.filter(
|
||||
@@ -1594,10 +1618,11 @@ export class Convo {
|
||||
|
||||
try {
|
||||
logger.debug(`Removing reaction ${emoji} from message ${messageId}`)
|
||||
await this.agent.chat.bsky.convo.removeReaction(
|
||||
{messageId, value: emoji, convoId: this.convoId},
|
||||
{encoding: 'application/json', headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
await this.chatClient.call(chat.bsky.convo.removeReaction, {
|
||||
messageId,
|
||||
value: emoji,
|
||||
convoId: this.convoId,
|
||||
})
|
||||
} catch (error) {
|
||||
if (restore) restore()
|
||||
throw error
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
useState,
|
||||
useSyncExternalStore,
|
||||
} from 'react'
|
||||
import {ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
@@ -28,15 +27,20 @@ import {
|
||||
} from '#/state/queries/messages/conversation'
|
||||
import {RQKEY_ROOT as ListConvosQueryKeyRoot} from '#/state/queries/messages/list-conversations'
|
||||
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {type GroupConvoMember} from '#/components/dms/util'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export * from '#/state/messages/convo/util'
|
||||
|
||||
function membersChanged(
|
||||
a: ChatBskyConvoDefs.ConvoView['members'],
|
||||
b: ChatBskyConvoDefs.ConvoView['members'],
|
||||
) {
|
||||
/*
|
||||
* Only the member dids are compared, so accept any member-shaped arrays. This
|
||||
* also lets the old-typed `convo.convo.members` (from `#/components/dms/util`,
|
||||
* migrates in a later task) flow in alongside the new lexicon ConvoView
|
||||
* members.
|
||||
*/
|
||||
function membersChanged(a: Array<{did: string}>, b: Array<{did: string}>) {
|
||||
if (a.length !== b.length) return true
|
||||
const aDids = new Set(a.map(m => m.did))
|
||||
return b.some(m => !aDids.has(m.did))
|
||||
@@ -80,15 +84,16 @@ export function ConvoProvider({
|
||||
convoId,
|
||||
}: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const events = useMessagesEventBus()
|
||||
const [convo] = useState(() => {
|
||||
const placeholder = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
getConvoKey(convoId),
|
||||
)
|
||||
const placeholder =
|
||||
queryClient.getQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
getConvoKey(convoId),
|
||||
)
|
||||
return new Convo({
|
||||
convoId,
|
||||
agent,
|
||||
chatClient,
|
||||
events,
|
||||
placeholderData: placeholder ? {convo: placeholder} : undefined,
|
||||
})
|
||||
@@ -141,14 +146,14 @@ export function ConvoProvider({
|
||||
const queryKey = event.query.queryKey as string[]
|
||||
if (queryKey[0] === root && queryKey[1] === id) {
|
||||
const data = event.query.state.data as
|
||||
| ChatBskyConvoDefs.ConvoView
|
||||
| chat.bsky.convo.defs.ConvoView
|
||||
| undefined
|
||||
if (data && convo.convo && data.muted !== convo.convo.view.muted) {
|
||||
convo.updateMuted(data.muted)
|
||||
}
|
||||
if (
|
||||
data &&
|
||||
ChatBskyConvoDefs.isGroupConvo(data.kind) &&
|
||||
bsky.isType(chat.bsky.convo.defs.groupConvo, data.kind) &&
|
||||
convo.convo?.kind === 'group'
|
||||
) {
|
||||
if (data.kind.name !== convo.convo.details.name) {
|
||||
@@ -170,7 +175,7 @@ export function ConvoProvider({
|
||||
}
|
||||
if (
|
||||
data &&
|
||||
ChatBskyConvoDefs.isGroupConvo(data.kind) &&
|
||||
bsky.isType(chat.bsky.convo.defs.groupConvo, data.kind) &&
|
||||
convo.convo?.kind === 'group' &&
|
||||
(membersChanged(data.members, convo.convo.members) ||
|
||||
data.kind.memberCount !== convo.convo.details.memberCount)
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyEmbedRecord,
|
||||
type ChatBskyActorDefs,
|
||||
type ChatBskyConvoDefs,
|
||||
type ChatBskyConvoSendMessage,
|
||||
type ChatBskyEmbedJoinLink,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
|
||||
import {type MessagesEventBus} from '#/state/messages/events/agent'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {type ConvoWithDetails} from '#/components/dms/util'
|
||||
import {app, type chat} from '#/lexicons'
|
||||
|
||||
export type ConvoParams = {
|
||||
convoId: string
|
||||
agent: SessionAgent
|
||||
chatClient: Client
|
||||
events: MessagesEventBus
|
||||
placeholderData?: {
|
||||
convo: ChatBskyConvoDefs.ConvoView
|
||||
convo: chat.bsky.convo.defs.ConvoView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +68,12 @@ export type ConvoItem =
|
||||
| {
|
||||
type: 'message'
|
||||
key: string
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
message: chat.bsky.convo.defs.MessageView
|
||||
}
|
||||
| {
|
||||
type: 'pending-message'
|
||||
key: string
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
message: chat.bsky.convo.defs.MessageView
|
||||
failed: boolean
|
||||
/**
|
||||
* Retry sending the message. If present, the message is in a failed state.
|
||||
@@ -89,12 +83,12 @@ export type ConvoItem =
|
||||
| {
|
||||
type: 'deleted-message'
|
||||
key: string
|
||||
message: ChatBskyConvoDefs.DeletedMessageView
|
||||
message: chat.bsky.convo.defs.DeletedMessageView
|
||||
}
|
||||
| {
|
||||
type: 'system-message'
|
||||
key: string
|
||||
message: ChatBskyConvoDefs.SystemMessageView
|
||||
message: chat.bsky.convo.defs.SystemMessageView
|
||||
}
|
||||
| {
|
||||
type: 'error'
|
||||
@@ -108,12 +102,12 @@ export type ConvoItem =
|
||||
|
||||
type DeleteMessage = (messageId: string) => Promise<void>
|
||||
type SendMessage = (
|
||||
message: ChatBskyConvoSendMessage.InputSchema['message'],
|
||||
message: chat.bsky.convo.sendMessage.$InputBody['message'],
|
||||
optimisticEmbedView:
|
||||
| $Typed<AppBskyEmbedRecord.View>
|
||||
| $Typed<ChatBskyEmbedJoinLink.View>
|
||||
| $Typed<app.bsky.embed.record.View>
|
||||
| $Typed<chat.bsky.embed.joinLink.View>
|
||||
| undefined,
|
||||
optimisticReplyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||
optimisticReplyTo?: $Typed<chat.bsky.convo.defs.MessageView>,
|
||||
) => void
|
||||
type FetchMessageHistory = () => Promise<void>
|
||||
type MarkConvoAccepted = () => void
|
||||
@@ -152,7 +146,7 @@ export type ConvoStateReady = {
|
||||
status: ConvoStatus.Ready
|
||||
items: ConvoItem[]
|
||||
convo: ConvoWithDetails
|
||||
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
|
||||
relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
|
||||
error: undefined
|
||||
isFetchingHistory: boolean
|
||||
hasAllHistory: boolean
|
||||
@@ -167,7 +161,7 @@ export type ConvoStateBackgrounded = {
|
||||
status: ConvoStatus.Backgrounded
|
||||
items: ConvoItem[]
|
||||
convo: ConvoWithDetails
|
||||
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
|
||||
relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
|
||||
error: undefined
|
||||
isFetchingHistory: boolean
|
||||
hasAllHistory: boolean
|
||||
@@ -182,7 +176,7 @@ export type ConvoStateSuspended = {
|
||||
status: ConvoStatus.Suspended
|
||||
items: ConvoItem[]
|
||||
convo: ConvoWithDetails
|
||||
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
|
||||
relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
|
||||
error: undefined
|
||||
isFetchingHistory: boolean
|
||||
hasAllHistory: boolean
|
||||
@@ -211,7 +205,7 @@ export type ConvoStateDisabled = {
|
||||
status: ConvoStatus.Disabled
|
||||
items: ConvoItem[]
|
||||
convo: ConvoWithDetails
|
||||
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
|
||||
relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
|
||||
error: undefined
|
||||
isFetchingHistory: boolean
|
||||
hasAllHistory: boolean
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {type ChatBskyConvoGetLog} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {
|
||||
isErrorMaybeAppPasswordPermissions,
|
||||
isNetworkError,
|
||||
@@ -21,14 +20,14 @@ import {
|
||||
type MessagesEventBusParams,
|
||||
MessagesEventBusStatus,
|
||||
} from '#/state/messages/events/types'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
|
||||
const logger = Logger.create(Logger.Context.DMsAgent)
|
||||
|
||||
export class MessagesEventBus {
|
||||
private id: string
|
||||
|
||||
private agent: SessionAgent
|
||||
private chatClient: Client
|
||||
private emitter = new EventEmitter<{event: [MessagesEventBusEvent]}>()
|
||||
|
||||
private status: MessagesEventBusStatus = MessagesEventBusStatus.Initializing
|
||||
@@ -38,7 +37,7 @@ export class MessagesEventBus {
|
||||
|
||||
constructor(params: MessagesEventBusParams) {
|
||||
this.id = nanoid(3)
|
||||
this.agent = params.agent
|
||||
this.chatClient = params.chatClient
|
||||
|
||||
this.init()
|
||||
}
|
||||
@@ -261,14 +260,11 @@ export class MessagesEventBus {
|
||||
|
||||
try {
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.getLog(
|
||||
{},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.getLog, {})
|
||||
})
|
||||
// throw new Error('UNCOMMENT TO TEST INIT FAILURE')
|
||||
|
||||
const {cursor} = response.data
|
||||
const {cursor} = response
|
||||
|
||||
// should always be defined
|
||||
if (cursor) {
|
||||
@@ -356,21 +352,18 @@ export class MessagesEventBus {
|
||||
// )
|
||||
|
||||
let needsEmit = false
|
||||
let batch: ChatBskyConvoGetLog.OutputSchema['logs'] = []
|
||||
let batch: chat.bsky.convo.getLog.$OutputBody['logs'] = []
|
||||
|
||||
try {
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.chat.bsky.convo.getLog(
|
||||
{
|
||||
cursor: this.latestRev,
|
||||
},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return this.chatClient.call(chat.bsky.convo.getLog, {
|
||||
cursor: this.latestRev,
|
||||
})
|
||||
})
|
||||
|
||||
// throw new Error('UNCOMMENT TO TEST POLL FAILURE')
|
||||
|
||||
const {logs: events} = response.data
|
||||
const {logs: events} = response
|
||||
|
||||
for (const ev of events) {
|
||||
/*
|
||||
|
||||
@@ -2,7 +2,7 @@ import {createContext, useContext, useEffect, useState} from 'react'
|
||||
import {AppState} from 'react-native'
|
||||
|
||||
import {MessagesEventBus} from '#/state/messages/events/agent'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
|
||||
const MessagesEventBusContext = createContext<MessagesEventBus | null>(null)
|
||||
MessagesEventBusContext.displayName = 'MessagesEventBusContext'
|
||||
@@ -42,11 +42,11 @@ export function MessagesEventBusProviderInner({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const [bus] = useState(
|
||||
() =>
|
||||
new MessagesEventBus({
|
||||
agent,
|
||||
chatClient,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {type ChatBskyConvoGetLog} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {type chat} from '#/lexicons'
|
||||
|
||||
export type MessagesEventBusParams = {
|
||||
agent: SessionAgent
|
||||
chatClient: Client
|
||||
}
|
||||
|
||||
export enum MessagesEventBusStatus {
|
||||
@@ -66,5 +66,5 @@ export type MessagesEventBusEvent =
|
||||
}
|
||||
| {
|
||||
type: 'logs'
|
||||
logs: ChatBskyConvoGetLog.OutputSchema['logs']
|
||||
logs: chat.bsky.convo.getLog.$OutputBody['logs']
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {createContext, useContext, useMemo} from 'react'
|
||||
import {AtpAgent, type ModerationOpts} from '@atproto/api'
|
||||
import {Client} from '@atproto/lex-client'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
|
||||
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
@@ -38,18 +39,30 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
userDid,
|
||||
/*
|
||||
* `did`/`hiddenPosts` come from persisted storage typed as plain
|
||||
* `string`, so brand them to the SDK's `DidString`/`AtUriString` slots.
|
||||
*/
|
||||
userDid: userDid as ModerationOpts['userDid'],
|
||||
prefs: {
|
||||
...moderationPrefs,
|
||||
labelers: moderationPrefs.labelers.length
|
||||
? moderationPrefs.labelers
|
||||
: AtpAgent.appLabelers.map(did => ({
|
||||
: Client.appLabelers.map(did => ({
|
||||
did,
|
||||
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
||||
})),
|
||||
hiddenPosts: hiddenPosts || [],
|
||||
hiddenPosts: (hiddenPosts ||
|
||||
[]) as ModerationOpts['prefs']['hiddenPosts'],
|
||||
},
|
||||
labelDefs,
|
||||
/*
|
||||
* TODO(phase4): drop this cast once `#/state/preferences/label-defs`
|
||||
* flips its `InterpretedLabelValueDefinition` source from `@atproto/api`
|
||||
* to `@bsky.app/sdk/moderation`. The value is already produced by the
|
||||
* SDK's `interpretLabelValueDefinitions` (see `../queries/preferences`);
|
||||
* only the intermediate context type is still old-world.
|
||||
*/
|
||||
labelDefs: labelDefs,
|
||||
}
|
||||
}, [override, userDid, labelDefs, moderationPrefs, hiddenPosts])
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyNotificationDeclaration,
|
||||
type AppBskyNotificationListActivitySubscriptions,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -13,23 +9,25 @@ import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const RQKEY_getActivitySubscriptions = ['activity-subscriptions']
|
||||
export const RQKEY_getNotificationDeclaration = ['notification-declaration']
|
||||
|
||||
export function useActivitySubscriptionsQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: RQKEY_getActivitySubscriptions,
|
||||
queryFn: async ({pageParam}) => {
|
||||
const response =
|
||||
await agent.app.bsky.notification.listActivitySubscriptions({
|
||||
return await client.call(
|
||||
app.bsky.notification.listActivitySubscriptions,
|
||||
{
|
||||
cursor: pageParam,
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
)
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: prev => prev.cursor,
|
||||
@@ -37,14 +35,14 @@ export function useActivitySubscriptionsQuery() {
|
||||
}
|
||||
|
||||
export function useNotificationDeclarationQuery() {
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
const {currentAccount} = useSession()
|
||||
return useQuery({
|
||||
queryKey: RQKEY_getNotificationDeclaration,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const response = await agent.app.bsky.notification.declaration.get({
|
||||
repo: currentAccount!.did,
|
||||
const response = await client.get(app.bsky.notification.declaration, {
|
||||
repo: currentAccount!.did as AtIdentifierString,
|
||||
rkey: 'self',
|
||||
})
|
||||
return response
|
||||
@@ -57,7 +55,7 @@ export function useNotificationDeclarationQuery() {
|
||||
value: {
|
||||
$type: 'app.bsky.notification.declaration',
|
||||
allowSubscriptions: 'followers',
|
||||
} satisfies AppBskyNotificationDeclaration.Record,
|
||||
} satisfies app.bsky.notification.declaration.Main,
|
||||
}
|
||||
} else {
|
||||
throw err
|
||||
@@ -68,17 +66,18 @@ export function useNotificationDeclarationQuery() {
|
||||
}
|
||||
|
||||
export function useNotificationDeclarationMutation() {
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (record: AppBskyNotificationDeclaration.Record) => {
|
||||
const response = await agent.app.bsky.notification.declaration.put(
|
||||
mutationFn: async (record: app.bsky.notification.declaration.Main) => {
|
||||
const response = await client.put(
|
||||
app.bsky.notification.declaration,
|
||||
record,
|
||||
{
|
||||
repo: currentAccount!.did,
|
||||
repo: currentAccount!.did as AtIdentifierString,
|
||||
rkey: 'self',
|
||||
},
|
||||
record,
|
||||
)
|
||||
return response
|
||||
},
|
||||
@@ -88,7 +87,7 @@ export function useNotificationDeclarationMutation() {
|
||||
(old?: {
|
||||
uri: string
|
||||
cid: string
|
||||
value: AppBskyNotificationDeclaration.Record
|
||||
value: app.bsky.notification.declaration.Main
|
||||
}) => {
|
||||
if (!old) return old
|
||||
return {
|
||||
@@ -109,9 +108,9 @@ export function useNotificationDeclarationMutation() {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyNotificationListActivitySubscriptions.OutputSchema>
|
||||
InfiniteData<app.bsky.notification.listActivitySubscriptions.$OutputBody>
|
||||
>({
|
||||
queryKey: RQKEY_getActivitySubscriptions,
|
||||
})
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import {useCallback} from 'react'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
moderateProfile,
|
||||
type ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences'
|
||||
|
||||
@@ -27,7 +24,7 @@ export function useActorAutocompleteQuery(
|
||||
limit?: number,
|
||||
) {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
prefix = prefix.toLowerCase().trim()
|
||||
if (prefix.endsWith('.')) {
|
||||
@@ -35,20 +32,20 @@ export function useActorAutocompleteQuery(
|
||||
prefix = prefix.slice(0, -1)
|
||||
}
|
||||
|
||||
return useQuery<AppBskyActorDefs.ProfileViewBasic[]>({
|
||||
return useQuery<app.bsky.actor.defs.ProfileViewBasic[]>({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(prefix || ''),
|
||||
async queryFn() {
|
||||
const res = prefix
|
||||
? await agent.searchActorsTypeahead({
|
||||
? await client.call(app.bsky.actor.searchActorsTypeahead, {
|
||||
q: prefix,
|
||||
limit: limit || 8,
|
||||
})
|
||||
: undefined
|
||||
return res?.data.actors || []
|
||||
return res?.actors || []
|
||||
},
|
||||
select: useCallback(
|
||||
(data: AppBskyActorDefs.ProfileViewBasic[]) => {
|
||||
(data: app.bsky.actor.defs.ProfileViewBasic[]) => {
|
||||
return computeSuggestions({
|
||||
q: prefix,
|
||||
searched: data,
|
||||
@@ -65,7 +62,7 @@ export type ActorAutocompleteFn = ReturnType<typeof useActorAutocompleteFn>
|
||||
export function useActorAutocompleteFn() {
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useCallback(
|
||||
async ({query, limit = 8}: {query: string; limit?: number}) => {
|
||||
@@ -77,7 +74,7 @@ export function useActorAutocompleteFn() {
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(query || ''),
|
||||
queryFn: () =>
|
||||
agent.searchActorsTypeahead({
|
||||
client.call(app.bsky.actor.searchActorsTypeahead, {
|
||||
q: query,
|
||||
limit,
|
||||
}),
|
||||
@@ -91,11 +88,11 @@ export function useActorAutocompleteFn() {
|
||||
|
||||
return computeSuggestions({
|
||||
q: query,
|
||||
searched: res?.data.actors,
|
||||
searched: res?.actors,
|
||||
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
|
||||
})
|
||||
},
|
||||
[queryClient, moderationOpts, agent],
|
||||
[queryClient, moderationOpts, client],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,10 +102,10 @@ function computeSuggestions({
|
||||
moderationOpts,
|
||||
}: {
|
||||
q?: string
|
||||
searched?: AppBskyActorDefs.ProfileViewBasic[]
|
||||
searched?: app.bsky.actor.defs.ProfileViewBasic[]
|
||||
moderationOpts: ModerationOpts
|
||||
}) {
|
||||
let items: AppBskyActorDefs.ProfileViewBasic[] = []
|
||||
let items: app.bsky.actor.defs.ProfileViewBasic[] = []
|
||||
for (const item of searched) {
|
||||
if (!items.find(item2 => item2.handle === item.handle)) {
|
||||
items.push(item)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyActorSearchActors} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
@@ -8,7 +7,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export const RQKEY_ROOT = 'actor-search'
|
||||
export const RQKEY = (query: string, limit?: number) => [
|
||||
@@ -28,23 +28,22 @@ export function useActorSearch({
|
||||
maintainData?: boolean
|
||||
limit?: number
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyActorSearchActors.OutputSchema,
|
||||
app.bsky.actor.searchActors.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyActorSearchActors.OutputSchema>,
|
||||
InfiniteData<app.bsky.actor.searchActors.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: RQKEY(query, limit),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.searchActors({
|
||||
return await client.call(app.bsky.actor.searchActors, {
|
||||
q: query,
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
enabled: enabled && !!query,
|
||||
initialPageParam: undefined,
|
||||
@@ -54,7 +53,7 @@ export function useActorSearch({
|
||||
})
|
||||
}
|
||||
|
||||
function select(data: InfiniteData<AppBskyActorSearchActors.OutputSchema>) {
|
||||
function select(data: InfiniteData<app.bsky.actor.searchActors.$OutputBody>) {
|
||||
// enforce uniqueness
|
||||
const dids = new Set()
|
||||
|
||||
@@ -77,7 +76,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyActorSearchActors.OutputSchema>
|
||||
InfiniteData<app.bsky.actor.searchActors.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
import {type ComAtprotoServerCreateAppPassword} from '@atproto/api'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '../session'
|
||||
import {com} from '#/lexicons'
|
||||
import {usePdsClient} from '../session'
|
||||
|
||||
const RQKEY_ROOT = 'app-passwords'
|
||||
export const RQKEY = () => [RQKEY_ROOT]
|
||||
|
||||
export function useAppPasswordsQuery() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useQuery({
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: RQKEY(),
|
||||
queryFn: async () => {
|
||||
const res = await agent.com.atproto.server.listAppPasswords({})
|
||||
return res.data.passwords
|
||||
const data = await pdsClient.call(com.atproto.server.listAppPasswords, {})
|
||||
return data.passwords
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useAppPasswordCreateMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<
|
||||
ComAtprotoServerCreateAppPassword.OutputSchema,
|
||||
com.atproto.server.createAppPassword.$OutputBody,
|
||||
Error,
|
||||
{name: string; privileged: boolean}
|
||||
>({
|
||||
mutationFn: async ({name, privileged}) => {
|
||||
return (
|
||||
await agent.com.atproto.server.createAppPassword({
|
||||
name,
|
||||
privileged,
|
||||
})
|
||||
).data
|
||||
return await pdsClient.call(com.atproto.server.createAppPassword, {
|
||||
name,
|
||||
privileged,
|
||||
})
|
||||
},
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -45,10 +43,10 @@ export function useAppPasswordCreateMutation() {
|
||||
|
||||
export function useAppPasswordDeleteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {name: string}>({
|
||||
mutationFn: async ({name}) => {
|
||||
await agent.com.atproto.server.revokeAppPassword({
|
||||
await pdsClient.call(com.atproto.server.revokeAppPassword, {
|
||||
name,
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
optimisticallyDeleteBookmark,
|
||||
optimisticallySaveBookmark,
|
||||
} from '#/state/queries/bookmarks/useBookmarksQuery'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
type MutationArgs =
|
||||
| {action: 'create'; post: AppBskyFeedDefs.PostView}
|
||||
| {action: 'create'; post: app.bsky.feed.defs.PostView}
|
||||
| {
|
||||
action: 'delete'
|
||||
/**
|
||||
@@ -23,20 +24,20 @@ type MutationArgs =
|
||||
|
||||
export function useBookmarkMutation() {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useMutation({
|
||||
async mutationFn(args: MutationArgs) {
|
||||
if (args.action === 'create') {
|
||||
updatePostShadow(qc, args.post.uri, {bookmarked: true})
|
||||
await agent.app.bsky.bookmark.createBookmark({
|
||||
await client.call(app.bsky.bookmark.createBookmark, {
|
||||
uri: args.post.uri,
|
||||
cid: args.post.cid,
|
||||
})
|
||||
} else if (args.action === 'delete') {
|
||||
updatePostShadow(qc, args.uri, {bookmarked: false})
|
||||
await agent.app.bsky.bookmark.deleteBookmark({
|
||||
uri: args.uri,
|
||||
await client.call(app.bsky.bookmark.deleteBookmark, {
|
||||
uri: args.uri as AtUriString,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyBookmarkGetBookmarks,
|
||||
AppBskyFeedDefs,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {type l} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -16,28 +12,28 @@ import {
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export const bookmarksQueryKeyRoot = 'bookmarks'
|
||||
export const createBookmarksQueryKey = () => [bookmarksQueryKeyRoot]
|
||||
|
||||
export function useBookmarksQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyBookmarkGetBookmarks.OutputSchema,
|
||||
app.bsky.bookmark.getBookmarks.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>,
|
||||
InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
queryKey: createBookmarksQueryKey(),
|
||||
async queryFn({pageParam}) {
|
||||
const res = await agent.app.bsky.bookmark.getBookmarks({
|
||||
return await client.call(app.bsky.bookmark.getBookmarks, {
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -45,7 +41,7 @@ export function useBookmarksQuery() {
|
||||
}
|
||||
|
||||
export async function truncateAndInvalidate(qc: QueryClient) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{queryKey: [bookmarksQueryKeyRoot]},
|
||||
data => {
|
||||
if (data) {
|
||||
@@ -62,9 +58,9 @@ export async function truncateAndInvalidate(qc: QueryClient) {
|
||||
|
||||
export async function optimisticallySaveBookmark(
|
||||
qc: QueryClient,
|
||||
post: AppBskyFeedDefs.PostView,
|
||||
post: app.bsky.feed.defs.PostView,
|
||||
) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
},
|
||||
@@ -75,19 +71,17 @@ export async function optimisticallySaveBookmark(
|
||||
pages: data.pages.map((page, index) => {
|
||||
if (index === 0) {
|
||||
post.$type = 'app.bsky.feed.defs#postView'
|
||||
const bookmark: app.bsky.bookmark.defs.BookmarkView = {
|
||||
createdAt: new Date().toISOString() as l.DatetimeString,
|
||||
subject: {
|
||||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
},
|
||||
item: post as l.$Typed<app.bsky.feed.defs.PostView>,
|
||||
}
|
||||
return {
|
||||
...page,
|
||||
bookmarks: [
|
||||
{
|
||||
createdAt: new Date().toISOString(),
|
||||
subject: {
|
||||
uri: post.uri,
|
||||
cid: post.cid,
|
||||
},
|
||||
item: post as $Typed<AppBskyFeedDefs.PostView>,
|
||||
},
|
||||
...page.bookmarks,
|
||||
],
|
||||
bookmarks: [bookmark, ...page.bookmarks],
|
||||
}
|
||||
}
|
||||
return page
|
||||
@@ -101,7 +95,7 @@ export async function optimisticallyDeleteBookmark(
|
||||
qc: QueryClient,
|
||||
{uri}: {uri: string},
|
||||
) {
|
||||
qc.setQueriesData<InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>>(
|
||||
qc.setQueriesData<InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>>(
|
||||
{
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
},
|
||||
@@ -123,9 +117,9 @@ export async function optimisticallyDeleteBookmark(
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
): Generator<app.bsky.feed.defs.PostView, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyBookmarkGetBookmarks.OutputSchema>
|
||||
InfiniteData<app.bsky.bookmark.getBookmarks.$OutputBody>
|
||||
>({
|
||||
queryKey: [bookmarksQueryKeyRoot],
|
||||
})
|
||||
@@ -137,13 +131,7 @@ export function* findAllPostsInQueryData(
|
||||
}
|
||||
for (const page of queryData?.pages) {
|
||||
for (const bookmark of page.bookmarks) {
|
||||
if (
|
||||
!bsky.dangerousIsType<AppBskyFeedDefs.PostView>(
|
||||
bookmark.item,
|
||||
AppBskyFeedDefs.isPostView,
|
||||
)
|
||||
)
|
||||
continue
|
||||
if (!bsky.isType(app.bsky.feed.defs.postView, bookmark.item)) continue
|
||||
|
||||
if (didOrHandleUriMatches(atUri, bookmark.item)) {
|
||||
yield bookmark.item
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {useMemo, useRef} from 'react'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
@@ -28,7 +24,9 @@ import {
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const RQKEY_ROOT = 'feed-previews'
|
||||
const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds]
|
||||
@@ -85,7 +83,7 @@ export type FeedPreviewItem =
|
||||
| {
|
||||
type: 'preview:header'
|
||||
key: string
|
||||
feed: AppBskyFeedDefs.GeneratorView
|
||||
feed: app.bsky.feed.defs.GeneratorView
|
||||
}
|
||||
| {
|
||||
type: 'preview:footer'
|
||||
@@ -97,7 +95,7 @@ export type FeedPreviewItem =
|
||||
key: string
|
||||
slice: FeedPostSlice
|
||||
indexInSlice: number
|
||||
feed: AppBskyFeedDefs.GeneratorView
|
||||
feed: app.bsky.feed.defs.GeneratorView
|
||||
showReplyTo: boolean
|
||||
hideTopBorder: boolean
|
||||
}
|
||||
@@ -108,7 +106,7 @@ export type FeedPreviewItem =
|
||||
}
|
||||
|
||||
export function useFeedPreviews(
|
||||
feedsMaybeWithDuplicates: AppBskyFeedDefs.GeneratorView[],
|
||||
feedsMaybeWithDuplicates: app.bsky.feed.defs.GeneratorView[],
|
||||
isEnabled: boolean = true,
|
||||
) {
|
||||
const feeds = useMemo(
|
||||
@@ -121,7 +119,7 @@ export function useFeedPreviews(
|
||||
|
||||
const uris = feeds.map(feed => feed.uri)
|
||||
const {_} = useLingui()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const userInterests = aggregateUserInterests(preferences)
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -130,8 +128,8 @@ export function useFeedPreviews(
|
||||
const processedPageCache = useRef(
|
||||
new Map<
|
||||
{
|
||||
feed: AppBskyFeedDefs.GeneratorView
|
||||
posts: AppBskyFeedDefs.FeedViewPost[]
|
||||
feed: app.bsky.feed.defs.GeneratorView
|
||||
posts: app.bsky.feed.defs.FeedViewPost[]
|
||||
},
|
||||
FeedPreviewItem[]
|
||||
>(),
|
||||
@@ -143,7 +141,7 @@ export function useFeedPreviews(
|
||||
queryFn: async ({pageParam}) => {
|
||||
const feed = feeds[pageParam]
|
||||
const api = new CustomFeedAPI({
|
||||
agent,
|
||||
client,
|
||||
feedParams: {feed: feed.uri},
|
||||
userInterests,
|
||||
})
|
||||
@@ -207,7 +205,12 @@ export function useFeedPreviews(
|
||||
if (item.isFallbackMarker) continue
|
||||
|
||||
const moderations = item.items.map(item =>
|
||||
moderatePost(item.post, moderationOpts!),
|
||||
// TODO(phase4): drop toLex once feed-manip is migrated off
|
||||
// @atproto/api and yields lex-typed slice items.
|
||||
moderatePost(
|
||||
bsky.toLex<app.bsky.feed.defs.PostView>(item.post),
|
||||
moderationOpts!,
|
||||
),
|
||||
)
|
||||
|
||||
// apply moderation filters
|
||||
@@ -233,10 +236,20 @@ export function useFeedPreviews(
|
||||
const feedPostSliceItem: FeedPostSliceItem = {
|
||||
_reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`,
|
||||
uri: subItem.post.uri,
|
||||
post: subItem.post,
|
||||
record: subItem.record,
|
||||
// TODO(phase4): drop toLex once feed-manip is migrated
|
||||
// off @atproto/api and yields lex-typed slice items.
|
||||
post: bsky.toLex<app.bsky.feed.defs.PostView>(
|
||||
subItem.post,
|
||||
),
|
||||
record: bsky.toLex<app.bsky.feed.post.Main>(
|
||||
subItem.record,
|
||||
),
|
||||
moderation: moderations[i],
|
||||
parentAuthor: subItem.parentAuthor,
|
||||
parentAuthor: subItem.parentAuthor
|
||||
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>(
|
||||
subItem.parentAuthor,
|
||||
)
|
||||
: undefined,
|
||||
isParentBlocked: subItem.isParentBlocked,
|
||||
isParentNotFound: subItem.isParentNotFound,
|
||||
}
|
||||
@@ -349,13 +362,13 @@ export function useFeedPreviews(
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
): Generator<app.bsky.feed.defs.PostView, undefined> {
|
||||
const atUri = new AtUri(uri)
|
||||
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<{
|
||||
feed: AppBskyFeedDefs.GeneratorView
|
||||
posts: AppBskyFeedDefs.FeedViewPost[]
|
||||
feed: app.bsky.feed.defs.GeneratorView
|
||||
posts: app.bsky.feed.defs.FeedViewPost[]
|
||||
}>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
@@ -375,7 +388,7 @@ export function* findAllPostsInQueryData(
|
||||
yield embedViewRecordToPostView(quotedPost)
|
||||
}
|
||||
|
||||
if (AppBskyFeedDefs.isPostView(item.reply?.parent)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent)) {
|
||||
if (didOrHandleUriMatches(atUri, item.reply.parent)) {
|
||||
yield item.reply.parent
|
||||
}
|
||||
@@ -389,7 +402,7 @@ export function* findAllPostsInQueryData(
|
||||
}
|
||||
}
|
||||
|
||||
if (AppBskyFeedDefs.isPostView(item.reply?.root)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.root)) {
|
||||
if (didOrHandleUriMatches(atUri, item.reply.root)) {
|
||||
yield item.reply.root
|
||||
}
|
||||
@@ -407,11 +420,11 @@ export function* findAllPostsInQueryData(
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> {
|
||||
): Generator<app.bsky.actor.defs.ProfileViewBasic, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<{
|
||||
feed: AppBskyFeedDefs.GeneratorView
|
||||
posts: AppBskyFeedDefs.FeedViewPost[]
|
||||
feed: app.bsky.feed.defs.GeneratorView
|
||||
posts: app.bsky.feed.defs.FeedViewPost[]
|
||||
}>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
@@ -430,13 +443,13 @@ export function* findAllProfilesInQueryData(
|
||||
yield quotedPost.author
|
||||
}
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(item.reply?.parent) &&
|
||||
bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent) &&
|
||||
item.reply?.parent?.author.did === did
|
||||
) {
|
||||
yield item.reply.parent.author
|
||||
}
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(item.reply?.root) &&
|
||||
bsky.isType(app.bsky.feed.defs.postView, item.reply?.root) &&
|
||||
item.reply?.root?.author.did === did
|
||||
) {
|
||||
yield item.reply.root.author
|
||||
|
||||
+84
-76
@@ -1,13 +1,7 @@
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyUnspeccedGetPopularFeedGenerators,
|
||||
AtUri,
|
||||
moderateFeedGenerator,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {AtUri, type AtUriString} from '@atproto/syntax'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -26,7 +20,8 @@ import {GCTIME, STALE} from '#/state/queries'
|
||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {router} from '#/routes'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {type FeedDescriptor} from './post-feed'
|
||||
@@ -34,7 +29,7 @@ import {precacheResolvedUri} from './resolve-uri'
|
||||
|
||||
export type FeedSourceFeedInfo = {
|
||||
type: 'feed'
|
||||
view?: AppBskyFeedDefs.GeneratorView
|
||||
view?: app.bsky.feed.defs.GeneratorView
|
||||
uri: string
|
||||
feedDescriptor: FeedDescriptor
|
||||
route: {
|
||||
@@ -51,12 +46,12 @@ export type FeedSourceFeedInfo = {
|
||||
likeCount: number | undefined
|
||||
acceptsInteractions?: boolean
|
||||
likeUri: string | undefined
|
||||
contentMode: AppBskyFeedDefs.GeneratorView['contentMode']
|
||||
contentMode: app.bsky.feed.defs.GeneratorView['contentMode']
|
||||
}
|
||||
|
||||
export type FeedSourceListInfo = {
|
||||
type: 'list'
|
||||
view?: AppBskyGraphDefs.ListView
|
||||
view?: app.bsky.graph.defs.ListView
|
||||
uri: string
|
||||
feedDescriptor: FeedDescriptor
|
||||
route: {
|
||||
@@ -93,7 +88,7 @@ const feedSourceNSIDs = {
|
||||
}
|
||||
|
||||
export function hydrateFeedGenerator(
|
||||
view: AppBskyFeedDefs.GeneratorView,
|
||||
view: app.bsky.feed.defs.GeneratorView,
|
||||
): FeedSourceInfo {
|
||||
const urip = new AtUri(view.uri)
|
||||
const collection =
|
||||
@@ -135,7 +130,9 @@ export function hydrateFeedGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
|
||||
export function hydrateList(
|
||||
view: app.bsky.graph.defs.ListView,
|
||||
): FeedSourceInfo {
|
||||
const urip = new AtUri(view.uri)
|
||||
const collection =
|
||||
urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists'
|
||||
@@ -184,7 +181,7 @@ export function getAvatarTypeFromUri(uri: string) {
|
||||
|
||||
export function useFeedSourceInfoQuery({uri}: {uri: string}) {
|
||||
const type = getFeedTypeFromUri(uri)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
@@ -193,14 +190,16 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
|
||||
let view: FeedSourceInfo
|
||||
|
||||
if (type === 'feed') {
|
||||
const res = await agent.app.bsky.feed.getFeedGenerator({feed: uri})
|
||||
view = hydrateFeedGenerator(res.data.view)
|
||||
const res = await client.call(app.bsky.feed.getFeedGenerator, {
|
||||
feed: uri as AtUriString,
|
||||
})
|
||||
view = hydrateFeedGenerator(res.view)
|
||||
} else {
|
||||
const res = await agent.app.bsky.graph.getList({
|
||||
list: uri,
|
||||
const res = await client.call(app.bsky.graph.getList, {
|
||||
list: uri as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
view = hydrateList(res.data.list)
|
||||
view = hydrateList(res.list)
|
||||
}
|
||||
|
||||
return view
|
||||
@@ -234,7 +233,7 @@ export function createGetPopularFeedsQueryKey(
|
||||
|
||||
export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
const {hasSession} = useSession()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const limit = options?.limit || 10
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -255,24 +254,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
enabled: Boolean(moderationOpts) && options?.enabled !== false,
|
||||
queryKey: createGetPopularFeedsQueryKey(options),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
const res = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
},
|
||||
)
|
||||
|
||||
// precache feeds
|
||||
for (const feed of res.data.feeds) {
|
||||
for (const feed of res.feeds) {
|
||||
const hydratedFeed = hydrateFeedGenerator(feed)
|
||||
precacheFeed(queryClient, hydratedFeed)
|
||||
}
|
||||
|
||||
return res.data
|
||||
return res
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
select: useCallback(
|
||||
(
|
||||
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
|
||||
data: InfiniteData<app.bsky.unspecced.getPopularFeedGenerators.$OutputBody>,
|
||||
) => {
|
||||
const {
|
||||
savedFeeds,
|
||||
@@ -336,24 +338,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||
}
|
||||
|
||||
export function useSearchPopularFeedsMutation() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (query: string) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 10,
|
||||
query: query,
|
||||
})
|
||||
const res = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit: 10,
|
||||
query: query,
|
||||
},
|
||||
)
|
||||
|
||||
if (moderationOpts) {
|
||||
return res.data.feeds.filter(feed => {
|
||||
return res.feeds.filter(feed => {
|
||||
const decision = moderateFeedGenerator(feed, moderationOpts)
|
||||
return !decision.ui('contentMedia').blur
|
||||
})
|
||||
}
|
||||
|
||||
return res.data.feeds
|
||||
return res.feeds
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -371,7 +376,7 @@ export function usePopularFeedsSearch({
|
||||
query: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabledInner = enabled ?? Boolean(moderationOpts)
|
||||
|
||||
@@ -379,12 +384,15 @@ export function usePopularFeedsSearch({
|
||||
enabled: enabledInner,
|
||||
queryKey: createPopularFeedsSearchQueryKey(query),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 15,
|
||||
query: query,
|
||||
})
|
||||
const res = await client.call(
|
||||
app.bsky.unspecced.getPopularFeedGenerators,
|
||||
{
|
||||
limit: 15,
|
||||
query: query,
|
||||
},
|
||||
)
|
||||
|
||||
return res.data.feeds
|
||||
return res.feeds
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
select(data) {
|
||||
@@ -397,7 +405,7 @@ export function usePopularFeedsSearch({
|
||||
}
|
||||
|
||||
export type SavedFeedSourceInfo = FeedSourceInfo & {
|
||||
savedFeed: AppBskyActorDefs.SavedFeed
|
||||
savedFeed: app.bsky.actor.defs.SavedFeed
|
||||
}
|
||||
|
||||
const PWI_DISCOVER_FEED_STUB: SavedFeedSourceInfo = {
|
||||
@@ -444,7 +452,7 @@ const createPinnedFeedInfosQueryKey = (
|
||||
|
||||
export function usePinnedFeedsInfos() {
|
||||
const {hasSession} = useSession()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? []
|
||||
|
||||
@@ -467,13 +475,13 @@ export function usePinnedFeedsInfos() {
|
||||
const pinnedFeeds = pinnedItems.filter(feed => feed.type === 'feed')
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (pinnedFeeds.length > 0) {
|
||||
feedsPromise = agent.app.bsky.feed
|
||||
.getFeedGenerators({
|
||||
feeds: pinnedFeeds.map(f => f.value),
|
||||
feedsPromise = client
|
||||
.call(app.bsky.feed.getFeedGenerators, {
|
||||
feeds: pinnedFeeds.map(f => f.value as AtUriString),
|
||||
})
|
||||
.then(res => {
|
||||
for (let i = 0; i < res.data.feeds.length; i++) {
|
||||
const feedView = res.data.feeds[i]
|
||||
for (let i = 0; i < res.feeds.length; i++) {
|
||||
const feedView = res.feeds[i]
|
||||
resolved.set(feedView.uri, hydrateFeedGenerator(feedView))
|
||||
}
|
||||
})
|
||||
@@ -482,13 +490,13 @@ export function usePinnedFeedsInfos() {
|
||||
// Get all lists. This currently has to be done individually.
|
||||
const pinnedLists = pinnedItems.filter(feed => feed.type === 'list')
|
||||
const listsPromises = pinnedLists.map(list =>
|
||||
agent.app.bsky.graph
|
||||
.getList({
|
||||
list: list.value,
|
||||
client
|
||||
.call(app.bsky.graph.getList, {
|
||||
list: list.value as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
const listView = res.list
|
||||
resolved.set(listView.uri, hydrateList(listView))
|
||||
}),
|
||||
)
|
||||
@@ -536,22 +544,22 @@ export function usePinnedFeedsInfos() {
|
||||
export type SavedFeedItem =
|
||||
| {
|
||||
type: 'feed'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
view: AppBskyFeedDefs.GeneratorView
|
||||
config: app.bsky.actor.defs.SavedFeed
|
||||
view: app.bsky.feed.defs.GeneratorView
|
||||
}
|
||||
| {
|
||||
type: 'list'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
view: AppBskyGraphDefs.ListView
|
||||
config: app.bsky.actor.defs.SavedFeed
|
||||
view: app.bsky.graph.defs.ListView
|
||||
}
|
||||
| {
|
||||
type: 'timeline'
|
||||
config: AppBskyActorDefs.SavedFeed
|
||||
config: app.bsky.actor.defs.SavedFeed
|
||||
view: undefined
|
||||
}
|
||||
|
||||
export function useSavedFeeds() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
|
||||
const savedItems = preferences?.savedFeeds ?? []
|
||||
const queryClient = useQueryClient()
|
||||
@@ -574,33 +582,33 @@ export function useSavedFeeds() {
|
||||
)
|
||||
},
|
||||
queryFn: async () => {
|
||||
const resolvedFeeds = new Map<string, AppBskyFeedDefs.GeneratorView>()
|
||||
const resolvedLists = new Map<string, AppBskyGraphDefs.ListView>()
|
||||
const resolvedFeeds = new Map<string, app.bsky.feed.defs.GeneratorView>()
|
||||
const resolvedLists = new Map<string, app.bsky.graph.defs.ListView>()
|
||||
|
||||
const savedFeeds = savedItems.filter(feed => feed.type === 'feed')
|
||||
const savedLists = savedItems.filter(feed => feed.type === 'list')
|
||||
|
||||
let feedsPromise = Promise.resolve()
|
||||
if (savedFeeds.length > 0) {
|
||||
feedsPromise = agent.app.bsky.feed
|
||||
.getFeedGenerators({
|
||||
feeds: savedFeeds.map(f => f.value),
|
||||
feedsPromise = client
|
||||
.call(app.bsky.feed.getFeedGenerators, {
|
||||
feeds: savedFeeds.map(f => f.value as AtUriString),
|
||||
})
|
||||
.then(res => {
|
||||
res.data.feeds.forEach(f => {
|
||||
res.feeds.forEach(f => {
|
||||
resolvedFeeds.set(f.uri, f)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const listsPromises = savedLists.map(list =>
|
||||
agent.app.bsky.graph
|
||||
.getList({
|
||||
list: list.value,
|
||||
client
|
||||
.call(app.bsky.graph.getList, {
|
||||
list: list.value as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
.then(res => {
|
||||
const listView = res.data.list
|
||||
const listView = res.list
|
||||
resolvedLists.set(listView.uri, listView)
|
||||
}),
|
||||
)
|
||||
@@ -656,7 +664,7 @@ export function useSavedFeeds() {
|
||||
const feedInfoQueryKeyRoot = 'feedInfo'
|
||||
|
||||
export function useFeedInfo(feedUri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
staleTime: STALE.INFINITY,
|
||||
@@ -666,11 +674,11 @@ export function useFeedInfo(feedUri: string | undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const res = await agent.app.bsky.feed.getFeedGenerator({
|
||||
feed: feedUri,
|
||||
const res = await client.call(app.bsky.feed.getFeedGenerator, {
|
||||
feed: feedUri as AtUriString,
|
||||
})
|
||||
|
||||
const feedSourceInfo = hydrateFeedGenerator(res.data.view)
|
||||
const feedSourceInfo = hydrateFeedGenerator(res.view)
|
||||
return feedSourceInfo
|
||||
},
|
||||
})
|
||||
@@ -690,10 +698,10 @@ function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) {
|
||||
|
||||
export function precacheList(
|
||||
queryClient: QueryClient,
|
||||
list: AppBskyGraphDefs.ListView,
|
||||
list: app.bsky.graph.defs.ListView,
|
||||
) {
|
||||
precacheResolvedUri(queryClient, list.creator.handle, list.creator.did)
|
||||
queryClient.setQueryData<AppBskyGraphDefs.ListView>(
|
||||
queryClient.setQueryData<app.bsky.graph.defs.ListView>(
|
||||
listQueryKey(list.uri),
|
||||
list,
|
||||
)
|
||||
@@ -701,7 +709,7 @@ export function precacheList(
|
||||
|
||||
export function precacheFeedFromGeneratorView(
|
||||
queryClient: QueryClient,
|
||||
view: AppBskyFeedDefs.GeneratorView,
|
||||
view: app.bsky.feed.defs.GeneratorView,
|
||||
) {
|
||||
const hydratedFeed = hydrateFeedGenerator(view)
|
||||
precacheFeed(queryClient, hydratedFeed)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyContactGetMatches} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -6,8 +5,9 @@ import {
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type Match} from '#/components/contacts/state'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {STALE} from '.'
|
||||
|
||||
@@ -15,13 +15,12 @@ const RQ_KEY_ROOT = 'find-contacts'
|
||||
export const findContactsStatusQueryKey = [RQ_KEY_ROOT, 'sync-status']
|
||||
|
||||
export function useContactsSyncStatusQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: findContactsStatusQueryKey,
|
||||
queryFn: async () => {
|
||||
const status = await agent.app.bsky.contact.getSyncStatus()
|
||||
return status.data
|
||||
return await client.call(app.bsky.contact.getSyncStatus, {})
|
||||
},
|
||||
staleTime: STALE.SECONDS.THIRTY,
|
||||
})
|
||||
@@ -30,15 +29,14 @@ export function useContactsSyncStatusQuery() {
|
||||
export const findContactsGetMatchesQueryKey = [RQ_KEY_ROOT, 'matches']
|
||||
|
||||
export function useContactsMatchesQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: findContactsGetMatchesQueryKey,
|
||||
queryFn: async ({pageParam}) => {
|
||||
const matches = await agent.app.bsky.contact.getMatches({
|
||||
return await client.call(app.bsky.contact.getMatches, {
|
||||
cursor: pageParam,
|
||||
})
|
||||
return matches.data
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -47,20 +45,19 @@ export function useContactsMatchesQuery() {
|
||||
}
|
||||
|
||||
export function optimisticRemoveMatch(queryClient: QueryClient, did: string) {
|
||||
queryClient.setQueryData<InfiniteData<AppBskyContactGetMatches.OutputSchema>>(
|
||||
findContactsGetMatchesQueryKey,
|
||||
old => {
|
||||
if (!old) return old
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<app.bsky.contact.getMatches.$OutputBody>
|
||||
>(findContactsGetMatchesQueryKey, old => {
|
||||
if (!old) return old
|
||||
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
matches: page.matches.filter(match => match.did !== did),
|
||||
})),
|
||||
}
|
||||
},
|
||||
)
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
matches: page.matches.filter(match => match.did !== did),
|
||||
})),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const findContactsMatchesPassthroughQueryKey = (dids: string[]) => [
|
||||
@@ -95,7 +92,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
): Generator<bsky.profile.AnyProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyContactGetMatches.OutputSchema>
|
||||
InfiniteData<app.bsky.contact.getMatches.$OutputBody>
|
||||
>({
|
||||
queryKey: findContactsGetMatchesQueryKey,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {ComAtprotoTempCheckHandleAvailability} from '@atproto/api'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -9,9 +8,21 @@ import {
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {Agent} from '../session/agent'
|
||||
|
||||
/*
|
||||
* `com.atproto.temp.checkHandleAvailability` is an entryway-only endpoint that
|
||||
* isn't generated into `#/lexicons`, so we describe its result union locally.
|
||||
* The response is discriminated by `$type`; we narrow against these shapes
|
||||
* rather than a branded lexicon guard.
|
||||
*/
|
||||
type CheckHandleAvailabilityResult =
|
||||
| {$type: 'com.atproto.temp.checkHandleAvailability#resultAvailable'}
|
||||
| {
|
||||
$type: 'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
||||
suggestions: {handle: string; method: string}[]
|
||||
}
|
||||
|
||||
export const RQKEY_handleAvailability = (
|
||||
handle: string,
|
||||
domain: string,
|
||||
@@ -87,22 +98,20 @@ export async function checkHandleAvailability(
|
||||
email,
|
||||
})
|
||||
|
||||
const result = data.result as CheckHandleAvailabilityResult
|
||||
|
||||
if (
|
||||
bsky.dangerousIsType<ComAtprotoTempCheckHandleAvailability.ResultAvailable>(
|
||||
data.result,
|
||||
ComAtprotoTempCheckHandleAvailability.isResultAvailable,
|
||||
)
|
||||
result.$type ===
|
||||
'com.atproto.temp.checkHandleAvailability#resultAvailable'
|
||||
) {
|
||||
return {available: true} as const
|
||||
} else if (
|
||||
bsky.dangerousIsType<ComAtprotoTempCheckHandleAvailability.ResultUnavailable>(
|
||||
data.result,
|
||||
ComAtprotoTempCheckHandleAvailability.isResultUnavailable,
|
||||
)
|
||||
result.$type ===
|
||||
'com.atproto.temp.checkHandleAvailability#resultUnavailable'
|
||||
) {
|
||||
return {
|
||||
available: false,
|
||||
suggestions: data.result.suggestions,
|
||||
suggestions: result.suggestions,
|
||||
} as const
|
||||
} else {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphGetKnownFollowers,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -9,7 +6,8 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
type RQPageParam = string | undefined
|
||||
@@ -18,22 +16,21 @@ const RQKEY_ROOT = 'profile-known-followers'
|
||||
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||
|
||||
export function useProfileKnownFollowersQuery(did: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetKnownFollowers.OutputSchema,
|
||||
app.bsky.graph.getKnownFollowers.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetKnownFollowers.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getKnownFollowers.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(did || ''),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getKnownFollowers({
|
||||
actor: did!,
|
||||
return await client.call(app.bsky.graph.getKnownFollowers, {
|
||||
actor: did! as AtIdentifierString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -44,9 +41,9 @@ export function useProfileKnownFollowersQuery(did: string | undefined) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetKnownFollowers.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getKnownFollowers.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {type AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {addLabeler, removeLabeler} from '@bsky.app/sdk'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {z} from 'zod'
|
||||
|
||||
@@ -9,7 +10,8 @@ import {
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const labelerInfoQueryKeyRoot = 'labeler-info'
|
||||
export const labelerInfoQueryKey = (did: string) => [
|
||||
@@ -33,56 +35,60 @@ export function useLabelerInfoQuery({
|
||||
did?: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
enabled: !!did && enabled !== false,
|
||||
queryKey: labelerInfoQueryKey(did as string),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.labeler.getServices({
|
||||
dids: [did!],
|
||||
const res = await client.call(app.bsky.labeler.getServices, {
|
||||
dids: [did! as DidString],
|
||||
detailed: true,
|
||||
})
|
||||
return res.data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed
|
||||
return res.views[0] as app.bsky.labeler.defs.LabelerViewDetailed
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useLabelersInfoQuery({dids}: {dids: string[]}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
enabled: !!dids.length,
|
||||
queryKey: labelersInfoQueryKey(dids),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.labeler.getServices({dids})
|
||||
return res.data.views as AppBskyLabelerDefs.LabelerView[]
|
||||
const res = await client.call(app.bsky.labeler.getServices, {
|
||||
dids: dids as DidString[],
|
||||
})
|
||||
return res.views as app.bsky.labeler.defs.LabelerView[]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
enabled: !!dids.length,
|
||||
queryKey: createLabelersDetailedInfoQueryKey(dids),
|
||||
gcTime: GCTIME.INFINITY,
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.labeler.getServices({
|
||||
dids,
|
||||
const res = await client.call(app.bsky.labeler.getServices, {
|
||||
dids: dids as DidString[],
|
||||
detailed: true,
|
||||
})
|
||||
return res.data.views as AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||
return res.views as app.bsky.labeler.defs.LabelerViewDetailed[]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useRemoveLabelersMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
async mutationFn({dids}: {dids: string[]}) {
|
||||
await Promise.all(dids.map(did => agent.removeLabeler(did)))
|
||||
await Promise.all(
|
||||
dids.map(did => client.call(removeLabeler, did as DidString)),
|
||||
)
|
||||
},
|
||||
async onSuccess() {
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -94,7 +100,8 @@ export function useRemoveLabelersMutation() {
|
||||
|
||||
export function useLabelerSubscriptionMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const preferences = usePreferencesQuery()
|
||||
|
||||
return useMutation({
|
||||
@@ -117,26 +124,32 @@ export function useLabelerSubscriptionMutation() {
|
||||
const labelerDids = (
|
||||
preferences.data?.moderationPrefs?.labelers ?? []
|
||||
).map(l => l.did)
|
||||
const invalidLabelers: string[] = []
|
||||
const invalidLabelers: DidString[] = []
|
||||
if (labelerDids.length) {
|
||||
const profiles = await agent.getProfiles({actors: labelerDids})
|
||||
if (profiles.data) {
|
||||
for (const did of labelerDids) {
|
||||
const exists = profiles.data.profiles.find(p => p.did === did)
|
||||
const profiles = await appviewClient.call(app.bsky.actor.getProfiles, {
|
||||
actors: labelerDids,
|
||||
})
|
||||
if (profiles) {
|
||||
for (const labelerDid of labelerDids) {
|
||||
const exists = profiles.profiles.find(p => p.did === labelerDid)
|
||||
if (exists) {
|
||||
// profile came back but it's not a valid labeler
|
||||
if (exists.associated && !exists.associated.labeler) {
|
||||
invalidLabelers.push(did)
|
||||
invalidLabelers.push(labelerDid)
|
||||
}
|
||||
} else {
|
||||
// no response came back, might be deactivated or takendown
|
||||
invalidLabelers.push(did)
|
||||
invalidLabelers.push(labelerDid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidLabelers.length) {
|
||||
await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did)))
|
||||
await Promise.all(
|
||||
invalidLabelers.map(labelerDid =>
|
||||
pdsClient.call(removeLabeler, labelerDid),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (subscribe) {
|
||||
@@ -144,9 +157,9 @@ export function useLabelerSubscriptionMutation() {
|
||||
if (labelerCount >= MAX_LABELERS) {
|
||||
throw new Error('MAX_LABELERS')
|
||||
}
|
||||
await agent.addLabeler(did)
|
||||
await pdsClient.call(addLabeler, did as DidString)
|
||||
} else {
|
||||
await agent.removeLabeler(did)
|
||||
await pdsClient.call(removeLabeler, did as DidString)
|
||||
}
|
||||
},
|
||||
async onSuccess() {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyGraphGetList,
|
||||
} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -12,7 +9,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
type RQPageParam = string | undefined
|
||||
@@ -23,23 +21,22 @@ export const RQKEY = (uri: string) => [RQKEY_ROOT, uri]
|
||||
export const RQKEY_ALL = (uri: string) => [RQKEY_ROOT_ALL, uri]
|
||||
|
||||
export function useListMembersQuery(uri?: string, limit: number = PAGE_SIZE) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetList.OutputSchema,
|
||||
app.bsky.graph.getList.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetList.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getList.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(uri ?? ''),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getList({
|
||||
list: uri!, // the enabled flag will prevent this from running until uri is set
|
||||
return await client.call(app.bsky.graph.getList, {
|
||||
list: uri! as AtUriString, // the enabled flag will prevent this from running until uri is set
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -48,32 +45,32 @@ export function useListMembersQuery(uri?: string, limit: number = PAGE_SIZE) {
|
||||
}
|
||||
|
||||
export function useAllListMembersQuery(uri?: string) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY_ALL(uri ?? ''),
|
||||
queryFn: async () => {
|
||||
return getAllListMembers(agent, uri!)
|
||||
return getAllListMembers(client, uri!)
|
||||
},
|
||||
enabled: Boolean(uri),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getAllListMembers(agent: SessionAgent, uri: string) {
|
||||
export async function getAllListMembers(client: Client, uri: string) {
|
||||
let hasMore = true
|
||||
let cursor: string | undefined
|
||||
const listItems: AppBskyGraphDefs.ListItemView[] = []
|
||||
const listItems: app.bsky.graph.defs.ListItemView[] = []
|
||||
// We want to cap this at 6 pages, just for anything weird happening with the api
|
||||
let i = 0
|
||||
while (hasMore && i < 6) {
|
||||
const res = await agent.app.bsky.graph.getList({
|
||||
list: uri,
|
||||
const res = await client.call(app.bsky.graph.getList, {
|
||||
list: uri as AtUriString,
|
||||
limit: 50,
|
||||
cursor,
|
||||
})
|
||||
listItems.push(...res.data.items)
|
||||
hasMore = Boolean(res.data.cursor)
|
||||
cursor = res.data.cursor
|
||||
listItems.push(...res.items)
|
||||
hasMore = Boolean(res.cursor)
|
||||
cursor = res.cursor
|
||||
i++
|
||||
}
|
||||
return listItems
|
||||
@@ -92,9 +89,9 @@ export async function invalidateListMembersQuery({
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetList.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getList.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
@@ -115,7 +112,7 @@ export function* findAllProfilesInQueryData(
|
||||
}
|
||||
|
||||
const allQueryData = queryClient.getQueriesData<
|
||||
AppBskyGraphDefs.ListItemView[]
|
||||
app.bsky.graph.defs.ListItemView[]
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT_ALL],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphGetStarterPacksWithMembership,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
type AtUriString,
|
||||
type DatetimeString,
|
||||
type DidString,
|
||||
} from '@atproto/syntax'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
useMutation,
|
||||
@@ -13,7 +14,8 @@ import {
|
||||
RQKEY as LIST_MEMBERS_RQKEY,
|
||||
RQKEY_ALL as LIST_MEMBERS_ALL_RQKEY,
|
||||
} from '#/state/queries/list-members'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {usePdsClient, useSession} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY} from './actor-starter-packs'
|
||||
|
||||
@@ -30,7 +32,7 @@ export function useListMembershipAddMutation({
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<
|
||||
{uri: string; cid: string},
|
||||
@@ -41,14 +43,11 @@ export function useListMembershipAddMutation({
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
const res = await agent.app.bsky.graph.listitem.create(
|
||||
{repo: currentAccount.did},
|
||||
{
|
||||
subject: actorDid,
|
||||
list: listUri,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
const res = await pdsClient.create(app.bsky.graph.listitem, {
|
||||
subject: actorDid as DidString,
|
||||
list: listUri as AtUriString,
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
})
|
||||
return res
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
@@ -66,7 +65,7 @@ export function useListMembershipAddMutation({
|
||||
// update WITH_MEMBERSHIPS query for starter packs
|
||||
if (subject) {
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody>
|
||||
>(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => {
|
||||
if (!old) return old
|
||||
|
||||
@@ -86,8 +85,8 @@ export function useListMembershipAddMutation({
|
||||
...spWithMembership.starterPack,
|
||||
listItemsSample: [
|
||||
{
|
||||
uri: data.uri,
|
||||
subject: subject as AppBskyActorDefs.ProfileView,
|
||||
uri: data.uri as AtUriString,
|
||||
subject: subject as app.bsky.actor.defs.ProfileView,
|
||||
},
|
||||
...(spWithMembership.starterPack.listItemsSample?.filter(
|
||||
item => item.subject.did !== variables.actorDid,
|
||||
@@ -101,8 +100,8 @@ export function useListMembershipAddMutation({
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
uri: data.uri,
|
||||
subject: subject as AppBskyActorDefs.ProfileView,
|
||||
uri: data.uri as AtUriString,
|
||||
subject: subject as app.bsky.actor.defs.ProfileView,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -129,7 +128,7 @@ export function useListMembershipRemoveMutation({
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<
|
||||
void,
|
||||
@@ -141,8 +140,8 @@ export function useListMembershipRemoveMutation({
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
const membershipUrip = new AtUri(membershipUri)
|
||||
await agent.app.bsky.graph.listitem.delete({
|
||||
repo: currentAccount.did,
|
||||
await pdsClient.delete(app.bsky.graph.listitem, {
|
||||
repo: currentAccount.did as DidString,
|
||||
rkey: membershipUrip.rkey,
|
||||
})
|
||||
},
|
||||
@@ -160,7 +159,7 @@ export function useListMembershipRemoveMutation({
|
||||
|
||||
// update WITH_MEMBERSHIPS query for starter packs
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<AppBskyGraphGetStarterPacksWithMembership.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody>
|
||||
>(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => {
|
||||
if (!old) return old
|
||||
|
||||
|
||||
+79
-82
@@ -1,13 +1,17 @@
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyGraphGetList,
|
||||
type AppBskyGraphList,
|
||||
type AtIdentifierString,
|
||||
AtUri,
|
||||
type ComAtprotoRepoApplyWrites,
|
||||
type Facet,
|
||||
type Un$Typed,
|
||||
} from '@atproto/api'
|
||||
type AtUriString,
|
||||
type DatetimeString,
|
||||
type NsidString,
|
||||
} from '@atproto/syntax'
|
||||
import {
|
||||
blockActorList,
|
||||
muteActorList,
|
||||
unblockActorList,
|
||||
unmuteActorList,
|
||||
} from '@bsky.app/sdk'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import chunk from 'lodash.chunk'
|
||||
|
||||
@@ -15,7 +19,8 @@ import {uploadBlob} from '#/lib/api'
|
||||
import {until} from '#/lib/async/until'
|
||||
import {type ImageMeta} from '#/state/gallery'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {type SessionAgent, useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import {app, com} from '#/lexicons'
|
||||
import {FEED_INFO_RQKEY_ROOT} from './feed'
|
||||
import {invalidate as invalidateMyLists} from './my-lists'
|
||||
import {RQKEY as PROFILE_LISTS_RQKEY} from './profile-lists'
|
||||
@@ -24,19 +29,19 @@ export const RQKEY_ROOT = 'list'
|
||||
export const RQKEY = (uri: string) => [RQKEY_ROOT, uri]
|
||||
|
||||
export function useListQuery(uri?: string) {
|
||||
const agent = useAgent()
|
||||
return useQuery<AppBskyGraphDefs.ListView, Error>({
|
||||
const client = useAppviewClient()
|
||||
return useQuery<app.bsky.graph.defs.ListView, Error>({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(uri || ''),
|
||||
async queryFn() {
|
||||
if (!uri) {
|
||||
throw new Error('URI not provided')
|
||||
}
|
||||
const res = await agent.app.bsky.graph.getList({
|
||||
list: uri,
|
||||
const res = await client.call(app.bsky.graph.getList, {
|
||||
list: uri as AtUriString,
|
||||
limit: 1,
|
||||
})
|
||||
return res.data.list
|
||||
return res.list
|
||||
},
|
||||
enabled: !!uri,
|
||||
})
|
||||
@@ -46,13 +51,14 @@ export interface ListCreateMutateParams {
|
||||
purpose: string
|
||||
name: string
|
||||
description: string
|
||||
descriptionFacets: Facet[] | undefined
|
||||
descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||
avatar: ImageMeta | null | undefined
|
||||
}
|
||||
export function useListCreateMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<{uri: string; cid: string}, Error, ListCreateMutateParams>(
|
||||
{
|
||||
async mutationFn({
|
||||
@@ -71,33 +77,24 @@ export function useListCreateMutation() {
|
||||
) {
|
||||
throw new Error('Invalid list purpose: must be curatelist or modlist')
|
||||
}
|
||||
const record: Un$Typed<AppBskyGraphList.Record> = {
|
||||
const record: Omit<app.bsky.graph.list.Main, '$type'> = {
|
||||
purpose,
|
||||
name,
|
||||
description,
|
||||
descriptionFacets,
|
||||
avatar: undefined,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
}
|
||||
if (avatar) {
|
||||
const blobRes = await uploadBlob(agent, avatar.path, avatar.mime)
|
||||
record.avatar = blobRes.data.blob
|
||||
const blobRes = await uploadBlob(pdsClient, avatar.path, avatar.mime)
|
||||
record.avatar = blobRes.blob
|
||||
}
|
||||
const res = await agent.app.bsky.graph.list.create(
|
||||
{
|
||||
repo: currentAccount.did,
|
||||
},
|
||||
record,
|
||||
)
|
||||
const res = await pdsClient.create(app.bsky.graph.list, record)
|
||||
|
||||
// wait for the appview to update
|
||||
await whenAppViewReady(
|
||||
agent,
|
||||
res.uri,
|
||||
(v: AppBskyGraphGetList.Response) => {
|
||||
return typeof v?.data?.list.uri === 'string'
|
||||
},
|
||||
)
|
||||
await whenAppViewReady(appviewClient, res.uri, v => {
|
||||
return typeof v?.list.uri === 'string'
|
||||
})
|
||||
return res
|
||||
},
|
||||
onSuccess() {
|
||||
@@ -114,12 +111,13 @@ export interface ListMetadataMutateParams {
|
||||
uri: string
|
||||
name: string
|
||||
description: string
|
||||
descriptionFacets: Facet[] | undefined
|
||||
descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||
avatar: ImageMeta | null | undefined
|
||||
}
|
||||
export function useListMetadataMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<
|
||||
{uri: string; cid: string},
|
||||
@@ -136,7 +134,7 @@ export function useListMetadataMutation() {
|
||||
}
|
||||
|
||||
// get the current record
|
||||
const {value: record} = await agent.app.bsky.graph.list.get({
|
||||
const {value: record} = await pdsClient.get(app.bsky.graph.list, {
|
||||
repo: currentAccount.did,
|
||||
rkey,
|
||||
})
|
||||
@@ -146,31 +144,25 @@ export function useListMetadataMutation() {
|
||||
record.description = description
|
||||
record.descriptionFacets = descriptionFacets
|
||||
if (avatar) {
|
||||
const blobRes = await uploadBlob(agent, avatar.path, avatar.mime)
|
||||
record.avatar = blobRes.data.blob
|
||||
const blobRes = await uploadBlob(pdsClient, avatar.path, avatar.mime)
|
||||
record.avatar = blobRes.blob
|
||||
} else if (avatar === null) {
|
||||
record.avatar = undefined
|
||||
}
|
||||
const res = (
|
||||
await agent.com.atproto.repo.putRecord({
|
||||
repo: currentAccount.did,
|
||||
collection: 'app.bsky.graph.list',
|
||||
rkey,
|
||||
record,
|
||||
})
|
||||
).data
|
||||
const res = await pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo: currentAccount.did,
|
||||
collection: 'app.bsky.graph.list',
|
||||
rkey,
|
||||
record,
|
||||
})
|
||||
|
||||
// wait for the appview to update
|
||||
await whenAppViewReady(
|
||||
agent,
|
||||
res.uri,
|
||||
(v: AppBskyGraphGetList.Response) => {
|
||||
const list = v.data.list
|
||||
return (
|
||||
list.name === record.name && list.description === record.description
|
||||
)
|
||||
},
|
||||
)
|
||||
await whenAppViewReady(appviewClient, res.uri, v => {
|
||||
const list = v.list
|
||||
return (
|
||||
list.name === record.name && list.description === record.description
|
||||
)
|
||||
})
|
||||
return res
|
||||
},
|
||||
onSuccess(data, variables) {
|
||||
@@ -190,7 +182,8 @@ export function useListMetadataMutation() {
|
||||
|
||||
export function useListDeleteMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, Error, {uri: string}>({
|
||||
mutationFn: async ({uri}) => {
|
||||
@@ -198,11 +191,11 @@ export function useListDeleteMutation() {
|
||||
return
|
||||
}
|
||||
// fetch all the listitem records that belong to this list
|
||||
let cursor
|
||||
let cursor: string | undefined
|
||||
let listitemRecordUris: string[] = []
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const res = await agent.app.bsky.graph.listitem.list({
|
||||
repo: currentAccount.did,
|
||||
const res = await pdsClient.list(app.bsky.graph.listitem, {
|
||||
repo: currentAccount.did as AtIdentifierString,
|
||||
cursor,
|
||||
limit: 100,
|
||||
})
|
||||
@@ -220,11 +213,11 @@ export function useListDeleteMutation() {
|
||||
// batch delete the list and listitem records
|
||||
const createDel = (
|
||||
uri: string,
|
||||
): $Typed<ComAtprotoRepoApplyWrites.Delete> => {
|
||||
): com.atproto.repo.applyWrites.$InputBody['writes'][number] => {
|
||||
const urip = new AtUri(uri)
|
||||
return {
|
||||
$type: 'com.atproto.repo.applyWrites#delete',
|
||||
collection: urip.collection,
|
||||
collection: urip.collection as NsidString,
|
||||
rkey: urip.rkey,
|
||||
}
|
||||
}
|
||||
@@ -234,15 +227,17 @@ export function useListDeleteMutation() {
|
||||
|
||||
// apply in chunks
|
||||
for (const writesChunk of chunk(writes, 10)) {
|
||||
await agent.com.atproto.repo.applyWrites({
|
||||
repo: currentAccount.did,
|
||||
await pdsClient.call(com.atproto.repo.applyWrites, {
|
||||
repo: currentAccount.did as AtIdentifierString,
|
||||
writes: writesChunk,
|
||||
})
|
||||
}
|
||||
|
||||
// wait for the appview to update
|
||||
await whenAppViewReady(agent, uri, (v: AppBskyGraphGetList.Response) => {
|
||||
return !v?.success
|
||||
// wait for the appview to update. once the list is deleted, getList
|
||||
// throws (404), `until` catches it and passes `undefined` here, so an
|
||||
// absent body signals a completed delete.
|
||||
await whenAppViewReady(appviewClient, uri, v => {
|
||||
return !v
|
||||
})
|
||||
},
|
||||
onSuccess() {
|
||||
@@ -257,17 +252,18 @@ export function useListDeleteMutation() {
|
||||
|
||||
export function useListMuteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {uri: string; mute: boolean}>({
|
||||
mutationFn: async ({uri, mute}) => {
|
||||
if (mute) {
|
||||
await agent.muteModList(uri)
|
||||
await pdsClient.call(muteActorList, {list: uri as AtUriString})
|
||||
} else {
|
||||
await agent.unmuteModList(uri)
|
||||
await pdsClient.call(unmuteActorList, {list: uri as AtUriString})
|
||||
}
|
||||
|
||||
await whenAppViewReady(agent, uri, (v: AppBskyGraphGetList.Response) => {
|
||||
return Boolean(v?.data.list.viewer?.muted) === mute
|
||||
await whenAppViewReady(appviewClient, uri, v => {
|
||||
return Boolean(v?.list.viewer?.muted) === mute
|
||||
})
|
||||
},
|
||||
onSuccess(data, variables) {
|
||||
@@ -280,19 +276,20 @@ export function useListMuteMutation() {
|
||||
|
||||
export function useListBlockMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {uri: string; block: boolean}>({
|
||||
mutationFn: async ({uri, block}) => {
|
||||
if (block) {
|
||||
await agent.blockModList(uri)
|
||||
await pdsClient.call(blockActorList, {list: uri as AtUriString})
|
||||
} else {
|
||||
await agent.unblockModList(uri)
|
||||
await pdsClient.call(unblockActorList, {list: uri as AtUriString})
|
||||
}
|
||||
|
||||
await whenAppViewReady(agent, uri, (v: AppBskyGraphGetList.Response) => {
|
||||
await whenAppViewReady(appviewClient, uri, v => {
|
||||
return block
|
||||
? typeof v?.data.list.viewer?.blocked === 'string'
|
||||
: !v?.data.list.viewer?.blocked
|
||||
? typeof v?.list.viewer?.blocked === 'string'
|
||||
: !v?.list.viewer?.blocked
|
||||
})
|
||||
},
|
||||
onSuccess(data, variables) {
|
||||
@@ -304,17 +301,17 @@ export function useListBlockMutation() {
|
||||
}
|
||||
|
||||
async function whenAppViewReady(
|
||||
agent: SessionAgent,
|
||||
client: Client,
|
||||
uri: string,
|
||||
fn: (res: AppBskyGraphGetList.Response) => boolean,
|
||||
fn: (res: app.bsky.graph.getList.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
() =>
|
||||
agent.app.bsky.graph.getList({
|
||||
list: uri,
|
||||
client.call(app.bsky.graph.getList, {
|
||||
list: uri as AtUriString,
|
||||
limit: 1,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphGetListsWithMembership,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString, type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -10,10 +7,11 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type ListWithMembership =
|
||||
AppBskyGraphGetListsWithMembership.ListWithMembership
|
||||
app.bsky.graph.getListsWithMembership.ListWithMembership
|
||||
|
||||
const listsWithMembershipQueryKeyRoot = 'lists-with-membership'
|
||||
export const createListsWithMembershipQueryKey = (args: {actor: string}) =>
|
||||
@@ -26,23 +24,22 @@ export function useListsWithMembershipQuery({
|
||||
actor: string | undefined
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetListsWithMembership.OutputSchema,
|
||||
app.bsky.graph.getListsWithMembership.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetListsWithMembership.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getListsWithMembership.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
queryKey: createListsWithMembershipQueryKey({actor: actor ?? ''}),
|
||||
queryFn: async ({pageParam}: {pageParam?: string}) => {
|
||||
const res = await agent.app.bsky.graph.getListsWithMembership({
|
||||
actor: actor!, // the enabled flag prevents this from running until actor is set
|
||||
return await client.call(app.bsky.graph.getListsWithMembership, {
|
||||
actor: actor! as AtIdentifierString, // the enabled flag prevents this from running until actor is set
|
||||
limit: 50,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
enabled: Boolean(actor) && enabled,
|
||||
initialPageParam: undefined,
|
||||
@@ -61,10 +58,10 @@ export function updateListMembershipOptimistically({
|
||||
actor: string
|
||||
listUri: string
|
||||
membershipUri: string
|
||||
subject: AppBskyActorDefs.ProfileView
|
||||
subject: app.bsky.actor.defs.ProfileView
|
||||
}) {
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<AppBskyGraphGetListsWithMembership.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getListsWithMembership.$OutputBody>
|
||||
>(createListsWithMembershipQueryKey({actor}), old => {
|
||||
if (!old) return old
|
||||
|
||||
@@ -77,7 +74,7 @@ export function updateListMembershipOptimistically({
|
||||
return {
|
||||
...lwm,
|
||||
listItem: {
|
||||
uri: membershipUri,
|
||||
uri: membershipUri as AtUriString,
|
||||
subject,
|
||||
},
|
||||
}
|
||||
@@ -99,7 +96,7 @@ export function removeListMembershipOptimistically({
|
||||
listUri: string
|
||||
}) {
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<AppBskyGraphGetListsWithMembership.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getListsWithMembership.$OutputBody>
|
||||
>(createListsWithMembershipQueryKey({actor}), old => {
|
||||
if (!old) return old
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import {
|
||||
type ChatBskyConvoAcceptConvo,
|
||||
type ChatBskyConvoDefs,
|
||||
} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {
|
||||
type ConvoRequestListQueryData,
|
||||
optimisticDelete as optimisticDeleteRequest,
|
||||
@@ -29,19 +25,16 @@ export function useAcceptConversation(
|
||||
onError,
|
||||
}: {
|
||||
onMutate?: () => void
|
||||
onSuccess?: (data: ChatBskyConvoAcceptConvo.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.convo.acceptConvo.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const {data} = await agent.chat.bsky.convo.acceptConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.acceptConvo, {convoId})
|
||||
|
||||
return data
|
||||
},
|
||||
@@ -52,7 +45,7 @@ export function useAcceptConversation(
|
||||
queryClient.getQueriesData<ConvoListQueryData>({
|
||||
queryKey: [CONVO_LIST_ROOT_KEY],
|
||||
})
|
||||
let convoBeingAccepted: ChatBskyConvoDefs.ConvoView | null = null
|
||||
let convoBeingAccepted: chat.bsky.convo.defs.ConvoView | null = null
|
||||
for (const [_key, data] of queryClient.getQueriesData<ConvoListQueryData>(
|
||||
{queryKey: CONVO_LIST_PARTIAL_KEY('request')},
|
||||
)) {
|
||||
@@ -65,7 +58,7 @@ export function useAcceptConversation(
|
||||
(old?: ConvoListQueryData) => optimisticDelete(convoId, old),
|
||||
)
|
||||
if (convoBeingAccepted) {
|
||||
const acceptedConvo: ChatBskyConvoDefs.ConvoView = {
|
||||
const acceptedConvo: chat.bsky.convo.defs.ConvoView = {
|
||||
...convoBeingAccepted,
|
||||
status: 'accepted',
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type ChatBskyActorDeclaration,
|
||||
} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {usePdsClient, useSession} from '#/state/session'
|
||||
import {agentToLexClient} from '#/state/session/clients'
|
||||
import {type SessionAgent} from '#/state/session/session-core'
|
||||
import {resolveAllowGroupInvites} from '#/components/dms/util'
|
||||
import {type app, chat, com} from '#/lexicons'
|
||||
import {RQKEY as PROFILE_RKEY} from '../profile'
|
||||
|
||||
export function useUpdateActorDeclaration({
|
||||
@@ -19,7 +18,7 @@ export function useUpdateActorDeclaration({
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (update: {
|
||||
@@ -28,7 +27,7 @@ export function useUpdateActorDeclaration({
|
||||
}) => {
|
||||
if (!currentAccount) throw new Error('Not signed in')
|
||||
const current =
|
||||
queryClient.getQueryData<AppBskyActorDefs.ProfileViewDetailed>(
|
||||
queryClient.getQueryData<app.bsky.actor.defs.ProfileViewDetailed>(
|
||||
PROFILE_RKEY(currentAccount.did),
|
||||
)
|
||||
const allowIncoming =
|
||||
@@ -41,8 +40,8 @@ export function useUpdateActorDeclaration({
|
||||
update.allowGroupInvites ??
|
||||
current?.associated?.chat?.allowGroupInvites,
|
||||
})
|
||||
const result = await agent.com.atproto.repo.putRecord({
|
||||
repo: currentAccount.did,
|
||||
const result = await pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo: currentAccount.did as DidString,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
rkey: 'self',
|
||||
record: {
|
||||
@@ -57,7 +56,7 @@ export function useUpdateActorDeclaration({
|
||||
if (!currentAccount) return
|
||||
queryClient.setQueryData(
|
||||
PROFILE_RKEY(currentAccount?.did),
|
||||
(old?: AppBskyActorDefs.ProfileViewDetailed) => {
|
||||
(old?: app.bsky.actor.defs.ProfileViewDetailed) => {
|
||||
if (!old) return old
|
||||
const allowIncoming =
|
||||
update.allowIncoming ??
|
||||
@@ -81,7 +80,7 @@ export function useUpdateActorDeclaration({
|
||||
allowGroupInvites,
|
||||
},
|
||||
},
|
||||
} satisfies AppBskyActorDefs.ProfileViewDetailed
|
||||
} satisfies app.bsky.actor.defs.ProfileViewDetailed
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -101,13 +100,13 @@ export function useUpdateActorDeclaration({
|
||||
// for use in the settings screen for testing
|
||||
export function useDeleteActorDeclaration() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!currentAccount) throw new Error('Not signed in')
|
||||
const result = await agent.api.com.atproto.repo.deleteRecord({
|
||||
repo: currentAccount.did,
|
||||
const result = await pdsClient.call(com.atproto.repo.deleteRecord, {
|
||||
repo: currentAccount.did as DidString,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
rkey: 'self',
|
||||
})
|
||||
@@ -124,12 +123,20 @@ export async function fetchActorDeclarationRecord({
|
||||
did?: string
|
||||
}) {
|
||||
if (!did) return
|
||||
const res = await agent.com.atproto.repo
|
||||
.getRecord({
|
||||
repo: did,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
rkey: 'self',
|
||||
})
|
||||
/*
|
||||
* This helper is called with a bridge `SessionAgent` threaded from
|
||||
* `#/ageAssurance/data`. Wrap it as an account lex `Client` so the record
|
||||
* read goes through the same path as the migrated hooks; the caller keeps
|
||||
* passing the agent until the bridge is removed (Phase 4). The cast is safe:
|
||||
* `agentToLexClient` only reads `did` and `fetchHandler`, both of which the
|
||||
* base `Agent` (and thus `SessionAgent`) provides - its `AtpAgent` parameter
|
||||
* type is just narrower than it needs. TODO(phase4): drop with the bridge.
|
||||
*/
|
||||
const client = agentToLexClient(
|
||||
agent as unknown as Parameters<typeof agentToLexClient>[0],
|
||||
)
|
||||
const res = await client
|
||||
.get(chat.bsky.actor.declaration, {repo: did as DidString, rkey: 'self'})
|
||||
.catch(_e => undefined)
|
||||
return res?.data.value as ChatBskyActorDeclaration.Main
|
||||
return res?.value
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
type ChatBskyGroupAddMembers,
|
||||
} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
|
||||
import {listConvoMembersQueryKey} from './list-convo-members'
|
||||
@@ -25,12 +20,12 @@ export function useAddGroupMembers(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupAddMembers.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.addMembers.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const {currentAccount} = useSession()
|
||||
const {data: myProfile} = useProfileQuery({did: currentAccount?.did})
|
||||
|
||||
@@ -42,48 +37,60 @@ export function useAddGroupMembers(
|
||||
profiles: bsky.profile.AnyProfileView[]
|
||||
}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.addMembers(
|
||||
{convoId, members},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.addMembers, {
|
||||
convoId,
|
||||
members: members as DidString[],
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: ({profiles}) => {
|
||||
if (!convoId) return
|
||||
|
||||
const prevConvo = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
const prevConvo =
|
||||
queryClient.getQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
const prevListEntries = queryClient.getQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]})
|
||||
const prevMemberList = queryClient.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
|
||||
const addedBy: ChatBskyActorDefs.ProfileViewBasic | undefined = myProfile
|
||||
? {
|
||||
...myProfile,
|
||||
/*
|
||||
* The profile views come from producers that still emit the old
|
||||
* `@atproto/api` shapes (useProfileQuery migrates in a later task), while
|
||||
* the chat caches are now typed on the lexicon views. Structurally
|
||||
* identical modulo branded strings. TODO(phase4): drop toLex once those
|
||||
* producers migrate.
|
||||
*/
|
||||
const addedBy: chat.bsky.actor.defs.ProfileViewBasic | undefined =
|
||||
myProfile
|
||||
? bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({
|
||||
...myProfile,
|
||||
$type: 'chat.bsky.actor.defs#profileViewBasic',
|
||||
})
|
||||
: undefined
|
||||
|
||||
const optimisticMembers: chat.bsky.actor.defs.ProfileViewBasic[] =
|
||||
profiles.map(profile =>
|
||||
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({
|
||||
...profile,
|
||||
$type: 'chat.bsky.actor.defs#profileViewBasic',
|
||||
}
|
||||
: undefined
|
||||
kind: {
|
||||
$type: 'chat.bsky.actor.defs#groupConvoMember',
|
||||
role: 'standard',
|
||||
addedBy,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const optimisticMembers: ChatBskyActorDefs.ProfileViewBasic[] =
|
||||
profiles.map(profile => ({
|
||||
...profile,
|
||||
$type: 'chat.bsky.actor.defs#profileViewBasic',
|
||||
kind: {
|
||||
$type: 'chat.bsky.actor.defs#groupConvoMember',
|
||||
role: 'standard',
|
||||
addedBy,
|
||||
},
|
||||
}))
|
||||
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
prev => {
|
||||
if (!prev) return
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return prev
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return prev
|
||||
return {
|
||||
...prev,
|
||||
members: [...prev.members, ...optimisticMembers],
|
||||
@@ -96,7 +103,7 @@ export function useAddGroupMembers(
|
||||
)
|
||||
|
||||
queryClient.setQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]}, prev => {
|
||||
if (!prev?.pages) return
|
||||
return {
|
||||
@@ -105,7 +112,8 @@ export function useAddGroupMembers(
|
||||
...page,
|
||||
convos: page.convos.map(convo => {
|
||||
if (convo.id !== convoId) return convo
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind))
|
||||
return convo
|
||||
return {
|
||||
...convo,
|
||||
members: [...convo.members, ...optimisticMembers],
|
||||
@@ -120,7 +128,7 @@ export function useAddGroupMembers(
|
||||
}
|
||||
})
|
||||
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
queryClient.setQueryData<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
||||
listConvoMembersQueryKey(convoId),
|
||||
prev => {
|
||||
if (!prev) return
|
||||
@@ -132,13 +140,13 @@ export function useAddGroupMembers(
|
||||
},
|
||||
onSuccess: data => {
|
||||
if (convoId) {
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
data.convo,
|
||||
)
|
||||
|
||||
queryClient.setQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]}, prev => {
|
||||
if (!prev?.pages) return
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
type ChatBskyConvoDefs,
|
||||
type ChatBskyConvoGetConvo,
|
||||
type ChatBskyConvoGetUnreadCounts,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
type QueryClient,
|
||||
useMutation,
|
||||
@@ -11,10 +5,10 @@ import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useOnMarkAsRead} from '#/state/queries/messages/list-conversations'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {
|
||||
RQKEY_PARTIAL as UNREAD_COUNTS_PARTIAL_KEY,
|
||||
UNREAD_ACCEPTED_CAP,
|
||||
@@ -30,15 +24,12 @@ export const RQKEY_ROOT = 'convo'
|
||||
export const RQKEY = (convoId: string) => [RQKEY_ROOT, convoId]
|
||||
|
||||
export function useConvoQuery({convoId}: {convoId: string}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: RQKEY(convoId),
|
||||
queryFn: async () => {
|
||||
const {data} = await agent.chat.bsky.convo.getConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.getConvo, {convoId})
|
||||
return data.convo
|
||||
},
|
||||
staleTime: STALE.INFINITY,
|
||||
@@ -47,7 +38,7 @@ export function useConvoQuery({convoId}: {convoId: string}) {
|
||||
|
||||
export function precacheConvoQuery(
|
||||
queryClient: QueryClient,
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueryData(RQKEY(convo.id), convo)
|
||||
}
|
||||
@@ -55,7 +46,7 @@ export function precacheConvoQuery(
|
||||
export function useMarkAsReadMutation() {
|
||||
const optimisticUpdate = useOnMarkAsRead()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -67,16 +58,10 @@ export function useMarkAsReadMutation() {
|
||||
}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
|
||||
await agent.chat.bsky.convo.updateRead(
|
||||
{
|
||||
convoId,
|
||||
messageId,
|
||||
},
|
||||
{
|
||||
encoding: 'application/json',
|
||||
headers: DM_SERVICE_HEADERS,
|
||||
},
|
||||
)
|
||||
await chatClient.call(chat.bsky.convo.updateRead, {
|
||||
convoId,
|
||||
messageId,
|
||||
})
|
||||
},
|
||||
onMutate({convoId}) {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
@@ -90,7 +75,7 @@ export function useMarkAsReadMutation() {
|
||||
// find the convo so we know which badge counter (if any) to decrement.
|
||||
// keep scanning past a stale unreadCount === 0 cache so another cache
|
||||
// holding the true unread state still drives the decrement
|
||||
let unreadStatus: ChatBskyConvoDefs.ConvoView['status'] | undefined
|
||||
let unreadStatus: chat.bsky.convo.defs.ConvoView['status'] | undefined
|
||||
for (const [, data] of prevListQueries) {
|
||||
if (!data) continue
|
||||
const convo = getConvoFromQueryData(convoId, data)
|
||||
@@ -105,11 +90,13 @@ export function useMarkAsReadMutation() {
|
||||
// the badge count query is a separate server query that the list caches
|
||||
// don't feed, so decrement it here to keep the badge in sync
|
||||
const prevUnreadCountsQueries =
|
||||
queryClient.getQueriesData<ChatBskyConvoGetUnreadCounts.OutputSchema>({
|
||||
queryKey: UNREAD_COUNTS_PARTIAL_KEY,
|
||||
})
|
||||
queryClient.getQueriesData<chat.bsky.convo.getUnreadCounts.$OutputBody>(
|
||||
{
|
||||
queryKey: UNREAD_COUNTS_PARTIAL_KEY,
|
||||
},
|
||||
)
|
||||
if (unreadStatus) {
|
||||
queryClient.setQueriesData<ChatBskyConvoGetUnreadCounts.OutputSchema>(
|
||||
queryClient.setQueriesData<chat.bsky.convo.getUnreadCounts.$OutputBody>(
|
||||
{queryKey: UNREAD_COUNTS_PARTIAL_KEY},
|
||||
old => {
|
||||
if (!old) return old
|
||||
@@ -193,9 +180,9 @@ export function useMarkAsReadMutation() {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<ChatBskyActorDefs.ProfileViewBasic, void> {
|
||||
): Generator<chat.bsky.actor.defs.ProfileViewBasic, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
ChatBskyConvoGetConvo.OutputSchema['convo']
|
||||
chat.bsky.convo.getConvo.$OutputBody['convo']
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import {type ChatBskyGroupCreateGroup} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {precacheConvoQuery} from './conversation'
|
||||
|
||||
export function useCreateGroupChat({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupCreateGroup.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.createGroup.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({name, members}: {name: string; members: string[]}) => {
|
||||
const {data} = await agent.chat.bsky.group.createGroup(
|
||||
{name, members},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.createGroup, {
|
||||
name,
|
||||
members: members as DidString[],
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import {
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyGroupCreateJoinLink,
|
||||
type ChatBskyGroupDefs,
|
||||
} from '@atproto/api'
|
||||
import {type DatetimeString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -19,32 +16,34 @@ export function useCreateJoinLink(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupCreateJoinLink.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.createJoinLink.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
joinRule,
|
||||
requireApproval,
|
||||
}: {
|
||||
joinRule: ChatBskyGroupDefs.JoinRule
|
||||
joinRule: chat.bsky.group.defs.JoinRule
|
||||
requireApproval: boolean
|
||||
}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.createJoinLink(
|
||||
{convoId, joinRule, requireApproval},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.createJoinLink, {
|
||||
convoId,
|
||||
joinRule,
|
||||
requireApproval,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: ({joinRule, requireApproval}) => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {
|
||||
@@ -55,7 +54,9 @@ export function useCreateJoinLink(
|
||||
enabledStatus: 'enabled',
|
||||
joinRule,
|
||||
requireApproval,
|
||||
createdAt: new Date().toISOString(),
|
||||
// ISO string is a valid datetime; assert the branded type the
|
||||
// generated JoinLinkView expects for this optimistic-only value.
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -64,7 +65,8 @@ export function useCreateJoinLink(
|
||||
onSuccess: data => {
|
||||
if (convoId) {
|
||||
updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {...prev.kind, joinLink: data.joinLink},
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import {
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyGroupDisableJoinLink,
|
||||
} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -19,26 +16,28 @@ export function useDisableJoinLink(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupDisableJoinLink.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.disableJoinLink.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.disableJoinLink(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.disableJoinLink, {
|
||||
convoId,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: () => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) {
|
||||
if (
|
||||
!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) ||
|
||||
!prev.kind.joinLink
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
@@ -53,7 +52,8 @@ export function useDisableJoinLink(
|
||||
onSuccess: data => {
|
||||
if (convoId) {
|
||||
updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {...prev.kind, joinLink: data.joinLink},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {ChatBskyConvoDefs, type ChatBskyGroupEditGroup} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -15,26 +15,27 @@ export function useEditGroupChatName(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupEditGroup.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.editGroup.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({name: groupName}: {name: string}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.editGroup(
|
||||
{convoId, name: groupName},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.editGroup, {
|
||||
convoId,
|
||||
name: groupName,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: ({name: groupName}) => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {...prev.kind, name: groupName},
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyGroupDefs,
|
||||
type ChatBskyGroupEditJoinLink,
|
||||
} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -19,32 +15,36 @@ export function useEditJoinLink(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupEditJoinLink.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.editJoinLink.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
joinRule,
|
||||
requireApproval,
|
||||
}: {
|
||||
joinRule: ChatBskyGroupDefs.JoinRule
|
||||
joinRule: chat.bsky.group.defs.JoinRule
|
||||
requireApproval: boolean
|
||||
}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.editJoinLink(
|
||||
{convoId, joinRule, requireApproval},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.editJoinLink, {
|
||||
convoId,
|
||||
joinRule,
|
||||
requireApproval,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: ({joinRule, requireApproval}) => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) {
|
||||
if (
|
||||
!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) ||
|
||||
!prev.kind.joinLink
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
@@ -59,7 +59,8 @@ export function useEditJoinLink(
|
||||
onSuccess: data => {
|
||||
if (convoId) {
|
||||
updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {...prev.kind, joinLink: data.joinLink},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {ChatBskyConvoDefs, type ChatBskyGroupEnableJoinLink} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -16,26 +16,28 @@ export function useEnableJoinLink(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupEnableJoinLink.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.enableJoinLink.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.enableJoinLink(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.enableJoinLink, {
|
||||
convoId,
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: () => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) {
|
||||
if (
|
||||
!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) ||
|
||||
!prev.kind.joinLink
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
@@ -50,7 +52,8 @@ export function useEnableJoinLink(
|
||||
onSuccess: data => {
|
||||
if (convoId) {
|
||||
updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {...prev.kind, joinLink: data.joinLink},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {STALE} from '..'
|
||||
|
||||
const RQKEY_ROOT = 'convo-availability'
|
||||
@@ -11,15 +12,14 @@ export function useGetConvoAvailabilityQuery(
|
||||
did: string,
|
||||
{enabled = true}: {enabled?: boolean} = {},
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: RQKEY(did),
|
||||
queryFn: async () => {
|
||||
const {data} = await agent.chat.bsky.convo.getConvoAvailability(
|
||||
{members: [did]},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.getConvoAvailability, {
|
||||
members: [did as DidString],
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import {type ChatBskyConvoGetConvoForMembers} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {precacheConvoQuery} from './conversation'
|
||||
|
||||
export function useGetConvoForMembers({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyConvoGetConvoForMembers.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.convo.getConvoForMembers.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (members: string[]) => {
|
||||
const {data} = await agent.chat.bsky.convo.getConvoForMembers(
|
||||
{members: members},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.getConvoForMembers, {
|
||||
members: members as DidString[],
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {STALE} from '..'
|
||||
import {createQueryKey} from '../util'
|
||||
|
||||
@@ -9,17 +9,14 @@ const chatActorStatusQueryKey = () =>
|
||||
createQueryKey('chat-actor-status', {}, {persistedVersion: 1})
|
||||
|
||||
export function useChatActorStatusQuery() {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useQuery({
|
||||
gcTime: STALE.INFINITY,
|
||||
staleTime: STALE.SECONDS.FIFTEEN,
|
||||
queryKey: chatActorStatusQueryKey(),
|
||||
queryFn: async () => {
|
||||
const {data} = await agent.chat.bsky.actor.getStatus(
|
||||
{},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.actor.getStatus, {})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {chat} from '#/lexicons'
|
||||
import {STALE} from '..'
|
||||
|
||||
const RQKEY_ROOT = 'convo-unread-counts'
|
||||
@@ -18,7 +18,7 @@ export const UNREAD_ACCEPTED_CAP = 100
|
||||
export const UNREAD_REQUEST_CAP = 100
|
||||
|
||||
export function useUnreadCountsQuery() {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const {hasSession} = useSession()
|
||||
const aa = useAgeAssurance()
|
||||
const includeGroupChats = !aa.flags.groupChatDisabled
|
||||
@@ -26,10 +26,9 @@ export function useUnreadCountsQuery() {
|
||||
return useQuery({
|
||||
queryKey: RQKEY(includeGroupChats),
|
||||
queryFn: async () => {
|
||||
const {data} = await agent.chat.bsky.convo.getUnreadCounts(
|
||||
{includeGroupChats},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.getUnreadCounts, {
|
||||
includeGroupChats,
|
||||
})
|
||||
return data
|
||||
},
|
||||
staleTime: STALE.SECONDS.FIFTEEN,
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
type ChatBskyGroupApproveJoinRequest,
|
||||
type ChatBskyGroupListJoinRequests,
|
||||
type ChatBskyGroupRejectJoinRequest,
|
||||
} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {listConvoMembersQueryKey} from './list-convo-members'
|
||||
import {createListJoinRequestsQueryKey} from './list-join-requests'
|
||||
|
||||
type JoinRequestAction = 'approve' | 'reject'
|
||||
|
||||
type JoinRequestOutput<A extends JoinRequestAction> = A extends 'approve'
|
||||
? ChatBskyGroupApproveJoinRequest.OutputSchema
|
||||
: ChatBskyGroupRejectJoinRequest.OutputSchema
|
||||
? chat.bsky.group.approveJoinRequest.$OutputBody
|
||||
: chat.bsky.group.rejectJoinRequest.$OutputBody
|
||||
|
||||
export function useJoinRequestMutation<A extends JoinRequestAction>(
|
||||
action: A,
|
||||
@@ -34,21 +29,21 @@ export function useJoinRequestMutation<A extends JoinRequestAction>(
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({member}: {member: string}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} =
|
||||
const data =
|
||||
action === 'approve'
|
||||
? await agent.chat.bsky.group.approveJoinRequest(
|
||||
{convoId, member},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
: await agent.chat.bsky.group.rejectJoinRequest(
|
||||
{convoId, member},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
? await chatClient.call(chat.bsky.group.approveJoinRequest, {
|
||||
convoId,
|
||||
member: member as DidString,
|
||||
})
|
||||
: await chatClient.call(chat.bsky.group.rejectJoinRequest, {
|
||||
convoId,
|
||||
member: member as DidString,
|
||||
})
|
||||
return data as JoinRequestOutput<A>
|
||||
},
|
||||
onMutate: ({member}) => {
|
||||
@@ -57,7 +52,7 @@ export function useJoinRequestMutation<A extends JoinRequestAction>(
|
||||
const requestsKey = createListJoinRequestsQueryKey({convoId})
|
||||
const prevRequests =
|
||||
queryClient.getQueryData<
|
||||
InfiniteData<ChatBskyGroupListJoinRequests.OutputSchema>
|
||||
InfiniteData<chat.bsky.group.listJoinRequests.$OutputBody>
|
||||
>(requestsKey)
|
||||
|
||||
const requestedByProfile = prevRequests?.pages
|
||||
@@ -65,7 +60,7 @@ export function useJoinRequestMutation<A extends JoinRequestAction>(
|
||||
.find(request => request.requestedBy.did === member)?.requestedBy
|
||||
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<ChatBskyGroupListJoinRequests.OutputSchema>
|
||||
InfiniteData<chat.bsky.group.listJoinRequests.$OutputBody>
|
||||
>(requestsKey, prev => {
|
||||
if (!prev?.pages) return prev
|
||||
return {
|
||||
@@ -79,14 +74,14 @@ export function useJoinRequestMutation<A extends JoinRequestAction>(
|
||||
}
|
||||
})
|
||||
|
||||
let prevMembers: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
||||
let prevMembers: chat.bsky.actor.defs.ProfileViewBasic[] | undefined
|
||||
if (action === 'approve' && requestedByProfile) {
|
||||
const membersKey = listConvoMembersQueryKey(convoId)
|
||||
prevMembers =
|
||||
queryClient.getQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
queryClient.getQueryData<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
||||
membersKey,
|
||||
)
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
queryClient.setQueryData<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
||||
membersKey,
|
||||
prev => {
|
||||
if (!prev) return prev
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
type ChatBskyConvoLeaveConvo,
|
||||
type ChatBskyConvoListConvos,
|
||||
} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {invalidateJoinLinkPreviewsForConvo} from '#/state/queries/join-links'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {
|
||||
type ConvoRequestListQueryData,
|
||||
optimisticDelete as optimisticDeleteRequest,
|
||||
@@ -22,7 +18,7 @@ export function RQKEY(convoId: string | undefined) {
|
||||
|
||||
type ConvoListQueryData = {
|
||||
pageParams: Array<string | undefined>
|
||||
pages: Array<ChatBskyConvoListConvos.OutputSchema>
|
||||
pages: Array<chat.bsky.convo.listConvos.$OutputBody>
|
||||
}
|
||||
|
||||
export function useLeaveConvo(
|
||||
@@ -33,22 +29,19 @@ export function useLeaveConvo(
|
||||
onError,
|
||||
}: {
|
||||
onMutate?: () => void
|
||||
onSuccess?: (data: ChatBskyConvoLeaveConvo.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.convo.leaveConvo.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationKey: RQKEY(convoId),
|
||||
mutationFn: async () => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
|
||||
const {data} = await agent.chat.bsky.convo.leaveConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.leaveConvo, {convoId})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import {
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvoRequests,
|
||||
ChatBskyGroupDefs,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const DEFAULT_LIMIT = 10
|
||||
|
||||
@@ -26,17 +22,16 @@ export function useListConvoRequests({
|
||||
enabled?: boolean
|
||||
limit?: number
|
||||
} = {}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
enabled,
|
||||
queryKey: RQKEY(limit),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const {data} = await agent.chat.bsky.convo.listConvoRequests(
|
||||
{limit, cursor: pageParam},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return data
|
||||
return await chatClient.call(chat.bsky.convo.listConvoRequests, {
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
})
|
||||
},
|
||||
initialPageParam: undefined as RQPageParam,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -45,16 +40,18 @@ export function useListConvoRequests({
|
||||
|
||||
export type ConvoRequestListQueryData = {
|
||||
pageParams: Array<string | undefined>
|
||||
pages: Array<ChatBskyConvoListConvoRequests.OutputSchema>
|
||||
pages: Array<chat.bsky.convo.listConvoRequests.$OutputBody>
|
||||
}
|
||||
|
||||
export type ConvoRequestItem =
|
||||
ChatBskyConvoListConvoRequests.OutputSchema['requests'][number]
|
||||
chat.bsky.convo.listConvoRequests.$OutputBody['requests'][number]
|
||||
|
||||
export function optimisticUpdate(
|
||||
chatId: string,
|
||||
old: ConvoRequestListQueryData | undefined,
|
||||
updateFn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView,
|
||||
updateFn: (
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
) => chat.bsky.convo.defs.ConvoView,
|
||||
): ConvoRequestListQueryData | undefined {
|
||||
if (!old) return old
|
||||
|
||||
@@ -63,7 +60,10 @@ export function optimisticUpdate(
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
requests: page.requests.map((item): ConvoRequestItem => {
|
||||
if (ChatBskyConvoDefs.isConvoView(item) && item.id === chatId) {
|
||||
if (
|
||||
bsky.isType(chat.bsky.convo.defs.convoView, item) &&
|
||||
item.id === chatId
|
||||
) {
|
||||
return {
|
||||
...updateFn(item),
|
||||
$type: 'chat.bsky.convo.defs#convoView',
|
||||
@@ -86,7 +86,9 @@ export function optimisticDelete(
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
requests: page.requests.filter(
|
||||
item => !ChatBskyConvoDefs.isConvoView(item) || item.id !== chatId,
|
||||
item =>
|
||||
!bsky.isType(chat.bsky.convo.defs.convoView, item) ||
|
||||
item.id !== chatId,
|
||||
),
|
||||
})),
|
||||
}
|
||||
@@ -102,7 +104,7 @@ export function markAllRead(
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
requests: page.requests.map((item): ConvoRequestItem => {
|
||||
if (ChatBskyConvoDefs.isConvoView(item)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.convoView, item)) {
|
||||
return {
|
||||
...item,
|
||||
$type: 'chat.bsky.convo.defs#convoView',
|
||||
@@ -127,7 +129,7 @@ export function optimisticDeleteJoinRequest(
|
||||
...page,
|
||||
requests: page.requests.filter(
|
||||
item =>
|
||||
!ChatBskyGroupDefs.isJoinRequestConvoView(item) ||
|
||||
!bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item) ||
|
||||
item.convoId !== convoId,
|
||||
),
|
||||
})),
|
||||
@@ -139,7 +141,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvoRequests.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvoRequests.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
@@ -148,13 +150,15 @@ export function* findAllProfilesInQueryData(
|
||||
|
||||
for (const page of queryData.pages) {
|
||||
for (const item of page.requests) {
|
||||
if (ChatBskyConvoDefs.isConvoView(item)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.convoView, item)) {
|
||||
for (const member of item.members) {
|
||||
if (member.did === did) {
|
||||
yield member
|
||||
}
|
||||
}
|
||||
} else if (ChatBskyGroupDefs.isJoinRequestConvoView(item)) {
|
||||
} else if (
|
||||
bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item)
|
||||
) {
|
||||
if (item.owner.did === did) {
|
||||
yield item.owner
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {useCallback, useEffect, useMemo} from 'react'
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type Query,
|
||||
@@ -14,11 +9,11 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {invalidateJoinLinkPreviewsForConvo} from '#/state/queries/join-links'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {
|
||||
@@ -68,7 +63,7 @@ export const RQKEY_PARTIAL = (
|
||||
* filters client-side or convos leak into lists that should exclude them.
|
||||
*/
|
||||
export function convoMatchesQueryKey(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
queryKey: QueryKey,
|
||||
): boolean {
|
||||
const [, status, readState, kind, lockStatus] = queryKey as ReturnType<
|
||||
@@ -76,7 +71,7 @@ export function convoMatchesQueryKey(
|
||||
>
|
||||
if (status !== 'all' && status !== convo.status) return false
|
||||
if (readState === 'unread' && convo.unreadCount === 0) return false
|
||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
if (kind === 'direct') return false
|
||||
if (lockStatus && convo.kind.lockStatus !== lockStatus) return false
|
||||
} else {
|
||||
@@ -94,7 +89,7 @@ export function convoMatchesQueryKey(
|
||||
* longer matches (e.g. unreadCount dropped to 0), mirroring how read/mute
|
||||
* log events update convos in place everywhere.
|
||||
*/
|
||||
export function convoListQueryPredicate(convo: ChatBskyConvoDefs.ConvoView) {
|
||||
export function convoListQueryPredicate(convo: chat.bsky.convo.defs.ConvoView) {
|
||||
return (query: Query): boolean => {
|
||||
const data = query.state.data as ConvoListQueryData | undefined
|
||||
if (data && getConvoFromQueryData(convo.id, data)) return true
|
||||
@@ -119,24 +114,20 @@ export function useListConvosQuery({
|
||||
limit?: number
|
||||
lockStatus?: 'unlocked' | 'locked' | 'locked-permanently'
|
||||
} = {}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useInfiniteQuery({
|
||||
enabled,
|
||||
queryKey: RQKEY(status ?? 'all', readState, kind, lockStatus, limit),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const {data} = await agent.chat.bsky.convo.listConvos(
|
||||
{
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
readState: readState === 'unread' ? 'unread' : undefined,
|
||||
kind: kind === 'all' ? undefined : kind,
|
||||
lockStatus,
|
||||
status,
|
||||
},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return data
|
||||
return await chatClient.call(chat.bsky.convo.listConvos, {
|
||||
limit,
|
||||
cursor: pageParam,
|
||||
readState: readState === 'unread' ? 'unread' : undefined,
|
||||
kind: kind === 'all' ? undefined : kind,
|
||||
lockStatus,
|
||||
status,
|
||||
})
|
||||
},
|
||||
initialPageParam: undefined as RQPageParam,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -200,10 +191,10 @@ export function ListConvosProviderInner({
|
||||
function mutateMembers(
|
||||
convoId: string,
|
||||
fn: (
|
||||
members: ChatBskyActorDefs.ProfileViewBasic[],
|
||||
) => ChatBskyActorDefs.ProfileViewBasic[],
|
||||
members: chat.bsky.actor.defs.ProfileViewBasic[],
|
||||
) => chat.bsky.actor.defs.ProfileViewBasic[],
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
queryClient.setQueryData<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
||||
listConvoMembersQueryKey(convoId),
|
||||
old => {
|
||||
if (!old) return // query doesn't exist yet, skip
|
||||
@@ -215,8 +206,8 @@ export function ListConvosProviderInner({
|
||||
function updateConvoInAllLists(
|
||||
convoId: string,
|
||||
fn: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
) => chat.bsky.convo.defs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueriesData<ConvoListQueryData>(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
@@ -231,10 +222,10 @@ export function ListConvosProviderInner({
|
||||
function mutateConvoView(
|
||||
convoId: string,
|
||||
fn: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
) => chat.bsky.convo.defs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
old => (old ? fn(old) : old),
|
||||
)
|
||||
@@ -255,7 +246,7 @@ export function ListConvosProviderInner({
|
||||
function handleMemberAdded(
|
||||
convoId: string,
|
||||
did: string,
|
||||
relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[],
|
||||
relatedProfiles: chat.bsky.actor.defs.ProfileViewBasic[],
|
||||
rev: string,
|
||||
) {
|
||||
const newMember = relatedProfiles.find(r => r.did === did)
|
||||
@@ -265,7 +256,7 @@ export function ListConvosProviderInner({
|
||||
const alreadyKnownMember =
|
||||
queryClient
|
||||
.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
?.some(m => m.did === did) ?? false
|
||||
mutateMembers(convoId, list =>
|
||||
@@ -289,7 +280,7 @@ export function ListConvosProviderInner({
|
||||
const alreadyRemovedMember =
|
||||
queryClient
|
||||
.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
?.some(m => m.did === did) === false
|
||||
mutateMembers(convoId, list => list.filter(m => m.did !== did))
|
||||
@@ -302,24 +293,36 @@ export function ListConvosProviderInner({
|
||||
}
|
||||
|
||||
for (const log of events.logs) {
|
||||
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.logBeginConvo, log)) {
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logLeaveConvo, log)) {
|
||||
deleteConvoFromAllLists(log.convoId)
|
||||
// The viewer is no longer in this convo (they left on another
|
||||
// device, or were removed - removed members receive a
|
||||
// logLeaveConvo, not a logRemoveMember). Refetch any cached join
|
||||
// link preview so its viewer state reflects the lost membership.
|
||||
void invalidateJoinLinkPreviewsForConvo(queryClient, log.convoId)
|
||||
} else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logDeleteMessage, log)) {
|
||||
updateConvoInAllLists(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
if (
|
||||
(ChatBskyConvoDefs.isDeletedMessageView(log.message) ||
|
||||
ChatBskyConvoDefs.isMessageView(log.message)) &&
|
||||
(ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) ||
|
||||
ChatBskyConvoDefs.isMessageView(convo.lastMessage))
|
||||
(bsky.isType(
|
||||
chat.bsky.convo.defs.deletedMessageView,
|
||||
log.message,
|
||||
) ||
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.messageView,
|
||||
log.message,
|
||||
)) &&
|
||||
(bsky.isType(
|
||||
chat.bsky.convo.defs.deletedMessageView,
|
||||
convo.lastMessage,
|
||||
) ||
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.messageView,
|
||||
convo.lastMessage,
|
||||
))
|
||||
) {
|
||||
return log.message.id === convo.lastMessage.id
|
||||
? {
|
||||
@@ -333,9 +336,9 @@ export function ListConvosProviderInner({
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogCreateMessage(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logCreateMessage, log)) {
|
||||
// Store in a new var to avoid TS errors due to closures.
|
||||
const logRef: ChatBskyConvoDefs.LogCreateMessage = log
|
||||
const logRef: chat.bsky.convo.defs.LogCreateMessage = log
|
||||
|
||||
// Get all matching queries
|
||||
const queries = queryClient.getQueriesData<ConvoListQueryData>({
|
||||
@@ -343,7 +346,7 @@ export function ListConvosProviderInner({
|
||||
})
|
||||
|
||||
// Check if convo exists in any query
|
||||
let foundConvo: ChatBskyConvoDefs.ConvoView | null = null
|
||||
let foundConvo: chat.bsky.convo.defs.ConvoView | null = null
|
||||
for (const [_key, query] of queries) {
|
||||
if (!query) continue
|
||||
const convo = getConvoFromQueryData(logRef.convoId, query)
|
||||
@@ -386,15 +389,23 @@ export function ListConvosProviderInner({
|
||||
lastMessage: logRef.message,
|
||||
unreadCount:
|
||||
foundConvo.id !== currentConvoId
|
||||
? (ChatBskyConvoDefs.isMessageView(logRef.message) ||
|
||||
ChatBskyConvoDefs.isDeletedMessageView(logRef.message)) &&
|
||||
? (bsky.isType(
|
||||
chat.bsky.convo.defs.messageView,
|
||||
logRef.message,
|
||||
) ||
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.deletedMessageView,
|
||||
logRef.message,
|
||||
)) &&
|
||||
logRef.message.sender.did !== currentAccount?.did
|
||||
? foundConvo.unreadCount + 1
|
||||
: foundConvo.unreadCount
|
||||
: 0,
|
||||
}
|
||||
|
||||
function filterConvoFromPage(convo: ChatBskyConvoDefs.ConvoView[]) {
|
||||
function filterConvoFromPage(
|
||||
convo: chat.bsky.convo.defs.ConvoView[],
|
||||
) {
|
||||
return convo.filter(c => c.id !== logRef.convoId)
|
||||
}
|
||||
|
||||
@@ -457,7 +468,7 @@ export function ListConvosProviderInner({
|
||||
old => moveConvoToTopInRequests(updatedConvo, old),
|
||||
)
|
||||
}
|
||||
} else if (ChatBskyConvoDefs.isLogReadMessage(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logReadMessage, log)) {
|
||||
updateConvoInAllLists(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => ({
|
||||
@@ -466,7 +477,7 @@ export function ListConvosProviderInner({
|
||||
rev: log.rev,
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogReadConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logReadConvo, log)) {
|
||||
updateConvoInAllLists(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => ({
|
||||
@@ -475,12 +486,12 @@ export function ListConvosProviderInner({
|
||||
rev: log.rev,
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogAcceptConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logAcceptConvo, log)) {
|
||||
const requestQueries =
|
||||
queryClient.getQueriesData<ConvoListQueryData>({
|
||||
queryKey: RQKEY_PARTIAL('request'),
|
||||
})
|
||||
let foundConvo: ChatBskyConvoDefs.ConvoView | null = null
|
||||
let foundConvo: chat.bsky.convo.defs.ConvoView | null = null
|
||||
for (const [_key, data] of requestQueries) {
|
||||
if (!data) continue
|
||||
foundConvo = getConvoFromQueryData(log.convoId, data)
|
||||
@@ -496,7 +507,7 @@ export function ListConvosProviderInner({
|
||||
if (log.rev <= foundConvo.rev) {
|
||||
continue
|
||||
}
|
||||
const acceptedConvo: ChatBskyConvoDefs.ConvoView = {
|
||||
const acceptedConvo: chat.bsky.convo.defs.ConvoView = {
|
||||
...foundConvo,
|
||||
status: 'accepted',
|
||||
rev: log.rev,
|
||||
@@ -557,7 +568,7 @@ export function ListConvosProviderInner({
|
||||
}
|
||||
},
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogMuteConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logMuteConvo, log)) {
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => ({
|
||||
@@ -566,7 +577,7 @@ export function ListConvosProviderInner({
|
||||
rev: log.rev,
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogUnmuteConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logUnmuteConvo, log)) {
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => ({
|
||||
@@ -575,11 +586,11 @@ export function ListConvosProviderInner({
|
||||
rev: log.rev,
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogLockConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logLockConvo, log)) {
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {
|
||||
...convo,
|
||||
kind: {...convo.kind, lockStatus: 'locked'},
|
||||
@@ -594,11 +605,11 @@ export function ListConvosProviderInner({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
} else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logUnlockConvo, log)) {
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {
|
||||
...convo,
|
||||
kind: {
|
||||
@@ -613,11 +624,13 @@ export function ListConvosProviderInner({
|
||||
return {...convo, rev: log.rev}
|
||||
}),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogLockConvoPermanently(log)) {
|
||||
} else if (
|
||||
bsky.isType(chat.bsky.convo.defs.logLockConvoPermanently, log)
|
||||
) {
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {
|
||||
...convo,
|
||||
kind: {...convo.kind, lockStatus: 'locked-permanently'},
|
||||
@@ -628,20 +641,20 @@ export function ListConvosProviderInner({
|
||||
}),
|
||||
)
|
||||
} else if (
|
||||
ChatBskyConvoDefs.isLogCreateJoinLink(log) ||
|
||||
ChatBskyConvoDefs.isLogEditJoinLink(log) ||
|
||||
ChatBskyConvoDefs.isLogEnableJoinLink(log) ||
|
||||
ChatBskyConvoDefs.isLogDisableJoinLink(log)
|
||||
bsky.isType(chat.bsky.convo.defs.logCreateJoinLink, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logEditJoinLink, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logEnableJoinLink, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logDisableJoinLink, log)
|
||||
) {
|
||||
// Join link data not included in the log event, trigger refetch to get it
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogEditGroup(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logEditGroup, log)) {
|
||||
// Updated group details (name etc.) aren't included in the log
|
||||
// event, so refetch to pick them up.
|
||||
debouncedRefetch()
|
||||
} else if (
|
||||
ChatBskyConvoDefs.isLogApproveJoinRequest(log) ||
|
||||
ChatBskyConvoDefs.isLogRejectJoinRequest(log)
|
||||
bsky.isType(chat.bsky.convo.defs.logApproveJoinRequest, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logRejectJoinRequest, log)
|
||||
) {
|
||||
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
||||
// single-convo cache updates too, keeping the in-convo requests
|
||||
@@ -652,7 +665,9 @@ export function ListConvosProviderInner({
|
||||
applyJoinRequestCountDelta(convo, log.rev, -1),
|
||||
),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogIncomingJoinRequest(log)) {
|
||||
} else if (
|
||||
bsky.isType(chat.bsky.convo.defs.logIncomingJoinRequest, log)
|
||||
) {
|
||||
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
||||
// single-convo cache updates too, letting the in-convo requests
|
||||
// banner appear live.
|
||||
@@ -662,14 +677,16 @@ export function ListConvosProviderInner({
|
||||
applyJoinRequestCountDelta(convo, log.rev, 1),
|
||||
),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogReadJoinRequests(log)) {
|
||||
} else if (
|
||||
bsky.isType(chat.bsky.convo.defs.logReadJoinRequests, log)
|
||||
) {
|
||||
// The owner marked join requests as read (possibly on another
|
||||
// device). Zero the unread count but keep the total, mirroring the
|
||||
// useMarkJoinRequestsRead mutation.
|
||||
mutateConvoView(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {...convo, rev: log.rev}
|
||||
}
|
||||
return {
|
||||
@@ -679,11 +696,18 @@ export function ListConvosProviderInner({
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogOutgoingJoinRequest(log)) {
|
||||
} else if (
|
||||
bsky.isType(chat.bsky.convo.defs.logOutgoingJoinRequest, log)
|
||||
) {
|
||||
// Viewer isn't in the chat yet, but the inbox surfaces outgoing
|
||||
// requests, so refetch to pick up the new entry.
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogWithdrawIncomingJoinRequest(log)) {
|
||||
} else if (
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.logWithdrawIncomingJoinRequest,
|
||||
log,
|
||||
)
|
||||
) {
|
||||
// A requester rescinded their request to a group the viewer owns.
|
||||
// Mirror of isLogIncomingJoinRequest: decrement the counts.
|
||||
mutateConvoView(
|
||||
@@ -692,14 +716,19 @@ export function ListConvosProviderInner({
|
||||
applyJoinRequestCountDelta(convo, log.rev, -1),
|
||||
),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogWithdrawOutgoingJoinRequest(log)) {
|
||||
} else if (
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.logWithdrawOutgoingJoinRequest,
|
||||
log,
|
||||
)
|
||||
) {
|
||||
// The viewer rescinded their own outgoing join request (possibly on
|
||||
// another device). Remove it from the requests inbox cache.
|
||||
queryClient.setQueriesData<ConvoRequestListQueryData>(
|
||||
{queryKey: [REQUESTS_RQKEY_ROOT]},
|
||||
old => optimisticDeleteJoinRequest(log.convoId, old),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogAddReaction(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logAddReaction, log)) {
|
||||
updateConvoInAllLists(
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
@@ -713,22 +742,28 @@ export function ListConvosProviderInner({
|
||||
return {
|
||||
...convo,
|
||||
members: [...convo.members, ...relatedProfilesSansMembers],
|
||||
lastReaction: {
|
||||
/*
|
||||
* `log.message` can also be a deleted-message view per the
|
||||
* log union, which the strict MessageAndReactionView type
|
||||
* rejects - the old types absorbed this via the open-union
|
||||
* catch-all. Keep the pre-migration runtime behavior (always
|
||||
* store the view we got) and assert the cache type.
|
||||
*/
|
||||
lastReaction: bsky.toLex<
|
||||
NonNullable<chat.bsky.convo.defs.ConvoView['lastReaction']>
|
||||
>({
|
||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||
reaction: log.reaction,
|
||||
message: log.message,
|
||||
},
|
||||
}),
|
||||
rev: log.rev,
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogAddMember(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logAddMember, log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataAddMember>(
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataAddMember,
|
||||
)
|
||||
bsky.isType(chat.bsky.convo.defs.systemMessageDataAddMember, data)
|
||||
) {
|
||||
handleMemberAdded(
|
||||
log.convoId,
|
||||
@@ -742,12 +777,12 @@ export function ListConvosProviderInner({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveMember(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logRemoveMember, log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataRemoveMember>(
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.systemMessageDataRemoveMember,
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataRemoveMember,
|
||||
)
|
||||
) {
|
||||
handleMemberRemoved(log.convoId, data.member.did, log.rev)
|
||||
@@ -757,12 +792,12 @@ export function ListConvosProviderInner({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberJoin(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logMemberJoin, log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberJoin>(
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.systemMessageDataMemberJoin,
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberJoin,
|
||||
)
|
||||
) {
|
||||
handleMemberAdded(
|
||||
@@ -776,12 +811,12 @@ export function ListConvosProviderInner({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberLeave(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logMemberLeave, log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
bsky.dangerousIsType<ChatBskyConvoDefs.SystemMessageDataMemberLeave>(
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.systemMessageDataMemberLeave,
|
||||
data,
|
||||
ChatBskyConvoDefs.isSystemMessageDataMemberLeave,
|
||||
)
|
||||
) {
|
||||
handleMemberRemoved(log.convoId, data.member.did, log.rev)
|
||||
@@ -790,7 +825,7 @@ export function ListConvosProviderInner({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveReaction(log)) {
|
||||
} else if (bsky.isType(chat.bsky.convo.defs.logRemoveReaction, log)) {
|
||||
queryClient.setQueriesData(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
(old?: ConvoListQueryData) =>
|
||||
@@ -801,10 +836,14 @@ export function ListConvosProviderInner({
|
||||
if (
|
||||
// if the convo is the same
|
||||
log.convoId === convo.id &&
|
||||
ChatBskyConvoDefs.isMessageAndReactionView(
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.messageAndReactionView,
|
||||
convo.lastReaction,
|
||||
) &&
|
||||
ChatBskyConvoDefs.isMessageView(log.message) &&
|
||||
bsky.isType(
|
||||
chat.bsky.convo.defs.messageView,
|
||||
log.message,
|
||||
) &&
|
||||
// ...and the message is the same
|
||||
convo.lastReaction.message.id === log.message.id &&
|
||||
// ...and the reaction is the same
|
||||
@@ -886,7 +925,7 @@ export function useUnreadMessageCount(): {
|
||||
|
||||
export type ConvoListQueryData = {
|
||||
pageParams: Array<string | undefined>
|
||||
pages: Array<ChatBskyConvoListConvos.OutputSchema>
|
||||
pages: Array<chat.bsky.convo.listConvos.$OutputBody>
|
||||
}
|
||||
|
||||
export function useOnMarkAsRead() {
|
||||
@@ -923,8 +962,8 @@ export function useOnMarkAsRead() {
|
||||
*/
|
||||
function withRevGuard(
|
||||
rev: string,
|
||||
fn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView,
|
||||
): (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView {
|
||||
fn: (convo: chat.bsky.convo.defs.ConvoView) => chat.bsky.convo.defs.ConvoView,
|
||||
): (convo: chat.bsky.convo.defs.ConvoView) => chat.bsky.convo.defs.ConvoView {
|
||||
return convo => (rev <= convo.rev ? convo : fn(convo))
|
||||
}
|
||||
|
||||
@@ -932,8 +971,8 @@ function optimisticUpdate(
|
||||
chatId: string,
|
||||
old?: ConvoListQueryData,
|
||||
updateFn?: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
) => chat.bsky.convo.defs.ConvoView,
|
||||
) {
|
||||
if (!old || !updateFn) return old
|
||||
|
||||
@@ -949,12 +988,12 @@ function optimisticUpdate(
|
||||
}
|
||||
|
||||
function applyJoinRequestCountDelta(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
rev: string,
|
||||
delta: 1 | -1,
|
||||
): ChatBskyConvoDefs.ConvoView {
|
||||
): chat.bsky.convo.defs.ConvoView {
|
||||
// Join requests are only meaningful for group convos.
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {...convo, rev}
|
||||
}
|
||||
// Bump the total and unread counts together. Both are clamped at 0 and
|
||||
@@ -975,7 +1014,7 @@ function applyJoinRequestCountDelta(
|
||||
}
|
||||
|
||||
function moveConvoToTopInRequests(
|
||||
updatedConvo: ChatBskyConvoDefs.ConvoView,
|
||||
updatedConvo: chat.bsky.convo.defs.ConvoView,
|
||||
old: ConvoRequestListQueryData | undefined,
|
||||
): ConvoRequestListQueryData | undefined {
|
||||
if (!old) return old
|
||||
@@ -989,7 +1028,8 @@ function moveConvoToTopInRequests(
|
||||
pages: old.pages.map((page, i) => {
|
||||
const filtered = page.requests.filter(
|
||||
item =>
|
||||
!ChatBskyConvoDefs.isConvoView(item) || item.id !== updatedConvo.id,
|
||||
!bsky.isType(chat.bsky.convo.defs.convoView, item) ||
|
||||
item.id !== updatedConvo.id,
|
||||
)
|
||||
if (i === 0) {
|
||||
return {
|
||||
@@ -1003,13 +1043,13 @@ function moveConvoToTopInRequests(
|
||||
}
|
||||
|
||||
function removeMemberFromConvoView(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
did: string,
|
||||
rev: string,
|
||||
alreadyRemovedMember: boolean,
|
||||
): ChatBskyConvoDefs.ConvoView {
|
||||
): chat.bsky.convo.defs.ConvoView {
|
||||
// Member add/remove/join/leave events are only meaningful for group convos.
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) return convo
|
||||
const nextMembers = convo.members.filter(m => m.did !== did)
|
||||
return {
|
||||
...convo,
|
||||
@@ -1025,13 +1065,13 @@ function removeMemberFromConvoView(
|
||||
}
|
||||
|
||||
function addMemberToConvoView(
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
member: ChatBskyActorDefs.ProfileViewBasic,
|
||||
convo: chat.bsky.convo.defs.ConvoView,
|
||||
member: chat.bsky.actor.defs.ProfileViewBasic,
|
||||
rev: string,
|
||||
alreadyKnownMember: boolean,
|
||||
): ChatBskyConvoDefs.ConvoView {
|
||||
): chat.bsky.convo.defs.ConvoView {
|
||||
// Member add/remove/join/leave events are only meaningful for group convos.
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) return convo
|
||||
const alreadyInCuratedList = convo.members.some(m => m.did === member.did)
|
||||
const nextMembers = alreadyInCuratedList
|
||||
? convo.members
|
||||
@@ -1077,7 +1117,7 @@ export function* findAllProfilesInQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {type ChatBskyActorDefs} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'listConvoMembers'
|
||||
export const listConvoMembersQueryKey = (convoId: string) =>
|
||||
@@ -18,21 +17,22 @@ export function useListConvoMembersQuery({
|
||||
placeholderData,
|
||||
}: {
|
||||
convoId: string
|
||||
placeholderData?: ChatBskyActorDefs.ProfileViewBasic[]
|
||||
placeholderData?: chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: listConvoMembersQueryKey(convoId),
|
||||
queryFn: async () => {
|
||||
const members = []
|
||||
let cursor
|
||||
let cursor: string | undefined
|
||||
|
||||
do {
|
||||
const {data} = await agent.chat.bsky.convo.getConvoMembers(
|
||||
{convoId, cursor, limit: LIMIT},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.getConvoMembers, {
|
||||
convoId,
|
||||
cursor,
|
||||
limit: LIMIT,
|
||||
})
|
||||
members.push(...data.members)
|
||||
cursor = data.cursor
|
||||
} while (cursor)
|
||||
@@ -47,9 +47,9 @@ export function useListConvoMembersQuery({
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<ChatBskyActorDefs.ProfileViewBasic, void> {
|
||||
): Generator<chat.bsky.actor.defs.ProfileViewBasic, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {useEffect} from 'react'
|
||||
import {ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useInfiniteQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {STALE} from '..'
|
||||
|
||||
export const JOIN_REQUESTS_THRESHOLD = 20
|
||||
@@ -22,7 +22,7 @@ export function useListJoinRequestsQuery({
|
||||
convoId: string | undefined
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const queryClient = useQueryClient()
|
||||
const messagesBus = useMessagesEventBus()
|
||||
const isEnabled = enabled !== false && !!convoId
|
||||
@@ -35,9 +35,9 @@ export function useListJoinRequestsQuery({
|
||||
if (event.type !== 'logs') return
|
||||
for (const log of event.logs) {
|
||||
if (
|
||||
ChatBskyConvoDefs.isLogIncomingJoinRequest(log) ||
|
||||
ChatBskyConvoDefs.isLogApproveJoinRequest(log) ||
|
||||
ChatBskyConvoDefs.isLogRejectJoinRequest(log)
|
||||
bsky.isType(chat.bsky.convo.defs.logIncomingJoinRequest, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logApproveJoinRequest, log) ||
|
||||
bsky.isType(chat.bsky.convo.defs.logRejectJoinRequest, log)
|
||||
) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: createListJoinRequestsQueryKey({convoId}),
|
||||
@@ -54,11 +54,11 @@ export function useListJoinRequestsQuery({
|
||||
enabled: isEnabled,
|
||||
queryKey: createListJoinRequestsQueryKey({convoId: convoId ?? ''}),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const {data} = await agent.chat.bsky.group.listJoinRequests(
|
||||
{convoId: convoId!, cursor: pageParam, limit: JOIN_REQUESTS_THRESHOLD},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return data
|
||||
return await chatClient.call(chat.bsky.group.listJoinRequests, {
|
||||
convoId: convoId!,
|
||||
cursor: pageParam,
|
||||
limit: JOIN_REQUESTS_THRESHOLD,
|
||||
})
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: page => page.cursor,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
|
||||
const listMutualGroupsQueryKeyRoot = 'list-mutual-groups'
|
||||
|
||||
@@ -18,7 +19,7 @@ export function useListMutualGroupsQuery({
|
||||
enabled?: boolean
|
||||
limit?: number
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const isEnabled = enabled !== false && !!subject
|
||||
|
||||
return useInfiniteQuery({
|
||||
@@ -27,11 +28,11 @@ export function useListMutualGroupsQuery({
|
||||
enabled: isEnabled,
|
||||
queryKey: createListMutualGroupsQueryKey({subject: subject ?? ''}),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const {data} = await agent.chat.bsky.group.listMutualGroups(
|
||||
{subject: subject!, cursor: pageParam, limit},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return data
|
||||
return await chatClient.call(chat.bsky.group.listMutualGroups, {
|
||||
subject: subject! as DidString,
|
||||
cursor: pageParam,
|
||||
limit,
|
||||
})
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: page => page.cursor,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {ChatBskyConvoDefs, type ChatBskyConvoLockConvo} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -15,7 +15,7 @@ export function useLockConvo(
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (
|
||||
data: ChatBskyConvoLockConvo.OutputSchema,
|
||||
data: chat.bsky.convo.lockConvo.$OutputBody,
|
||||
variables: {lock: boolean; silent?: boolean},
|
||||
) => void
|
||||
onError?: (
|
||||
@@ -25,29 +25,26 @@ export function useLockConvo(
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({lock}: {lock: boolean; silent?: boolean}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
if (lock) {
|
||||
const {data} = await agent.chat.bsky.convo.lockConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.lockConvo, {convoId})
|
||||
return data
|
||||
} else {
|
||||
const {data} = await agent.chat.bsky.convo.unlockConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.unlockConvo, {
|
||||
convoId,
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
onMutate: ({lock}) => {
|
||||
if (!convoId) return
|
||||
return updateConvoOptimistic(queryClient, convoId, prev => {
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind))
|
||||
return undefined
|
||||
return {
|
||||
...prev,
|
||||
kind: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {
|
||||
type ConvoListQueryData,
|
||||
@@ -12,26 +12,25 @@ import {
|
||||
|
||||
export function useMarkJoinRequestsRead(convoId: string | undefined) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
await agent.chat.bsky.group.updateJoinRequestsRead(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
await chatClient.call(chat.bsky.group.updateJoinRequestsRead, {convoId})
|
||||
},
|
||||
onMutate: () => {
|
||||
if (!convoId) return
|
||||
|
||||
const prevConvo = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView | undefined>(
|
||||
const prevConvo =
|
||||
queryClient.getQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView | undefined>(
|
||||
CONVO_KEY(convoId),
|
||||
old => {
|
||||
if (!old || !ChatBskyConvoDefs.isGroupConvo(old.kind)) return old
|
||||
if (!old || !bsky.isType(chat.bsky.convo.defs.groupConvo, old.kind))
|
||||
return old
|
||||
return {
|
||||
...old,
|
||||
kind: {...old.kind, unreadJoinRequestCount: 0},
|
||||
@@ -53,7 +52,7 @@ export function useMarkJoinRequestsRead(convoId: string | undefined) {
|
||||
convos: page.convos.map(convo => {
|
||||
if (
|
||||
convo.id !== convoId ||
|
||||
!ChatBskyConvoDefs.isGroupConvo(convo.kind)
|
||||
!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)
|
||||
) {
|
||||
return convo
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {type ChatBskyConvoMuteConvo} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {
|
||||
rollbackConvoOptimistic,
|
||||
updateConvoOptimistic,
|
||||
@@ -14,27 +13,23 @@ export function useMuteConvo(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyConvoMuteConvo.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.convo.muteConvo.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({mute}: {mute: boolean}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
if (mute) {
|
||||
const {data} = await agent.chat.bsky.convo.muteConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.muteConvo, {convoId})
|
||||
return data
|
||||
} else {
|
||||
const {data} = await agent.chat.bsky.convo.unmuteConvo(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.unmuteConvo, {
|
||||
convoId,
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import {
|
||||
type ChatBskyActorDefs,
|
||||
ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
type ChatBskyGroupRemoveMembers,
|
||||
} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
|
||||
import {listConvoMembersQueryKey} from './list-convo-members'
|
||||
@@ -23,42 +19,43 @@ export function useRemoveFromGroupChat(
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupRemoveMembers.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.removeMembers.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({members}: {members: string[]}) => {
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
const {data} = await agent.chat.bsky.group.removeMembers(
|
||||
{convoId, members},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.group.removeMembers, {
|
||||
convoId,
|
||||
members: members as DidString[],
|
||||
})
|
||||
return data
|
||||
},
|
||||
onMutate: ({members}) => {
|
||||
if (!convoId) return
|
||||
|
||||
const prevConvo = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
const prevConvo =
|
||||
queryClient.getQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
const prevListEntries = queryClient.getQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]})
|
||||
const prevMemberList = queryClient.getQueryData<
|
||||
ChatBskyActorDefs.ProfileViewBasic[]
|
||||
chat.bsky.actor.defs.ProfileViewBasic[]
|
||||
>(listConvoMembersQueryKey(convoId))
|
||||
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
prev => {
|
||||
if (!prev) return
|
||||
const nextMembers = prev.members.filter(m => !members.includes(m.did))
|
||||
const removed = prev.members.length - nextMembers.length
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) {
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) {
|
||||
return {...prev, members: nextMembers}
|
||||
}
|
||||
return {
|
||||
@@ -73,7 +70,7 @@ export function useRemoveFromGroupChat(
|
||||
)
|
||||
|
||||
queryClient.setQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]}, prev => {
|
||||
if (!prev?.pages) return
|
||||
return {
|
||||
@@ -86,7 +83,7 @@ export function useRemoveFromGroupChat(
|
||||
m => !members.includes(m.did),
|
||||
)
|
||||
const removed = convo.members.length - nextMembers.length
|
||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) {
|
||||
return {...convo, members: nextMembers}
|
||||
}
|
||||
return {
|
||||
@@ -102,7 +99,7 @@ export function useRemoveFromGroupChat(
|
||||
}
|
||||
})
|
||||
|
||||
queryClient.setQueryData<ChatBskyActorDefs.ProfileViewBasic[]>(
|
||||
queryClient.setQueryData<chat.bsky.actor.defs.ProfileViewBasic[]>(
|
||||
listConvoMembersQueryKey(convoId),
|
||||
prev => {
|
||||
if (!prev) return
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import {type ChatBskyGroupRequestJoin} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {RQKEY_ROOT as REQUESTS_RQKEY_ROOT} from './list-conversation-requests'
|
||||
|
||||
export function useRequestJoinGroupChat({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupRequestJoin.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.requestJoin.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
@@ -22,11 +21,8 @@ export function useRequestJoinGroupChat({
|
||||
if (!hasSession) throw new Error('Must be logged in to join')
|
||||
if (!code) throw new Error('No invite code')
|
||||
|
||||
const res = await agent.chat.bsky.group.requestJoin(
|
||||
{code},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return res.data
|
||||
const res = await chatClient.call(chat.bsky.group.requestJoin, {code})
|
||||
return res
|
||||
},
|
||||
onSuccess: data => {
|
||||
void queryClient.invalidateQueries({queryKey: [REQUESTS_RQKEY_ROOT]})
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {type ChatBskyActorDeclaration} from '@atproto/api'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {logger} from '#/logger'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {agentToLexClient} from '#/state/session/clients'
|
||||
import {
|
||||
getDidFromAgentSession,
|
||||
getOtherRequiredDataFromCache,
|
||||
setOtherRequiredDataActorDeclarationCache,
|
||||
} from '#/ageAssurance/data'
|
||||
import {chat} from '#/lexicons'
|
||||
|
||||
/**
|
||||
* Updates the chat actor declaration record to restrict who can contact the
|
||||
@@ -49,7 +51,7 @@ export async function restrictChatSettings({
|
||||
)
|
||||
}
|
||||
|
||||
const record: ChatBskyActorDeclaration.Main = {
|
||||
const record: chat.bsky.actor.declaration.Main = {
|
||||
$type: 'chat.bsky.actor.declaration',
|
||||
allowIncoming: restrictIncoming
|
||||
? 'none'
|
||||
@@ -67,13 +69,23 @@ export async function restrictChatSettings({
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
* Callers thread a bridge `SessionAgent` (session-core / birthdate); wrap it
|
||||
* as an account lex `Client` so the record write goes through the lex path.
|
||||
* The cast is safe: `agentToLexClient` only reads `did` and `fetchHandler`,
|
||||
* both of which the base `Agent` provides - its `AtpAgent` parameter type is
|
||||
* just narrower than it needs. TODO(phase4): take a Client directly once the
|
||||
* bridge is removed.
|
||||
*/
|
||||
const client = agentToLexClient(
|
||||
agent as unknown as Parameters<typeof agentToLexClient>[0],
|
||||
)
|
||||
|
||||
try {
|
||||
await networkRetry(3, () =>
|
||||
agent.com.atproto.repo.putRecord({
|
||||
repo: did,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
client.put(chat.bsky.actor.declaration, record, {
|
||||
repo: did as DidString,
|
||||
rkey: 'self',
|
||||
record,
|
||||
}),
|
||||
)
|
||||
// important, update local cache to avoid running this again
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {type ChatBskyConvoGetUnreadCounts} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useChatClient} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {RQKEY_PARTIAL as UNREAD_COUNTS_PARTIAL_KEY} from './get-unread-counts'
|
||||
import {
|
||||
type ConvoRequestListQueryData,
|
||||
@@ -29,14 +28,13 @@ export function useUpdateAllRead(
|
||||
},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const {data} = await agent.chat.bsky.convo.updateAllRead(
|
||||
{status},
|
||||
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||
)
|
||||
const data = await chatClient.call(chat.bsky.convo.updateAllRead, {
|
||||
status,
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
@@ -99,10 +97,12 @@ export function useUpdateAllRead(
|
||||
// zero out the badge count query that actually drives the unread badge,
|
||||
// since it's a separate server query that the list caches don't feed
|
||||
const prevUnreadCountsQueries =
|
||||
queryClient.getQueriesData<ChatBskyConvoGetUnreadCounts.OutputSchema>({
|
||||
queryKey: UNREAD_COUNTS_PARTIAL_KEY,
|
||||
})
|
||||
queryClient.setQueriesData<ChatBskyConvoGetUnreadCounts.OutputSchema>(
|
||||
queryClient.getQueriesData<chat.bsky.convo.getUnreadCounts.$OutputBody>(
|
||||
{
|
||||
queryKey: UNREAD_COUNTS_PARTIAL_KEY,
|
||||
},
|
||||
)
|
||||
queryClient.setQueriesData<chat.bsky.convo.getUnreadCounts.$OutputBody>(
|
||||
{queryKey: UNREAD_COUNTS_PARTIAL_KEY},
|
||||
old => {
|
||||
if (!old) return old
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import {
|
||||
type ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
type QueryKey,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {type chat} from '#/lexicons'
|
||||
import {RQKEY as CONVO_KEY} from '../conversation'
|
||||
import {RQKEY_ROOT as CONVO_LIST_KEY} from '../list-conversations'
|
||||
|
||||
type ConvoUpdater = (
|
||||
prev: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView | undefined
|
||||
prev: chat.bsky.convo.defs.ConvoView,
|
||||
) => chat.bsky.convo.defs.ConvoView | undefined
|
||||
|
||||
export type ConvoCacheSnapshot = {
|
||||
prevConvo: ChatBskyConvoDefs.ConvoView | undefined
|
||||
prevConvo: chat.bsky.convo.defs.ConvoView | undefined
|
||||
prevListEntries: Array<
|
||||
[QueryKey, InfiniteData<ChatBskyConvoListConvos.OutputSchema> | undefined]
|
||||
[QueryKey, InfiniteData<chat.bsky.convo.listConvos.$OutputBody> | undefined]
|
||||
>
|
||||
}
|
||||
|
||||
@@ -34,14 +31,14 @@ export function updateConvoOptimistic(
|
||||
convoId: string,
|
||||
updater: ConvoUpdater,
|
||||
): ConvoCacheSnapshot {
|
||||
const prevConvo = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
const prevConvo = queryClient.getQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
)
|
||||
const prevListEntries = queryClient.getQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]})
|
||||
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
prev => {
|
||||
if (!prev) return
|
||||
@@ -51,7 +48,7 @@ export function updateConvoOptimistic(
|
||||
)
|
||||
|
||||
queryClient.setQueriesData<
|
||||
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||
InfiniteData<chat.bsky.convo.listConvos.$OutputBody>
|
||||
>({queryKey: [CONVO_LIST_KEY]}, prev => {
|
||||
if (!prev?.pages) return
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {type ChatBskyGroupWithdrawJoinRequest} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useChatClient, useSession} from '#/state/session'
|
||||
import {chat} from '#/lexicons'
|
||||
import {
|
||||
type ConvoRequestListQueryData,
|
||||
optimisticDeleteJoinRequest,
|
||||
@@ -14,10 +13,10 @@ export function useWithdrawJoinGroupChatRequest({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: ChatBskyGroupWithdrawJoinRequest.OutputSchema) => void
|
||||
onSuccess?: (data: chat.bsky.group.withdrawJoinRequest.$OutputBody) => void
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const agent = useAgent()
|
||||
const chatClient = useChatClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
@@ -27,11 +26,10 @@ export function useWithdrawJoinGroupChatRequest({
|
||||
throw new Error('Must be logged in to withdraw a join request')
|
||||
if (!convoId) throw new Error('No convoId provided')
|
||||
|
||||
const res = await agent.chat.bsky.group.withdrawJoinRequest(
|
||||
{convoId},
|
||||
{headers: DM_SERVICE_HEADERS},
|
||||
)
|
||||
return res.data
|
||||
const res = await chatClient.call(chat.bsky.group.withdrawJoinRequest, {
|
||||
convoId,
|
||||
})
|
||||
return res
|
||||
},
|
||||
onSuccess: (data, {convoId}) => {
|
||||
queryClient.setQueriesData<ConvoRequestListQueryData>(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyActorDefs, type AppBskyGraphGetBlocks} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -6,28 +5,28 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'my-blocked-accounts'
|
||||
export const RQKEY = () => [RQKEY_ROOT]
|
||||
type RQPageParam = string | undefined
|
||||
|
||||
export function useMyBlockedAccountsQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetBlocks.OutputSchema,
|
||||
app.bsky.graph.getBlocks.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetBlocks.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getBlocks.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getBlocks({
|
||||
return await client.call(app.bsky.graph.getBlocks, {
|
||||
limit: 30,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -37,9 +36,9 @@ export function useMyBlockedAccountsQuery() {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetBlocks.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getBlocks.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {accumulate} from '#/lib/async/accumulate'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export type MyListsFilter =
|
||||
| 'all'
|
||||
@@ -16,50 +17,50 @@ export const RQKEY = (filter: MyListsFilter) => [RQKEY_ROOT, filter]
|
||||
|
||||
export function useMyListsQuery(filter: MyListsFilter) {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
return useQuery<AppBskyGraphDefs.ListView[]>({
|
||||
const client = useAppviewClient()
|
||||
return useQuery<app.bsky.graph.defs.ListView[]>({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(filter),
|
||||
async queryFn() {
|
||||
let lists: AppBskyGraphDefs.ListView[] = []
|
||||
let lists: app.bsky.graph.defs.ListView[] = []
|
||||
const promises = [
|
||||
accumulate(cursor =>
|
||||
agent.app.bsky.graph
|
||||
.getLists({
|
||||
actor: currentAccount!.did,
|
||||
client
|
||||
.call(app.bsky.graph.getLists, {
|
||||
actor: currentAccount!.did as AtIdentifierString,
|
||||
cursor,
|
||||
limit: 50,
|
||||
})
|
||||
.then(res => ({
|
||||
cursor: res.data.cursor,
|
||||
items: res.data.lists,
|
||||
cursor: res.cursor,
|
||||
items: res.lists,
|
||||
})),
|
||||
),
|
||||
]
|
||||
if (filter === 'all-including-subscribed' || filter === 'mod') {
|
||||
promises.push(
|
||||
accumulate(cursor =>
|
||||
agent.app.bsky.graph
|
||||
.getListMutes({
|
||||
client
|
||||
.call(app.bsky.graph.getListMutes, {
|
||||
cursor,
|
||||
limit: 50,
|
||||
})
|
||||
.then(res => ({
|
||||
cursor: res.data.cursor,
|
||||
items: res.data.lists,
|
||||
cursor: res.cursor,
|
||||
items: res.lists,
|
||||
})),
|
||||
),
|
||||
)
|
||||
promises.push(
|
||||
accumulate(cursor =>
|
||||
agent.app.bsky.graph
|
||||
.getListBlocks({
|
||||
client
|
||||
.call(app.bsky.graph.getListBlocks, {
|
||||
cursor,
|
||||
limit: 50,
|
||||
})
|
||||
.then(res => ({
|
||||
cursor: res.data.cursor,
|
||||
items: res.data.lists,
|
||||
cursor: res.cursor,
|
||||
items: res.lists,
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {type AppBskyActorDefs, type AppBskyGraphGetMutes} from '@atproto/api'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -6,28 +5,28 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'my-muted-accounts'
|
||||
export const RQKEY = () => [RQKEY_ROOT]
|
||||
type RQPageParam = string | undefined
|
||||
|
||||
export function useMyMutedAccountsQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetMutes.OutputSchema,
|
||||
app.bsky.graph.getMutes.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetMutes.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getMutes.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getMutes({
|
||||
return await client.call(app.bsky.graph.getMutes, {
|
||||
limit: 30,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -37,9 +36,9 @@ export function useMyMutedAccountsQuery() {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetMutes.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getMutes.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -17,12 +17,8 @@
|
||||
*/
|
||||
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -33,9 +29,10 @@ import {
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
didOrHandleUriMatches,
|
||||
embedViewRecordToPostView,
|
||||
@@ -60,7 +57,7 @@ export function useNotificationFeedQuery(opts: {
|
||||
enabled?: boolean
|
||||
filter: 'all' | 'mentions'
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const unreads = useUnreadNotificationsApi()
|
||||
@@ -106,7 +103,7 @@ export function useNotificationFeedQuery(opts: {
|
||||
]
|
||||
}
|
||||
const {page: fetchedPage} = await fetchPage({
|
||||
agent,
|
||||
client,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
queryClient,
|
||||
@@ -199,7 +196,9 @@ export function useNotificationFeedQuery(opts: {
|
||||
* a `$type` field on the `subject`. But if the nested
|
||||
* `record` is a post, we know it's a post view.
|
||||
*/
|
||||
if (AppBskyFeedPost.isRecord(item.subject?.record)) {
|
||||
if (
|
||||
bsky.isType(app.bsky.feed.post, item.subject?.record)
|
||||
) {
|
||||
const mod = moderatePost(item.subject, moderationOpts!)
|
||||
if (mod.ui('contentList').filter) {
|
||||
return false
|
||||
@@ -276,7 +275,7 @@ export function useNotificationFeedQuery(opts: {
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, void> {
|
||||
): Generator<app.bsky.feed.defs.PostView, void> {
|
||||
const atUri = new AtUri(uri)
|
||||
|
||||
const queryDatas = queryClient.getQueriesData<InfiniteData<FeedPage>>({
|
||||
@@ -295,10 +294,14 @@ export function* findAllPostsInQueryData(
|
||||
}
|
||||
}
|
||||
|
||||
if (AppBskyFeedDefs.isPostView(item.subject)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) {
|
||||
const quotedPost = getEmbeddedPost(item.subject?.embed)
|
||||
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
|
||||
yield embedViewRecordToPostView(quotedPost)
|
||||
// TODO(phase4): drop toLex once ../util is migrated and
|
||||
// embedViewRecordToPostView returns the lexicon PostView.
|
||||
yield bsky.toLex<app.bsky.feed.defs.PostView>(
|
||||
embedViewRecordToPostView(quotedPost),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,7 +336,7 @@ export function* findAllProfilesInQueryData(
|
||||
) {
|
||||
yield item.subject.author
|
||||
}
|
||||
if (AppBskyFeedDefs.isPostView(item.subject)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) {
|
||||
const quotedPost = getEmbeddedPost(item.subject?.embed)
|
||||
if (quotedPost?.author.did === did) {
|
||||
yield quotedPost.author
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type AppBskyNotificationDefs,
|
||||
type ChatBskyNotificationDefs,
|
||||
} from '@atproto/api'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type QueryClient,
|
||||
@@ -10,10 +6,10 @@ import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient, useChatClient} from '#/state/session'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {app, chat} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'notification-settings'
|
||||
const RQKEY_APP = [RQKEY_ROOT, 'app']
|
||||
@@ -24,18 +20,18 @@ const RQKEY_CHAT = [RQKEY_ROOT, 'chat']
|
||||
// fetched and cached separately. This combined type names every preference for
|
||||
// the generic settings dialog, but it is never the shape of a query response.
|
||||
export type NotificationSettingsPreferences = Omit<
|
||||
AppBskyNotificationDefs.Preferences,
|
||||
app.bsky.notification.defs.Preferences,
|
||||
'chat'
|
||||
> &
|
||||
Partial<Pick<ChatBskyNotificationDefs.Preferences, 'chat' | 'chatRequest'>>
|
||||
Partial<Pick<chat.bsky.notification.defs.Preferences, 'chat' | 'chatRequest'>>
|
||||
|
||||
export type AppNotificationSettingsPreferences = Omit<
|
||||
AppBskyNotificationDefs.Preferences,
|
||||
app.bsky.notification.defs.Preferences,
|
||||
'chat'
|
||||
>
|
||||
|
||||
export type ChatNotificationSettingsPreferences = Pick<
|
||||
ChatBskyNotificationDefs.Preferences,
|
||||
chat.bsky.notification.defs.Preferences,
|
||||
'chat' | 'chatRequest'
|
||||
>
|
||||
|
||||
@@ -45,9 +41,9 @@ export type NotificationSettingsPreferenceName = Exclude<
|
||||
>
|
||||
|
||||
export type NotificationSettingsPreference =
|
||||
| AppBskyNotificationDefs.Preference
|
||||
| AppBskyNotificationDefs.FilterablePreference
|
||||
| ChatBskyNotificationDefs.ChatPreference
|
||||
| app.bsky.notification.defs.Preference
|
||||
| app.bsky.notification.defs.FilterablePreference
|
||||
| chat.bsky.notification.defs.ChatPreference
|
||||
|
||||
export function isChatPreferenceName(
|
||||
name: NotificationSettingsPreferenceName,
|
||||
@@ -58,7 +54,7 @@ export function isChatPreferenceName(
|
||||
type NotificationSettingsUpdate = Partial<NotificationSettingsPreferences>
|
||||
|
||||
type AppNotificationSettingsUpdate = Partial<
|
||||
Omit<AppBskyNotificationDefs.Preferences, '$type' | 'chat'>
|
||||
Omit<app.bsky.notification.defs.Preferences, '$type' | 'chat'>
|
||||
>
|
||||
|
||||
type ChatNotificationSettingsUpdate =
|
||||
@@ -67,13 +63,13 @@ type ChatNotificationSettingsUpdate =
|
||||
export function useNotificationSettingsQuery({
|
||||
enabled,
|
||||
}: {enabled?: boolean} = {}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: RQKEY_APP,
|
||||
queryFn: async (): Promise<AppNotificationSettingsPreferences> => {
|
||||
const res = await agent.app.bsky.notification.getPreferences()
|
||||
return appPreferencesWithoutChat(res.data.preferences)
|
||||
const res = await client.call(app.bsky.notification.getPreferences)
|
||||
return appPreferencesWithoutChat(res.preferences)
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
@@ -82,21 +78,20 @@ export function useNotificationSettingsQuery({
|
||||
export function useChatNotificationSettingsQuery({
|
||||
enabled,
|
||||
}: {enabled?: boolean} = {}) {
|
||||
const agent = useAgent()
|
||||
const client = useChatClient()
|
||||
|
||||
return useQuery({
|
||||
queryKey: RQKEY_CHAT,
|
||||
queryFn: async (): Promise<ChatNotificationSettingsPreferences> => {
|
||||
const res = await agent.chat.bsky.notification.getPreferences(undefined, {
|
||||
headers: DM_SERVICE_HEADERS,
|
||||
})
|
||||
return chatPreferencesForSettings(res.data.preferences)
|
||||
const res = await client.call(chat.bsky.notification.getPreferences)
|
||||
return chatPreferencesForSettings(res.preferences)
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
export function useNotificationSettingsUpdateMutation() {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const chatClient = useChatClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
@@ -104,13 +99,13 @@ export function useNotificationSettingsUpdateMutation() {
|
||||
const {appUpdate, chatUpdate} = splitNotificationSettingsUpdate(update)
|
||||
await Promise.all([
|
||||
hasUpdates(appUpdate)
|
||||
? agent.app.bsky.notification.putPreferencesV2(appUpdate)
|
||||
? appviewClient.call(
|
||||
app.bsky.notification.putPreferencesV2,
|
||||
appUpdate,
|
||||
)
|
||||
: undefined,
|
||||
hasUpdates(chatUpdate)
|
||||
? agent.chat.bsky.notification.putPreferences(chatUpdate, {
|
||||
headers: DM_SERVICE_HEADERS,
|
||||
encoding: 'application/json',
|
||||
})
|
||||
? chatClient.call(chat.bsky.notification.putPreferences, chatUpdate)
|
||||
: undefined,
|
||||
])
|
||||
},
|
||||
@@ -156,15 +151,15 @@ function optimisticUpdateNotificationSettings(
|
||||
}
|
||||
|
||||
function appPreferencesWithoutChat(
|
||||
preferences: AppBskyNotificationDefs.Preferences,
|
||||
): Omit<AppBskyNotificationDefs.Preferences, 'chat'> {
|
||||
preferences: app.bsky.notification.defs.Preferences,
|
||||
): Omit<app.bsky.notification.defs.Preferences, 'chat'> {
|
||||
const {chat: _ignoredChat, ...appPreferences} = preferences
|
||||
return appPreferences
|
||||
}
|
||||
|
||||
function chatPreferencesForSettings(
|
||||
preferences: ChatBskyNotificationDefs.Preferences,
|
||||
): Pick<ChatBskyNotificationDefs.Preferences, 'chat' | 'chatRequest'> {
|
||||
preferences: chat.bsky.notification.defs.Preferences,
|
||||
): Pick<chat.bsky.notification.defs.Preferences, 'chat' | 'chatRequest'> {
|
||||
return {
|
||||
chat: preferences.chat,
|
||||
chatRequest: preferences.chatRequest,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyGraphDefs,
|
||||
type AppBskyNotificationListNotifications,
|
||||
} from '@atproto/api'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type NotificationType =
|
||||
| StarterPackNotificationType
|
||||
@@ -11,11 +7,11 @@ export type NotificationType =
|
||||
export type FeedNotification =
|
||||
| (FeedNotificationBase & {
|
||||
type: StarterPackNotificationType
|
||||
subject?: AppBskyGraphDefs.StarterPackViewBasic
|
||||
subject?: app.bsky.graph.defs.StarterPackViewBasic
|
||||
})
|
||||
| (FeedNotificationBase & {
|
||||
type: OtherNotificationType
|
||||
subject?: AppBskyFeedDefs.PostView
|
||||
subject?: app.bsky.feed.defs.PostView
|
||||
})
|
||||
|
||||
export interface FeedPage {
|
||||
@@ -54,8 +50,10 @@ type OtherNotificationType =
|
||||
|
||||
type FeedNotificationBase = {
|
||||
_reactKey: string
|
||||
notification: AppBskyNotificationListNotifications.Notification
|
||||
additional?: AppBskyNotificationListNotifications.Notification[]
|
||||
notification: app.bsky.notification.listNotifications.Notification
|
||||
additional?: app.bsky.notification.listNotifications.Notification[]
|
||||
subjectUri?: string
|
||||
subject?: AppBskyFeedDefs.PostView | AppBskyGraphDefs.StarterPackViewBasic
|
||||
subject?:
|
||||
| app.bsky.feed.defs.PostView
|
||||
| app.bsky.graph.defs.StarterPackViewBasic
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import {AppState} from 'react-native'
|
||||
import {type DatetimeString} from '@atproto/syntax'
|
||||
import {updateSeenNotifications} from '@bsky.app/sdk'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
|
||||
@@ -18,7 +20,7 @@ import BroadcastChannel from '#/lib/broadcast'
|
||||
import {resetBadgeCount} from '#/lib/notifications/notifications'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import {RQKEY as RQKEY_NOTIFS} from './feed'
|
||||
import {type CachedFeedPage, type FeedPage} from './types'
|
||||
import {fetchPage} from './util'
|
||||
@@ -52,7 +54,8 @@ apiContext.displayName = 'NotificationsUnreadApiContext'
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const {hasSession} = useSession()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
@@ -120,8 +123,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
return {
|
||||
async markAllRead() {
|
||||
// update server
|
||||
await agent.updateSeenNotifications(
|
||||
cacheRef.current.syncedAt.toISOString(),
|
||||
await pdsClient.call(
|
||||
updateSeenNotifications,
|
||||
// toISOString() always yields a valid datetime string
|
||||
cacheRef.current.syncedAt.toISOString() as DatetimeString,
|
||||
)
|
||||
|
||||
// update & broadcast
|
||||
@@ -135,7 +140,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
isPoll,
|
||||
}: {invalidate?: boolean; isPoll?: boolean} = {}) {
|
||||
try {
|
||||
if (!agent.session) return
|
||||
if (!hasSession) return
|
||||
if (AppState.currentState !== 'active') {
|
||||
return
|
||||
}
|
||||
@@ -156,7 +161,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
|
||||
// count
|
||||
const {page, indexedAt: lastIndexed} = await fetchPage({
|
||||
agent,
|
||||
client: appviewClient,
|
||||
cursor: undefined,
|
||||
limit: 40,
|
||||
queryClient,
|
||||
@@ -207,7 +212,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
}
|
||||
},
|
||||
}
|
||||
}, [setNumUnread, queryClient, moderationOpts, agent])
|
||||
}, [
|
||||
setNumUnread,
|
||||
queryClient,
|
||||
moderationOpts,
|
||||
appviewClient,
|
||||
pdsClient,
|
||||
hasSession,
|
||||
])
|
||||
checkUnreadRef.current = api.checkUnread
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyFeedLike,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedRepost,
|
||||
type AppBskyGraphDefs,
|
||||
AppBskyGraphStarterpack,
|
||||
type AppBskyNotificationListNotifications,
|
||||
hasMutedWord,
|
||||
moderateNotification,
|
||||
type ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
import chunk from 'lodash.chunk'
|
||||
|
||||
import {labelIsHideableOffense} from '#/lib/moderation'
|
||||
import {type SessionAgent} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {precacheProfile} from '../profile'
|
||||
import {
|
||||
@@ -38,7 +33,7 @@ const MS_2DAY = MS_1HR * 48
|
||||
// =
|
||||
|
||||
export async function fetchPage({
|
||||
agent,
|
||||
client,
|
||||
cursor,
|
||||
limit,
|
||||
queryClient,
|
||||
@@ -46,7 +41,7 @@ export async function fetchPage({
|
||||
fetchAdditionalData,
|
||||
reasons,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
client: Client
|
||||
cursor: string | undefined
|
||||
limit: number
|
||||
queryClient: QueryClient
|
||||
@@ -57,16 +52,16 @@ export async function fetchPage({
|
||||
page: FeedPage
|
||||
indexedAt: string | undefined
|
||||
}> {
|
||||
const res = await agent.listNotifications({
|
||||
const res = await client.call(app.bsky.notification.listNotifications, {
|
||||
limit,
|
||||
cursor,
|
||||
reasons,
|
||||
})
|
||||
|
||||
const indexedAt = res.data.notifications[0]?.indexedAt
|
||||
const indexedAt = res.notifications[0]?.indexedAt
|
||||
|
||||
// filter out notifs by mod rules
|
||||
const notifs = res.data.notifications.filter(
|
||||
const notifs = res.notifications.filter(
|
||||
notif => !shouldFilterNotif(notif, moderationOpts),
|
||||
)
|
||||
|
||||
@@ -76,7 +71,7 @@ export async function fetchPage({
|
||||
// we fetch subjects of notifications (usually posts) now instead of lazily
|
||||
// in the UI to avoid relayouts
|
||||
if (fetchAdditionalData) {
|
||||
const subjects = await fetchSubjects(agent, notifsGrouped)
|
||||
const subjects = await fetchSubjects(client, notifsGrouped)
|
||||
for (const notif of notifsGrouped) {
|
||||
if (notif.subjectUri) {
|
||||
if (
|
||||
@@ -96,17 +91,17 @@ export async function fetchPage({
|
||||
}
|
||||
}
|
||||
|
||||
let seenAt = res.data.seenAt ? new Date(res.data.seenAt) : new Date()
|
||||
let seenAt = res.seenAt ? new Date(res.seenAt) : new Date()
|
||||
if (Number.isNaN(seenAt.getTime())) {
|
||||
seenAt = new Date()
|
||||
}
|
||||
|
||||
return {
|
||||
page: {
|
||||
cursor: res.data.cursor,
|
||||
cursor: res.cursor,
|
||||
seenAt,
|
||||
items: notifsGrouped,
|
||||
priority: res.data.priority ?? false,
|
||||
priority: res.priority ?? false,
|
||||
},
|
||||
indexedAt,
|
||||
}
|
||||
@@ -116,7 +111,7 @@ export async function fetchPage({
|
||||
// =
|
||||
|
||||
export function shouldFilterNotif(
|
||||
notif: AppBskyNotificationListNotifications.Notification,
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
moderationOpts: ModerationOpts | undefined,
|
||||
): boolean {
|
||||
const containsImperative = !!notif.author.labels?.some(labelIsHideableOffense)
|
||||
@@ -128,10 +123,7 @@ export function shouldFilterNotif(
|
||||
}
|
||||
if (
|
||||
notif.reason === 'subscribed-post' &&
|
||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||
notif.record,
|
||||
AppBskyFeedPost.isRecord,
|
||||
) &&
|
||||
bsky.isType(app.bsky.feed.post, notif.record) &&
|
||||
hasMutedWord({
|
||||
mutedWords: moderationOpts.prefs.mutedWords,
|
||||
text: notif.record.text,
|
||||
@@ -150,7 +142,7 @@ export function shouldFilterNotif(
|
||||
}
|
||||
|
||||
export function groupNotifications(
|
||||
notifs: AppBskyNotificationListNotifications.Notification[],
|
||||
notifs: app.bsky.notification.listNotifications.Notification[],
|
||||
): FeedNotification[] {
|
||||
const groupedNotifs: FeedNotification[] = []
|
||||
for (const notif of notifs) {
|
||||
@@ -207,17 +199,21 @@ export function groupNotifications(
|
||||
}
|
||||
|
||||
async function fetchSubjects(
|
||||
agent: SessionAgent,
|
||||
client: Client,
|
||||
groupedNotifs: FeedNotification[],
|
||||
): Promise<{
|
||||
posts: Map<string, AppBskyFeedDefs.PostView>
|
||||
starterPacks: Map<string, AppBskyGraphDefs.StarterPackViewBasic>
|
||||
posts: Map<string, app.bsky.feed.defs.PostView>
|
||||
starterPacks: Map<string, app.bsky.graph.defs.StarterPackViewBasic>
|
||||
}> {
|
||||
const postUris = new Set<string>()
|
||||
const packUris = new Set<string>()
|
||||
/*
|
||||
* Subject/reason-subject URIs arrive as plain `string` on the notification,
|
||||
* so brand them to the at-uri slot the getPosts/getStarterPacks params expect.
|
||||
*/
|
||||
const postUris = new Set<AtUriString>()
|
||||
const packUris = new Set<AtUriString>()
|
||||
for (const notif of groupedNotifs) {
|
||||
if (notif.subjectUri?.includes('app.bsky.feed.post')) {
|
||||
postUris.add(notif.subjectUri)
|
||||
postUris.add(notif.subjectUri as AtUriString)
|
||||
} else if (
|
||||
notif.notification.reasonSubject?.includes('app.bsky.graph.starterpack')
|
||||
) {
|
||||
@@ -228,25 +224,25 @@ async function fetchSubjects(
|
||||
const packUriChunks = chunk(Array.from(packUris), 25)
|
||||
const postsChunks = await Promise.all(
|
||||
postUriChunks.map(uris =>
|
||||
agent.app.bsky.feed.getPosts({uris}).then(res => res.data.posts),
|
||||
client.call(app.bsky.feed.getPosts, {uris}).then(res => res.posts),
|
||||
),
|
||||
)
|
||||
const packsChunks = await Promise.all(
|
||||
packUriChunks.map(uris =>
|
||||
agent.app.bsky.graph
|
||||
.getStarterPacks({uris})
|
||||
.then(res => res.data.starterPacks),
|
||||
client
|
||||
.call(app.bsky.graph.getStarterPacks, {uris})
|
||||
.then(res => res.starterPacks),
|
||||
),
|
||||
)
|
||||
const postsMap = new Map<string, AppBskyFeedDefs.PostView>()
|
||||
const packsMap = new Map<string, AppBskyGraphDefs.StarterPackViewBasic>()
|
||||
const postsMap = new Map<string, app.bsky.feed.defs.PostView>()
|
||||
const packsMap = new Map<string, app.bsky.graph.defs.StarterPackViewBasic>()
|
||||
for (const post of postsChunks.flat()) {
|
||||
if (AppBskyFeedPost.isRecord(post.record)) {
|
||||
if (bsky.isType(app.bsky.feed.post, post.record)) {
|
||||
postsMap.set(post.uri, post)
|
||||
}
|
||||
}
|
||||
for (const pack of packsChunks.flat()) {
|
||||
if (AppBskyGraphStarterpack.isRecord(pack.record)) {
|
||||
if (bsky.isType(app.bsky.graph.starterpack, pack.record)) {
|
||||
packsMap.set(pack.uri, pack)
|
||||
}
|
||||
}
|
||||
@@ -257,7 +253,7 @@ async function fetchSubjects(
|
||||
}
|
||||
|
||||
function toKnownType(
|
||||
notif: AppBskyNotificationListNotifications.Notification,
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
): NotificationType {
|
||||
if (notif.reason === 'like') {
|
||||
if (notif.reasonSubject?.includes('feed.generator')) {
|
||||
@@ -286,7 +282,7 @@ function toKnownType(
|
||||
|
||||
function getSubjectUri(
|
||||
type: NotificationType,
|
||||
notif: AppBskyNotificationListNotifications.Notification,
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
): string | undefined {
|
||||
if (
|
||||
type === 'reply' ||
|
||||
@@ -302,14 +298,8 @@ function getSubjectUri(
|
||||
type === 'repost-via-repost'
|
||||
) {
|
||||
if (
|
||||
bsky.dangerousIsType<AppBskyFeedRepost.Record>(
|
||||
notif.record,
|
||||
AppBskyFeedRepost.isRecord,
|
||||
) ||
|
||||
bsky.dangerousIsType<AppBskyFeedLike.Record>(
|
||||
notif.record,
|
||||
AppBskyFeedLike.isRecord,
|
||||
)
|
||||
bsky.isType(app.bsky.feed.repost, notif.record) ||
|
||||
bsky.isType(app.bsky.feed.like, notif.record)
|
||||
) {
|
||||
return typeof notif.record.subject?.uri === 'string'
|
||||
? notif.record.subject?.uri
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type Data = Record<string, unknown> | undefined
|
||||
|
||||
export type BaseNux<
|
||||
T extends Pick<AppBskyActorDefs.Nux, 'id' | 'expiresAt'> & {data: Data},
|
||||
> = Pick<AppBskyActorDefs.Nux, 'id' | 'completed' | 'expiresAt'> & T
|
||||
T extends Pick<app.bsky.actor.defs.Nux, 'id' | 'expiresAt'> & {data: Data},
|
||||
> = Pick<app.bsky.actor.defs.Nux, 'id' | 'completed' | 'expiresAt'> & T
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {type AppBskyActorDefs, nuxSchema} from '@atproto/api'
|
||||
import {nuxSchema} from '@bsky.app/sdk/utils'
|
||||
|
||||
import {
|
||||
type AppNux,
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
nuxNames,
|
||||
NuxSchemas,
|
||||
} from '#/state/queries/nuxs/definitions'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export function parseAppNux(nux: AppBskyActorDefs.Nux): AppNux | undefined {
|
||||
export function parseAppNux(nux: app.bsky.actor.defs.Nux): AppNux | undefined {
|
||||
if (!nuxNames.has(nux.id as Nux)) return
|
||||
if (!nuxSchema.safeParse(nux).success) return
|
||||
|
||||
@@ -32,11 +33,11 @@ export function parseAppNux(nux: AppBskyActorDefs.Nux): AppNux | undefined {
|
||||
} as AppNux
|
||||
}
|
||||
|
||||
export function serializeAppNux(nux: AppNux): AppBskyActorDefs.Nux {
|
||||
export function serializeAppNux(nux: AppNux): app.bsky.actor.defs.Nux {
|
||||
const {data, ...rest} = nux
|
||||
const schema = NuxSchemas[nux.id]
|
||||
|
||||
const result: AppBskyActorDefs.Nux = {
|
||||
const result: app.bsky.actor.defs.Nux = {
|
||||
...rest,
|
||||
data: undefined,
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {AppState} from 'react-native'
|
||||
import {type AtIdentifierString, type Client} from '@atproto/lex-client'
|
||||
import {AtUri, type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
type AppBskyFeedPost,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
type ModerationDecision,
|
||||
type ModerationPrefs,
|
||||
} from '@atproto/api'
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -32,9 +30,11 @@ import {DISCOVER_FEED_URI} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {KnownError} from '#/view/com/posts/PostFeedErrorMessage'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {useFeedTuners} from '../preferences/feed-tuners'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {usePreferencesQuery} from './preferences'
|
||||
@@ -79,10 +79,10 @@ export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) {
|
||||
export interface FeedPostSliceItem {
|
||||
_reactKey: string
|
||||
uri: string
|
||||
post: AppBskyFeedDefs.PostView
|
||||
record: AppBskyFeedPost.Record
|
||||
post: app.bsky.feed.defs.PostView
|
||||
record: app.bsky.feed.post.Main
|
||||
moderation: ModerationDecision
|
||||
parentAuthor?: AppBskyActorDefs.ProfileViewBasic
|
||||
parentAuthor?: app.bsky.actor.defs.ProfileViewBasic
|
||||
isParentBlocked?: boolean
|
||||
isParentNotFound?: boolean
|
||||
}
|
||||
@@ -97,8 +97,8 @@ export interface FeedPostSlice {
|
||||
reqId: string | undefined
|
||||
feedPostUri: string
|
||||
reason?:
|
||||
| AppBskyFeedDefs.ReasonRepost
|
||||
| AppBskyFeedDefs.ReasonPin
|
||||
| app.bsky.feed.defs.ReasonRepost
|
||||
| app.bsky.feed.defs.ReasonPin
|
||||
| ReasonFeedSource
|
||||
| {[k: string]: unknown; $type: string}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export interface FeedPostSlice {
|
||||
export interface FeedPageUnselected {
|
||||
api: FeedAPI
|
||||
cursor: string | undefined
|
||||
feed: AppBskyFeedDefs.FeedViewPost[]
|
||||
feed: app.bsky.feed.defs.FeedViewPost[]
|
||||
fetchedAt: number
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export function usePostFeedQuery(
|
||||
f => f.pinned && f.value === 'following',
|
||||
) ?? -1
|
||||
const enableFollowingToDiscoverFallback = followingPinnedIndex === 0
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const lastRun = useRef<{
|
||||
data: InfiniteData<FeedPageUnselected>
|
||||
args: typeof selectArgs
|
||||
@@ -192,7 +192,7 @@ export function usePostFeedQuery(
|
||||
feedDesc,
|
||||
feedParams: params || {},
|
||||
feedTuners,
|
||||
agent,
|
||||
client,
|
||||
// Not in the query key because they don't change:
|
||||
userInterests,
|
||||
// Not in the query key. Reacting to it switching isn't important:
|
||||
@@ -209,7 +209,7 @@ export function usePostFeedQuery(
|
||||
* moderations happen later, which results in some posts being shown and
|
||||
* some not.
|
||||
*/
|
||||
if (!agent.session) {
|
||||
if (!client.did) {
|
||||
assertSomePostsPassModeration(
|
||||
res.feed,
|
||||
preferences?.moderationPrefs ||
|
||||
@@ -287,7 +287,12 @@ export function usePostFeedQuery(
|
||||
.tune(page.feed)
|
||||
.map(slice => {
|
||||
const moderations = slice.items.map(item =>
|
||||
moderatePost(item.post, moderationOpts!),
|
||||
moderatePost(
|
||||
// TODO(phase4): drop toLex once feed-manip is migrated
|
||||
// off @atproto/api and yields lex-typed slice items.
|
||||
bsky.toLex<app.bsky.feed.defs.PostView>(item.post),
|
||||
moderationOpts!,
|
||||
),
|
||||
)
|
||||
|
||||
// apply moderation filter
|
||||
@@ -337,10 +342,20 @@ export function usePostFeedQuery(
|
||||
const feedPostSliceItem: FeedPostSliceItem = {
|
||||
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
|
||||
uri: item.post.uri,
|
||||
post: item.post,
|
||||
record: item.record,
|
||||
// TODO(phase4): drop toLex once feed-manip is migrated
|
||||
// off @atproto/api and yields lex-typed slice items.
|
||||
post: bsky.toLex<app.bsky.feed.defs.PostView>(
|
||||
item.post,
|
||||
),
|
||||
record: bsky.toLex<app.bsky.feed.post.Main>(
|
||||
item.record,
|
||||
),
|
||||
moderation: moderations[i],
|
||||
parentAuthor: item.parentAuthor,
|
||||
parentAuthor: item.parentAuthor
|
||||
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>(
|
||||
item.parentAuthor,
|
||||
)
|
||||
: undefined,
|
||||
isParentBlocked: item.isParentBlocked,
|
||||
isParentNotFound: item.isParentNotFound,
|
||||
}
|
||||
@@ -442,62 +457,86 @@ function createApi({
|
||||
feedParams,
|
||||
feedTuners,
|
||||
userInterests,
|
||||
agent,
|
||||
client,
|
||||
enableFollowingToDiscoverFallback,
|
||||
}: {
|
||||
feedDesc: FeedDescriptor
|
||||
feedParams: FeedParams
|
||||
feedTuners: FeedTunerFn[]
|
||||
userInterests?: string
|
||||
agent: SessionAgent
|
||||
client: Client
|
||||
enableFollowingToDiscoverFallback: boolean
|
||||
}) {
|
||||
if (feedDesc === 'following') {
|
||||
if (feedParams.mergeFeedEnabled) {
|
||||
return new MergeFeedAPI({
|
||||
agent,
|
||||
client,
|
||||
feedParams,
|
||||
feedTuners,
|
||||
userInterests,
|
||||
})
|
||||
} else {
|
||||
if (enableFollowingToDiscoverFallback) {
|
||||
return new HomeFeedAPI({agent, userInterests})
|
||||
return new HomeFeedAPI({client, userInterests})
|
||||
} else {
|
||||
return new FollowingFeedAPI({agent})
|
||||
return new FollowingFeedAPI({client})
|
||||
}
|
||||
}
|
||||
} else if (feedDesc.startsWith('author')) {
|
||||
const [__, actor, filter] = feedDesc.split('|')
|
||||
return new AuthorFeedAPI({agent, feedParams: {actor, filter}})
|
||||
/*
|
||||
* The FeedAPI $Params types treat limit/includePins as required (post-parse
|
||||
* shape), but they are supplied at fetch() time, so the constructor receives
|
||||
* a partial. Cast to satisfy the constructor param.
|
||||
*/
|
||||
return new AuthorFeedAPI({
|
||||
client,
|
||||
feedParams: {
|
||||
actor: actor as AtIdentifierString,
|
||||
filter,
|
||||
} as app.bsky.feed.getAuthorFeed.$Params,
|
||||
})
|
||||
} else if (feedDesc.startsWith('likes')) {
|
||||
const [__, actor] = feedDesc.split('|')
|
||||
return new LikesFeedAPI({agent, feedParams: {actor}})
|
||||
return new LikesFeedAPI({
|
||||
client,
|
||||
feedParams: {
|
||||
actor: actor as AtIdentifierString,
|
||||
} as app.bsky.feed.getActorLikes.$Params,
|
||||
})
|
||||
} else if (feedDesc.startsWith('feedgen')) {
|
||||
const [__, feed] = feedDesc.split('|')
|
||||
return new CustomFeedAPI({
|
||||
agent,
|
||||
feedParams: {feed},
|
||||
client,
|
||||
feedParams: {feed: feed as AtUriString},
|
||||
userInterests,
|
||||
})
|
||||
} else if (feedDesc.startsWith('list')) {
|
||||
const [__, list] = feedDesc.split('|')
|
||||
return new ListFeedAPI({agent, feedParams: {list}})
|
||||
return new ListFeedAPI({
|
||||
client,
|
||||
feedParams: {
|
||||
list: list as AtUriString,
|
||||
} as app.bsky.feed.getListFeed.$Params,
|
||||
})
|
||||
} else if (feedDesc.startsWith('posts')) {
|
||||
const [__, uriList] = feedDesc.split('|')
|
||||
return new PostListFeedAPI({agent, feedParams: {uris: uriList.split(',')}})
|
||||
return new PostListFeedAPI({
|
||||
client,
|
||||
feedParams: {uris: uriList.split(',') as AtUriString[]},
|
||||
})
|
||||
} else if (feedDesc === 'demo') {
|
||||
return new DemoFeedAPI({agent})
|
||||
return new DemoFeedAPI({client})
|
||||
} else {
|
||||
// shouldnt happen
|
||||
return new FollowingFeedAPI({agent})
|
||||
return new FollowingFeedAPI({client})
|
||||
}
|
||||
}
|
||||
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
): Generator<app.bsky.feed.defs.PostView, undefined> {
|
||||
const atUri = new AtUri(uri)
|
||||
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
@@ -520,7 +559,7 @@ export function* findAllPostsInQueryData(
|
||||
yield embedViewRecordToPostView(quotedPost)
|
||||
}
|
||||
|
||||
if (AppBskyFeedDefs.isPostView(item.reply?.parent)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent)) {
|
||||
if (didOrHandleUriMatches(atUri, item.reply.parent)) {
|
||||
yield item.reply.parent
|
||||
}
|
||||
@@ -534,7 +573,7 @@ export function* findAllPostsInQueryData(
|
||||
}
|
||||
}
|
||||
|
||||
if (AppBskyFeedDefs.isPostView(item.reply?.root)) {
|
||||
if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.root)) {
|
||||
if (didOrHandleUriMatches(atUri, item.reply.root)) {
|
||||
yield item.reply.root
|
||||
}
|
||||
@@ -552,7 +591,7 @@ export function* findAllPostsInQueryData(
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileViewBasic, undefined> {
|
||||
): Generator<app.bsky.actor.defs.ProfileViewBasic, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<FeedPageUnselected>
|
||||
>({
|
||||
@@ -572,13 +611,13 @@ export function* findAllProfilesInQueryData(
|
||||
yield quotedPost.author
|
||||
}
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(item.reply?.parent) &&
|
||||
bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent) &&
|
||||
item.reply?.parent?.author.did === did
|
||||
) {
|
||||
yield item.reply.parent.author
|
||||
}
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(item.reply?.root) &&
|
||||
bsky.isType(app.bsky.feed.defs.postView, item.reply?.root) &&
|
||||
item.reply?.root?.author.did === did
|
||||
) {
|
||||
yield item.reply.root.author
|
||||
@@ -589,7 +628,7 @@ export function* findAllProfilesInQueryData(
|
||||
}
|
||||
|
||||
function assertSomePostsPassModeration(
|
||||
feed: AppBskyFeedDefs.FeedViewPost[],
|
||||
feed: app.bsky.feed.defs.FeedViewPost[],
|
||||
moderationPrefs: ModerationPrefs,
|
||||
) {
|
||||
// no posts in this feed
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {setPostInteractionSettings} from '@bsky.app/sdk'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {preferencesQueryKey} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export function usePostInteractionSettingsMutation({
|
||||
onError,
|
||||
@@ -12,10 +13,10 @@ export function usePostInteractionSettingsMutation({
|
||||
onSettled?: () => void
|
||||
} = {}) {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
return useMutation({
|
||||
async mutationFn(props: AppBskyActorDefs.PostInteractionSettingsPref) {
|
||||
await agent.setPostInteractionSettings(props)
|
||||
async mutationFn(props: app.bsky.actor.defs.PostInteractionSettingsPref) {
|
||||
await client.call(setPostInteractionSettings, props)
|
||||
},
|
||||
async onSuccess() {
|
||||
await qc.invalidateQueries({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {type AppBskyActorDefs, type AppBskyFeedGetLikes} from '@atproto/api'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
type RQPageParam = string | undefined
|
||||
@@ -19,22 +20,21 @@ const RQKEY_ROOT = 'liked-by'
|
||||
export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]
|
||||
|
||||
export function useLikedByQuery(resolvedUri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedGetLikes.OutputSchema,
|
||||
app.bsky.feed.getLikes.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyFeedGetLikes.OutputSchema>,
|
||||
InfiniteData<app.bsky.feed.getLikes.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(resolvedUri || ''),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.getLikes({
|
||||
uri: resolvedUri || '',
|
||||
return await client.call(app.bsky.feed.getLikes, {
|
||||
uri: (resolvedUri || '') as AtUriString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -59,12 +59,14 @@ export const createLikedBySampleQueryKey = (args: {uri: string}) =>
|
||||
* perturb the liked-by screen's pagination.
|
||||
*/
|
||||
export function useLikedBySampleQuery({uri}: {uri: string | undefined}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
queryKey: createLikedBySampleQueryKey({uri: uri ?? ''}),
|
||||
queryFn: async () => {
|
||||
const res = await agent.getLikes({uri: uri ?? '', limit: SAMPLE_SIZE})
|
||||
return res.data
|
||||
return await client.call(app.bsky.feed.getLikes, {
|
||||
uri: (uri ?? '') as AtUriString,
|
||||
limit: SAMPLE_SIZE,
|
||||
})
|
||||
},
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
enabled: !!uri,
|
||||
@@ -79,9 +81,9 @@ export function useLikedBySampleQuery({uri}: {uri: string | undefined}) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyFeedGetLikes.OutputSchema>
|
||||
InfiniteData<app.bsky.feed.getLikes.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
@@ -98,7 +100,7 @@ export function* findAllProfilesInQueryData(
|
||||
}
|
||||
}
|
||||
const sampleQueryDatas =
|
||||
queryClient.getQueriesData<AppBskyFeedGetLikes.OutputSchema>({
|
||||
queryClient.getQueriesData<app.bsky.feed.getLikes.$OutputBody>({
|
||||
queryKey: [likedBySampleQueryKeyRoot],
|
||||
})
|
||||
for (const [_queryKey, queryData] of sampleQueryDatas) {
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
AppBskyEmbedRecord,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedGetQuotes,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {AtUri, type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -12,7 +6,9 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {
|
||||
didOrHandleUriMatches,
|
||||
embedViewRecordToPostView,
|
||||
@@ -26,22 +22,21 @@ const RQKEY_ROOT = 'post-quotes'
|
||||
export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]
|
||||
|
||||
export function usePostQuotesQuery(resolvedUri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedGetQuotes.OutputSchema,
|
||||
app.bsky.feed.getQuotes.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyFeedGetQuotes.OutputSchema>,
|
||||
InfiniteData<app.bsky.feed.getQuotes.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(resolvedUri || ''),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.api.app.bsky.feed.getQuotes({
|
||||
uri: resolvedUri || '',
|
||||
return await client.call(app.bsky.feed.getQuotes, {
|
||||
uri: (resolvedUri || '') as AtUriString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -53,8 +48,16 @@ export function usePostQuotesQuery(resolvedUri: string | undefined) {
|
||||
return {
|
||||
...page,
|
||||
posts: page.posts.filter(post => {
|
||||
if (post.embed && AppBskyEmbedRecord.isView(post.embed)) {
|
||||
if (AppBskyEmbedRecord.isViewDetached(post.embed.record)) {
|
||||
if (
|
||||
post.embed &&
|
||||
bsky.isType(app.bsky.embed.record.view, post.embed)
|
||||
) {
|
||||
if (
|
||||
bsky.isType(
|
||||
app.bsky.embed.record.viewDetached,
|
||||
post.embed.record,
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -70,9 +73,9 @@ export function usePostQuotesQuery(resolvedUri: string | undefined) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileViewBasic, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileViewBasic, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyFeedGetQuotes.OutputSchema>
|
||||
InfiniteData<app.bsky.feed.getQuotes.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
@@ -97,9 +100,9 @@ export function* findAllProfilesInQueryData(
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
): Generator<app.bsky.feed.defs.PostView, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyFeedGetQuotes.OutputSchema>
|
||||
InfiniteData<app.bsky.feed.getQuotes.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyFeedGetRepostedBy,
|
||||
} from '@atproto/api'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -9,7 +6,8 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
type RQPageParam = string | undefined
|
||||
@@ -19,22 +17,21 @@ const RQKEY_ROOT = 'post-reposted-by'
|
||||
export const RQKEY = (resolvedUri: string) => [RQKEY_ROOT, resolvedUri]
|
||||
|
||||
export function usePostRepostedByQuery(resolvedUri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedGetRepostedBy.OutputSchema,
|
||||
app.bsky.feed.getRepostedBy.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyFeedGetRepostedBy.OutputSchema>,
|
||||
InfiniteData<app.bsky.feed.getRepostedBy.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(resolvedUri || ''),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.getRepostedBy({
|
||||
uri: resolvedUri || '',
|
||||
return await client.call(app.bsky.feed.getRepostedBy, {
|
||||
uri: (resolvedUri || '') as AtUriString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -45,9 +42,9 @@ export function usePostRepostedByQuery(resolvedUri: string | undefined) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyFeedGetRepostedBy.OutputSchema>
|
||||
InfiniteData<app.bsky.feed.getRepostedBy.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
+70
-57
@@ -1,5 +1,6 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax'
|
||||
import {deleteLike, deletePost, deleteRepost, like, repost} from '@bsky.app/sdk'
|
||||
import {
|
||||
type QueryClient,
|
||||
useMutation,
|
||||
@@ -10,10 +11,11 @@ import {
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Metrics, toClout} from '#/analytics/metrics'
|
||||
import {app, com} from '#/lexicons'
|
||||
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
||||
import {findProfileQueryData} from './profile'
|
||||
|
||||
@@ -21,8 +23,8 @@ const RQKEY_ROOT = 'post'
|
||||
export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]
|
||||
|
||||
export function usePostQuery(uri: string | undefined) {
|
||||
const agent = useAgent()
|
||||
return useQuery<AppBskyFeedDefs.PostView>({
|
||||
const client = useAppviewClient()
|
||||
return useQuery<app.bsky.feed.defs.PostView>({
|
||||
queryKey: RQKEY(uri || ''),
|
||||
queryFn: async () => {
|
||||
if (!uri) throw new Error('[unreachable] No URI provided')
|
||||
@@ -30,16 +32,17 @@ export function usePostQuery(uri: string | undefined) {
|
||||
const urip = new AtUri(uri)
|
||||
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
const {did} = await client.call(com.atproto.identity.resolveHandle, {
|
||||
handle: urip.host as HandleString,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
urip.host = did
|
||||
}
|
||||
|
||||
const res = await agent.getPosts({uris: [urip.toString()]})
|
||||
if (res.success && res.data.posts[0]) {
|
||||
return res.data.posts[0]
|
||||
const data = await client.call(app.bsky.feed.getPosts, {
|
||||
uris: [urip.toString()],
|
||||
})
|
||||
if (data.posts[0]) {
|
||||
return data.posts[0]
|
||||
}
|
||||
|
||||
throw new Error('No data')
|
||||
@@ -51,14 +54,14 @@ export function usePostQuery(uri: string | undefined) {
|
||||
export function precachePost(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
post: AppBskyFeedDefs.PostView,
|
||||
post: app.bsky.feed.defs.PostView,
|
||||
) {
|
||||
queryClient.setQueryData(RQKEY(uri), post)
|
||||
}
|
||||
|
||||
export function useGetPost() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useCallback(
|
||||
async ({uri}: {uri: string}) => {
|
||||
return queryClient.fetchQuery({
|
||||
@@ -67,55 +70,51 @@ export function useGetPost() {
|
||||
const urip = new AtUri(uri)
|
||||
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
const {did} = await client.call(
|
||||
com.atproto.identity.resolveHandle,
|
||||
{handle: urip.host as HandleString},
|
||||
)
|
||||
urip.host = did
|
||||
}
|
||||
|
||||
const res = await agent.getPosts({
|
||||
const data = await client.call(app.bsky.feed.getPosts, {
|
||||
uris: [urip.toString()],
|
||||
})
|
||||
|
||||
if (res.success && res.data.posts[0]) {
|
||||
return res.data.posts[0]
|
||||
if (data.posts[0]) {
|
||||
return data.posts[0]
|
||||
}
|
||||
|
||||
throw new Error('useGetPost: post not found')
|
||||
},
|
||||
})
|
||||
},
|
||||
[queryClient, agent],
|
||||
[queryClient, client],
|
||||
)
|
||||
}
|
||||
|
||||
export function useGetPosts() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useCallback(
|
||||
async ({uris}: {uris: string[]}) => {
|
||||
return queryClient.fetchQuery({
|
||||
queryKey: RQKEY(uris.join(',') || ''),
|
||||
async queryFn() {
|
||||
const res = await agent.getPosts({
|
||||
uris,
|
||||
const data = await client.call(app.bsky.feed.getPosts, {
|
||||
uris: uris as AtUriString[],
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
return res.data.posts
|
||||
} else {
|
||||
throw new Error('useGetPosts failed')
|
||||
}
|
||||
return data.posts
|
||||
},
|
||||
})
|
||||
},
|
||||
[queryClient, agent],
|
||||
[queryClient, client],
|
||||
)
|
||||
}
|
||||
|
||||
export function usePostLikeMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:like']['logContext'],
|
||||
@@ -179,20 +178,20 @@ export function usePostLikeMutationQueue(
|
||||
function usePostLikeMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:like']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
) {
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
const postAuthor = post.author
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the like
|
||||
{uri: AtUriString}, // responds with the uri of the like
|
||||
Error,
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
mutationFn: ({uri, cid, via}) => {
|
||||
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||
let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined
|
||||
if (currentAccount) {
|
||||
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||
}
|
||||
@@ -215,7 +214,11 @@ function usePostLikeMutation(
|
||||
: undefined,
|
||||
feedDescriptor: feedDescriptor,
|
||||
})
|
||||
return agent.like(uri, cid, via)
|
||||
return pdsClient.call(like, {
|
||||
uri: uri as AtUriString,
|
||||
cid: cid,
|
||||
via: via ? {uri: via.uri as AtUriString, cid: via.cid} : undefined,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -223,9 +226,9 @@ function usePostLikeMutation(
|
||||
function usePostUnlikeMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:unlike']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<void, Error, {postUri: string; likeUri: string}>({
|
||||
mutationFn: ({postUri, likeUri}) => {
|
||||
@@ -235,13 +238,13 @@ function usePostUnlikeMutation(
|
||||
logContext,
|
||||
feedDescriptor,
|
||||
})
|
||||
return agent.deleteLike(likeUri)
|
||||
return pdsClient.call(deleteLike, likeUri as AtUriString)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function usePostRepostMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:repost']['logContext'],
|
||||
@@ -307,12 +310,12 @@ export function usePostRepostMutationQueue(
|
||||
function usePostRepostMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:repost']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the repost
|
||||
{uri: AtUriString}, // responds with the uri of the repost
|
||||
Error,
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
@@ -323,7 +326,11 @@ function usePostRepostMutation(
|
||||
logContext,
|
||||
feedDescriptor,
|
||||
})
|
||||
return agent.repost(uri, cid, via)
|
||||
return pdsClient.call(repost, {
|
||||
uri: uri as AtUriString,
|
||||
cid: cid,
|
||||
via: via ? {uri: via.uri as AtUriString, cid: via.cid} : undefined,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -331,9 +338,9 @@ function usePostRepostMutation(
|
||||
function usePostUnrepostMutation(
|
||||
feedDescriptor: string | undefined,
|
||||
logContext: Metrics['post:unrepost']['logContext'],
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<void, Error, {postUri: string; repostUri: string}>({
|
||||
mutationFn: ({postUri, repostUri}) => {
|
||||
@@ -343,17 +350,17 @@ function usePostUnrepostMutation(
|
||||
logContext,
|
||||
feedDescriptor,
|
||||
})
|
||||
return agent.deleteRepost(repostUri)
|
||||
return pdsClient.call(deleteRepost, repostUri as AtUriString)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function usePostDeleteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {uri: string}>({
|
||||
mutationFn: async ({uri}) => {
|
||||
await agent.deletePost(uri)
|
||||
await pdsClient.call(deletePost, uri as AtUriString)
|
||||
},
|
||||
onSuccess(_, variables) {
|
||||
updatePostShadow(queryClient, variables.uri, {isDeleted: true})
|
||||
@@ -362,7 +369,7 @@ export function usePostDeleteMutation() {
|
||||
}
|
||||
|
||||
export function useThreadMuteMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
post: Shadow<app.bsky.feed.defs.PostView>,
|
||||
rootUri: string,
|
||||
) {
|
||||
const threadMuteMutation = useThreadMuteMutation()
|
||||
@@ -407,23 +414,29 @@ export function useThreadMuteMutationQueue(
|
||||
}
|
||||
|
||||
function useThreadMuteMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<
|
||||
{},
|
||||
Error,
|
||||
{uri: string} // the root post's uri
|
||||
>({
|
||||
mutationFn: ({uri}) => {
|
||||
return agent.api.app.bsky.graph.muteThread({root: uri})
|
||||
mutationFn: async ({uri}) => {
|
||||
await pdsClient.call(app.bsky.graph.muteThread, {
|
||||
root: uri as AtUriString,
|
||||
})
|
||||
return {}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function useThreadUnmuteMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<{}, Error, {uri: string}>({
|
||||
mutationFn: ({uri}) => {
|
||||
return agent.api.app.bsky.graph.unmuteThread({root: uri})
|
||||
mutationFn: async ({uri}) => {
|
||||
await pdsClient.call(app.bsky.graph.unmuteThread, {
|
||||
root: uri as AtUriString,
|
||||
})
|
||||
return {}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import {useRef} from 'react'
|
||||
import {
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyFeedPostgate,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry, retry} from '#/lib/async/retry'
|
||||
@@ -19,28 +14,28 @@ import {
|
||||
mergePostgateRecords,
|
||||
POSTGATE_COLLECTION,
|
||||
} from '#/state/queries/postgate/util'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
import {app, com} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export async function getPostgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
}): Promise<AppBskyFeedPostgate.Record | undefined> {
|
||||
}): Promise<app.bsky.feed.postgate.Main | undefined> {
|
||||
const urip = new AtUri(postUri)
|
||||
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
const {did} = await pdsClient.call(com.atproto.identity.resolveHandle, {
|
||||
handle: urip.host as `${string}.${string}`,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
urip.host = did
|
||||
}
|
||||
|
||||
try {
|
||||
const {data} = await retry(
|
||||
const data = await retry(
|
||||
2,
|
||||
e => {
|
||||
/*
|
||||
@@ -54,17 +49,14 @@ export async function getPostgateRecord({
|
||||
return true
|
||||
},
|
||||
() =>
|
||||
agent.api.com.atproto.repo.getRecord({
|
||||
pdsClient.call(com.atproto.repo.getRecord, {
|
||||
repo: urip.host,
|
||||
collection: POSTGATE_COLLECTION,
|
||||
rkey: urip.rkey,
|
||||
}),
|
||||
)
|
||||
|
||||
if (
|
||||
data.value &&
|
||||
bsky.validate(data.value, AppBskyFeedPostgate.validateRecord)
|
||||
) {
|
||||
if (data.value && bsky.matches(app.bsky.feed.postgate, data.value)) {
|
||||
return data.value
|
||||
} else {
|
||||
return undefined
|
||||
@@ -84,19 +76,19 @@ export async function getPostgateRecord({
|
||||
}
|
||||
|
||||
export async function writePostgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
postgate,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
postgate: AppBskyFeedPostgate.Record
|
||||
postgate: app.bsky.feed.postgate.Main
|
||||
}) {
|
||||
const postUrip = new AtUri(postUri)
|
||||
|
||||
await networkRetry(2, () =>
|
||||
agent.api.com.atproto.repo.putRecord({
|
||||
repo: agent.session!.did,
|
||||
pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo: pdsClient.assertDid,
|
||||
collection: POSTGATE_COLLECTION,
|
||||
rkey: postUrip.rkey,
|
||||
record: postgate,
|
||||
@@ -106,24 +98,24 @@ export async function writePostgateRecord({
|
||||
|
||||
export async function upsertPostgate(
|
||||
{
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
},
|
||||
callback: (
|
||||
postgate: AppBskyFeedPostgate.Record | undefined,
|
||||
) => Promise<AppBskyFeedPostgate.Record | undefined>,
|
||||
postgate: app.bsky.feed.postgate.Main | undefined,
|
||||
) => Promise<app.bsky.feed.postgate.Main | undefined>,
|
||||
) {
|
||||
const prev = await getPostgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
})
|
||||
const next = await callback(prev)
|
||||
if (!next) return
|
||||
await writePostgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
postgate: next,
|
||||
})
|
||||
@@ -134,18 +126,20 @@ export const createPostgateQueryKey = (postUri: string) => [
|
||||
postUri,
|
||||
]
|
||||
export function usePostgateQuery({postUri}: {postUri: string}) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useQuery({
|
||||
staleTime: STALE.SECONDS.THIRTY,
|
||||
queryKey: createPostgateQueryKey(postUri),
|
||||
async queryFn() {
|
||||
return await getPostgateRecord({agent, postUri}).then(res => res ?? null)
|
||||
return await getPostgateRecord({pdsClient, postUri}).then(
|
||||
res => res ?? null,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useWritePostgateMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -153,10 +147,10 @@ export function useWritePostgateMutation() {
|
||||
postgate,
|
||||
}: {
|
||||
postUri: string
|
||||
postgate: AppBskyFeedPostgate.Record
|
||||
postgate: app.bsky.feed.postgate.Main
|
||||
}) => {
|
||||
return writePostgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
postgate,
|
||||
})
|
||||
@@ -170,10 +164,10 @@ export function useWritePostgateMutation() {
|
||||
}
|
||||
|
||||
export function useToggleQuoteDetachmentMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const getPosts = useGetPosts()
|
||||
const prevEmbed = useRef<AppBskyFeedDefs.PostView['embed']>(undefined)
|
||||
const prevEmbed = useRef<app.bsky.feed.defs.PostView['embed']>(undefined)
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -181,7 +175,7 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
quoteUri,
|
||||
action,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quoteUri: string
|
||||
action: 'detach' | 'reattach'
|
||||
}) => {
|
||||
@@ -199,7 +193,7 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
})
|
||||
}
|
||||
|
||||
await upsertPostgate({agent, postUri: quoteUri}, async prev => {
|
||||
await upsertPostgate({pdsClient, postUri: quoteUri}, async prev => {
|
||||
if (prev) {
|
||||
if (action === 'detach') {
|
||||
return mergePostgateRecords(prev, {
|
||||
@@ -247,8 +241,8 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
if (action === 'detach' && prevEmbed.current) {
|
||||
// detach failed, add the embed back
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(prevEmbed.current) ||
|
||||
AppBskyEmbedRecordWithMedia.isView(prevEmbed.current)
|
||||
bsky.isType(app.bsky.embed.record.view, prevEmbed.current) ||
|
||||
bsky.isType(app.bsky.embed.recordWithMedia.view, prevEmbed.current)
|
||||
) {
|
||||
updatePostShadow(queryClient, post.uri, {
|
||||
embed: prevEmbed.current,
|
||||
@@ -263,7 +257,7 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
}
|
||||
|
||||
export function useToggleQuotepostEnabledMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -273,7 +267,7 @@ export function useToggleQuotepostEnabledMutation() {
|
||||
postUri: string
|
||||
action: 'enable' | 'disable'
|
||||
}) => {
|
||||
await upsertPostgate({agent, postUri: postUri}, async prev => {
|
||||
await upsertPostgate({pdsClient, postUri: postUri}, async prev => {
|
||||
if (prev) {
|
||||
if (action === 'disable') {
|
||||
return mergePostgateRecords(prev, {
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import {
|
||||
type $Typed,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPostgate,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export const POSTGATE_COLLECTION = 'app.bsky.feed.postgate'
|
||||
|
||||
export function createPostgateRecord(
|
||||
postgate: Partial<AppBskyFeedPostgate.Record> & {
|
||||
post: AppBskyFeedPostgate.Record['post']
|
||||
postgate: Omit<Partial<app.bsky.feed.postgate.Main>, 'post'> & {
|
||||
post: string
|
||||
},
|
||||
): AppBskyFeedPostgate.Record {
|
||||
): app.bsky.feed.postgate.Main {
|
||||
return {
|
||||
$type: POSTGATE_COLLECTION,
|
||||
createdAt: new Date().toISOString(),
|
||||
post: postgate.post,
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
post: postgate.post as AtUriString,
|
||||
detachedEmbeddingUris: postgate.detachedEmbeddingUris || [],
|
||||
embeddingRules: postgate.embeddingRules || [],
|
||||
}
|
||||
}
|
||||
|
||||
export function mergePostgateRecords(
|
||||
prev: AppBskyFeedPostgate.Record,
|
||||
next: Partial<AppBskyFeedPostgate.Record>,
|
||||
prev: app.bsky.feed.postgate.Main,
|
||||
next: Partial<app.bsky.feed.postgate.Main>,
|
||||
) {
|
||||
const detachedEmbeddingUris = Array.from(
|
||||
new Set([
|
||||
@@ -50,10 +47,10 @@ export function createEmbedViewDetachedRecord({
|
||||
uri,
|
||||
}: {
|
||||
uri: string
|
||||
}): $Typed<AppBskyEmbedRecord.View> {
|
||||
const record: $Typed<AppBskyEmbedRecord.ViewDetached> = {
|
||||
}): $Typed<app.bsky.embed.record.View> {
|
||||
const record: $Typed<app.bsky.embed.record.ViewDetached> = {
|
||||
$type: 'app.bsky.embed.record#viewDetached',
|
||||
uri,
|
||||
uri: uri as AtUriString,
|
||||
detached: true,
|
||||
}
|
||||
return {
|
||||
@@ -69,24 +66,27 @@ export function createMaybeDetachedQuoteEmbed({
|
||||
detached,
|
||||
}:
|
||||
| {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
quote: AppBskyFeedDefs.PostView
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quote: app.bsky.feed.defs.PostView
|
||||
quoteUri: undefined
|
||||
detached: false
|
||||
}
|
||||
| {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quote: undefined
|
||||
quoteUri: string
|
||||
detached: true
|
||||
}): AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined {
|
||||
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||
}):
|
||||
| app.bsky.embed.record.View
|
||||
| app.bsky.embed.recordWithMedia.View
|
||||
| undefined {
|
||||
if (bsky.isType(app.bsky.embed.record.view, post.embed)) {
|
||||
if (detached) {
|
||||
return createEmbedViewDetachedRecord({uri: quoteUri})
|
||||
} else {
|
||||
return createEmbedRecordView({post: quote})
|
||||
}
|
||||
} else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||
} else if (bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) {
|
||||
if (detached) {
|
||||
return {
|
||||
...post.embed,
|
||||
@@ -99,8 +99,8 @@ export function createMaybeDetachedQuoteEmbed({
|
||||
}
|
||||
|
||||
export function createEmbedViewRecordFromPost(
|
||||
post: AppBskyFeedDefs.PostView,
|
||||
): $Typed<AppBskyEmbedRecord.ViewRecord> {
|
||||
post: app.bsky.feed.defs.PostView,
|
||||
): $Typed<app.bsky.embed.record.ViewRecord> {
|
||||
return {
|
||||
$type: 'app.bsky.embed.record#viewRecord',
|
||||
uri: post.uri,
|
||||
@@ -120,8 +120,8 @@ export function createEmbedViewRecordFromPost(
|
||||
export function createEmbedRecordView({
|
||||
post,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
}): AppBskyEmbedRecord.View {
|
||||
post: app.bsky.feed.defs.PostView
|
||||
}): app.bsky.embed.record.View {
|
||||
return {
|
||||
$type: 'app.bsky.embed.record#view',
|
||||
record: createEmbedViewRecordFromPost(post),
|
||||
@@ -132,10 +132,10 @@ export function createEmbedRecordWithMediaView({
|
||||
post,
|
||||
quote,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
quote: AppBskyFeedDefs.PostView
|
||||
}): AppBskyEmbedRecordWithMedia.View | undefined {
|
||||
if (!AppBskyEmbedRecordWithMedia.isView(post.embed)) return
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quote: app.bsky.feed.defs.PostView
|
||||
}): app.bsky.embed.recordWithMedia.View | undefined {
|
||||
if (!bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) return
|
||||
return {
|
||||
...(post.embed || {}),
|
||||
record: {
|
||||
@@ -149,11 +149,11 @@ export function getMaybeDetachedQuoteEmbed({
|
||||
post,
|
||||
}: {
|
||||
viewerDid: string
|
||||
post: AppBskyFeedDefs.PostView
|
||||
post: app.bsky.feed.defs.PostView
|
||||
}) {
|
||||
if (AppBskyEmbedRecord.isView(post.embed)) {
|
||||
if (bsky.isType(app.bsky.embed.record.view, post.embed)) {
|
||||
// detached
|
||||
if (AppBskyEmbedRecord.isViewDetached(post.embed.record)) {
|
||||
if (bsky.isType(app.bsky.embed.record.viewDetached, post.embed.record)) {
|
||||
const urip = new AtUri(post.embed.record.uri)
|
||||
return {
|
||||
embed: post.embed,
|
||||
@@ -164,7 +164,7 @@ export function getMaybeDetachedQuoteEmbed({
|
||||
}
|
||||
|
||||
// post
|
||||
if (AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
|
||||
if (bsky.isType(app.bsky.embed.record.viewRecord, post.embed.record)) {
|
||||
const urip = new AtUri(post.embed.record.uri)
|
||||
return {
|
||||
embed: post.embed,
|
||||
@@ -173,9 +173,11 @@ export function getMaybeDetachedQuoteEmbed({
|
||||
isDetached: false,
|
||||
}
|
||||
}
|
||||
} else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
|
||||
} else if (bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) {
|
||||
// detached
|
||||
if (AppBskyEmbedRecord.isViewDetached(post.embed.record.record)) {
|
||||
if (
|
||||
bsky.isType(app.bsky.embed.record.viewDetached, post.embed.record.record)
|
||||
) {
|
||||
const urip = new AtUri(post.embed.record.record.uri)
|
||||
return {
|
||||
embed: post.embed,
|
||||
@@ -186,7 +188,9 @@ export function getMaybeDetachedQuoteEmbed({
|
||||
}
|
||||
|
||||
// post
|
||||
if (AppBskyEmbedRecord.isViewRecord(post.embed.record.record)) {
|
||||
if (
|
||||
bsky.isType(app.bsky.embed.record.viewRecord, post.embed.record.record)
|
||||
) {
|
||||
const urip = new AtUri(post.embed.record.record.uri)
|
||||
return {
|
||||
embed: post.embed,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DEFAULT_LABEL_SETTINGS} from '@atproto/api'
|
||||
import {DEFAULT_LABEL_SETTINGS} from '@bsky.app/sdk'
|
||||
|
||||
import {
|
||||
type ThreadViewPreferences,
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
addSavedFeeds,
|
||||
type BskyFeedViewPreference,
|
||||
type LabelPreference,
|
||||
} from '@atproto/api'
|
||||
dismissNudges,
|
||||
getPreferences,
|
||||
overwriteSavedFeeds,
|
||||
queueNudges,
|
||||
removeMutedWord,
|
||||
removeMutedWords,
|
||||
removeSavedFeeds,
|
||||
setActiveProgressGuide,
|
||||
setAdultContentEnabled,
|
||||
setContentLabelPref,
|
||||
setFeedViewPrefs,
|
||||
setIsBetaUser,
|
||||
setThreadViewPrefs,
|
||||
setVerificationPrefs,
|
||||
updateMutedWord,
|
||||
updateSavedFeeds,
|
||||
upsertMutedWords,
|
||||
} from '@bsky.app/sdk'
|
||||
import {type LabelPreference} from '@bsky.app/sdk/moderation'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||
@@ -20,11 +38,13 @@ import {
|
||||
type UsePreferencesQueryResponse,
|
||||
} from '#/state/queries/preferences/types'
|
||||
import {createQueryKey} from '#/state/queries/util'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
import {saveLabelers} from '#/state/session/agent-config'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {app} from '#/lexicons'
|
||||
import {toLex} from '#/types/bsky'
|
||||
|
||||
export * from '#/state/queries/preferences/const'
|
||||
export * from '#/state/queries/preferences/moderation'
|
||||
@@ -37,7 +57,7 @@ export const preferencesQueryKey = createQueryKey(
|
||||
)
|
||||
|
||||
export function usePreferencesQuery() {
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
const aa = useAgeAssurance()
|
||||
|
||||
const query = useQuery({
|
||||
@@ -47,14 +67,14 @@ export function usePreferencesQuery() {
|
||||
queryKey: preferencesQueryKey,
|
||||
gcTime: GCTIME.INFINITY,
|
||||
queryFn: async () => {
|
||||
if (!agent.did) {
|
||||
if (!client.did) {
|
||||
return DEFAULT_LOGGED_OUT_PREFERENCES
|
||||
} else {
|
||||
const res = await agent.getPreferences()
|
||||
const res = await client.call(getPreferences)
|
||||
|
||||
// save to local storage to ensure there are labels on initial requests
|
||||
void saveLabelers(
|
||||
agent.did,
|
||||
client.did,
|
||||
res.moderationPrefs.labelers.map(l => l.did),
|
||||
)
|
||||
|
||||
@@ -90,8 +110,16 @@ export function usePreferencesQuery() {
|
||||
) {
|
||||
data = {
|
||||
...data,
|
||||
moderationPrefs: makeAgeRestrictedModerationPrefs(
|
||||
data.moderationPrefs,
|
||||
/*
|
||||
* TODO(phase4): drop the toLex bridges once
|
||||
* `#/ageAssurance/util` (makeAgeRestrictedModerationPrefs) sources
|
||||
* `ModerationPrefs` from `@bsky.app/sdk/moderation` instead of
|
||||
* `@atproto/api`. The two shapes differ only in scalar branding
|
||||
* (e.g. MutedWord.actorTarget's UnknownString), so the values are
|
||||
* structurally interchangeable at this boundary.
|
||||
*/
|
||||
moderationPrefs: toLex(
|
||||
makeAgeRestrictedModerationPrefs(toLex(data.moderationPrefs)),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -113,11 +141,11 @@ export function usePreferencesQuery() {
|
||||
|
||||
export function useClearPreferencesMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
await agent.app.bsky.actor.putPreferences({preferences: []})
|
||||
await client.call(app.bsky.actor.putPreferences, {preferences: []})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -128,7 +156,7 @@ export function useClearPreferencesMutation() {
|
||||
|
||||
export function usePreferencesSetContentLabelMutation() {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<
|
||||
@@ -137,7 +165,11 @@ export function usePreferencesSetContentLabelMutation() {
|
||||
{label: string; visibility: LabelPreference; labelerDid: string | undefined}
|
||||
>({
|
||||
mutationFn: async ({label, visibility, labelerDid}) => {
|
||||
await agent.setContentLabelPref(label, visibility, labelerDid)
|
||||
await client.call(setContentLabelPref, {
|
||||
key: label,
|
||||
value: visibility,
|
||||
labelerDid: labelerDid as DidString | undefined,
|
||||
})
|
||||
ax.metric('moderation:changeLabelPreference', {preference: visibility})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -149,7 +181,7 @@ export function usePreferencesSetContentLabelMutation() {
|
||||
|
||||
export function useSetContentLabelMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -161,7 +193,11 @@ export function useSetContentLabelMutation() {
|
||||
visibility: LabelPreference
|
||||
labelerDid?: string
|
||||
}) => {
|
||||
await agent.setContentLabelPref(label, visibility, labelerDid)
|
||||
await client.call(setContentLabelPref, {
|
||||
key: label,
|
||||
value: visibility,
|
||||
labelerDid: labelerDid as DidString | undefined,
|
||||
})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -172,11 +208,11 @@ export function useSetContentLabelMutation() {
|
||||
|
||||
export function usePreferencesSetAdultContentMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, {enabled: boolean}>({
|
||||
mutationFn: async ({enabled}) => {
|
||||
await agent.setAdultContentEnabled(enabled)
|
||||
await client.call(setAdultContentEnabled, enabled)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -187,7 +223,7 @@ export function usePreferencesSetAdultContentMutation() {
|
||||
|
||||
export function useSetFeedViewPreferencesMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, Partial<BskyFeedViewPreference>>({
|
||||
mutationFn: async prefs => {
|
||||
@@ -195,7 +231,7 @@ export function useSetFeedViewPreferencesMutation() {
|
||||
* special handling here, merged into `feedViewPrefs` above, since
|
||||
* following was previously called `home`
|
||||
*/
|
||||
await agent.setFeedViewPrefs('home', prefs)
|
||||
await client.call(setFeedViewPrefs, {feed: 'home', ...prefs})
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -212,11 +248,11 @@ export function useSetThreadViewPreferencesMutation({
|
||||
onError?: (error: unknown) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, Partial<ThreadViewPreferences>>({
|
||||
mutationFn: async prefs => {
|
||||
await agent.setThreadViewPrefs(prefs)
|
||||
await client.call(setThreadViewPrefs, prefs)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -229,11 +265,11 @@ export function useSetThreadViewPreferencesMutation({
|
||||
|
||||
export function useOverwriteSavedFeedsMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({
|
||||
return useMutation<void, unknown, app.bsky.actor.defs.SavedFeed[]>({
|
||||
mutationFn: async savedFeeds => {
|
||||
await agent.overwriteSavedFeeds(savedFeeds)
|
||||
await client.call(overwriteSavedFeeds, savedFeeds)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -244,15 +280,15 @@ export function useOverwriteSavedFeedsMutation() {
|
||||
|
||||
export function useAddSavedFeedsMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<
|
||||
void,
|
||||
unknown,
|
||||
Pick<AppBskyActorDefs.SavedFeed, 'type' | 'value' | 'pinned'>[]
|
||||
Pick<app.bsky.actor.defs.SavedFeed, 'type' | 'value' | 'pinned'>[]
|
||||
>({
|
||||
mutationFn: async savedFeeds => {
|
||||
await agent.addSavedFeeds(savedFeeds)
|
||||
await client.call(addSavedFeeds, savedFeeds)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -263,11 +299,11 @@ export function useAddSavedFeedsMutation() {
|
||||
|
||||
export function useRemoveFeedMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, Pick<AppBskyActorDefs.SavedFeed, 'id'>>({
|
||||
return useMutation<void, unknown, Pick<app.bsky.actor.defs.SavedFeed, 'id'>>({
|
||||
mutationFn: async savedFeed => {
|
||||
await agent.removeSavedFeeds([savedFeed.id])
|
||||
await client.call(removeSavedFeeds, [savedFeed.id])
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -278,21 +314,21 @@ export function useRemoveFeedMutation() {
|
||||
|
||||
export function useReplaceForYouWithDiscoverFeedMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
forYouFeedConfig,
|
||||
discoverFeedConfig,
|
||||
}: {
|
||||
forYouFeedConfig: AppBskyActorDefs.SavedFeed | undefined
|
||||
discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined
|
||||
forYouFeedConfig: app.bsky.actor.defs.SavedFeed | undefined
|
||||
discoverFeedConfig: app.bsky.actor.defs.SavedFeed | undefined
|
||||
}) => {
|
||||
if (forYouFeedConfig) {
|
||||
await agent.removeSavedFeeds([forYouFeedConfig.id])
|
||||
await client.call(removeSavedFeeds, [forYouFeedConfig.id])
|
||||
}
|
||||
if (!discoverFeedConfig) {
|
||||
await agent.addSavedFeeds([
|
||||
await client.call(addSavedFeeds, [
|
||||
{
|
||||
type: 'feed',
|
||||
value: PROD_DEFAULT_FEED('whats-hot'),
|
||||
@@ -300,7 +336,7 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
|
||||
},
|
||||
])
|
||||
} else {
|
||||
await agent.updateSavedFeeds([
|
||||
await client.call(updateSavedFeeds, [
|
||||
{
|
||||
...discoverFeedConfig,
|
||||
pinned: true,
|
||||
@@ -317,11 +353,11 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
|
||||
|
||||
export function useUpdateSavedFeedsMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({
|
||||
return useMutation<void, unknown, app.bsky.actor.defs.SavedFeed[]>({
|
||||
mutationFn: async feeds => {
|
||||
await agent.updateSavedFeeds(feeds)
|
||||
await client.call(updateSavedFeeds, feeds)
|
||||
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -333,11 +369,11 @@ export function useUpdateSavedFeedsMutation() {
|
||||
|
||||
export function useUpsertMutedWordsMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => {
|
||||
await agent.upsertMutedWords(mutedWords)
|
||||
mutationFn: async (mutedWords: app.bsky.actor.defs.MutedWord[]) => {
|
||||
await client.call(upsertMutedWords, mutedWords)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -348,11 +384,11 @@ export function useUpsertMutedWordsMutation() {
|
||||
|
||||
export function useUpdateMutedWordMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
|
||||
await agent.updateMutedWord(mutedWord)
|
||||
mutationFn: async (mutedWord: app.bsky.actor.defs.MutedWord) => {
|
||||
await client.call(updateMutedWord, mutedWord)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -363,11 +399,11 @@ export function useUpdateMutedWordMutation() {
|
||||
|
||||
export function useRemoveMutedWordMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
|
||||
await agent.removeMutedWord(mutedWord)
|
||||
mutationFn: async (mutedWord: app.bsky.actor.defs.MutedWord) => {
|
||||
await client.call(removeMutedWord, mutedWord)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -378,11 +414,11 @@ export function useRemoveMutedWordMutation() {
|
||||
|
||||
export function useRemoveMutedWordsMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => {
|
||||
await agent.removeMutedWords(mutedWords)
|
||||
mutationFn: async (mutedWords: app.bsky.actor.defs.MutedWord[]) => {
|
||||
await client.call(removeMutedWords, mutedWords)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -393,11 +429,11 @@ export function useRemoveMutedWordsMutation() {
|
||||
|
||||
export function useQueueNudgesMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (nudges: string | string[]) => {
|
||||
await agent.bskyAppQueueNudges(nudges)
|
||||
await client.call(queueNudges, Array.isArray(nudges) ? nudges : [nudges])
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -408,11 +444,14 @@ export function useQueueNudgesMutation() {
|
||||
|
||||
export function useDismissNudgesMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (nudges: string | string[]) => {
|
||||
await agent.bskyAppDismissNudges(nudges)
|
||||
await client.call(
|
||||
dismissNudges,
|
||||
Array.isArray(nudges) ? nudges : [nudges],
|
||||
)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -423,13 +462,13 @@ export function useDismissNudgesMutation() {
|
||||
|
||||
export function useSetActiveProgressGuideMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (
|
||||
guide: AppBskyActorDefs.BskyAppProgressGuide | undefined,
|
||||
guide: app.bsky.actor.defs.BskyAppProgressGuide | undefined,
|
||||
) => {
|
||||
await agent.bskyAppSetActiveProgressGuide(guide)
|
||||
await client.call(setActiveProgressGuide, guide)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -440,11 +479,11 @@ export function useSetActiveProgressGuideMutation() {
|
||||
|
||||
export function useSetIsBetaUserMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (isBetaUser: boolean) => {
|
||||
await agent.setIsBetaUser(isBetaUser)
|
||||
await client.call(setIsBetaUser, isBetaUser)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
@@ -456,11 +495,11 @@ export function useSetIsBetaUserMutation() {
|
||||
export function useSetVerificationPrefsMutation() {
|
||||
const ax = useAnalytics()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const client = usePdsClient()
|
||||
|
||||
return useMutation<void, unknown, AppBskyActorDefs.VerificationPrefs>({
|
||||
return useMutation<void, unknown, app.bsky.actor.defs.VerificationPrefs>({
|
||||
mutationFn: async prefs => {
|
||||
await agent.setVerificationPrefs(prefs)
|
||||
await client.call(setVerificationPrefs, prefs)
|
||||
if (prefs.hideBadges) {
|
||||
ax.metric('verification:settings:hideBadges', {})
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useMemo} from 'react'
|
||||
import {AtpAgent, interpretLabelValueDefinitions} from '@atproto/api'
|
||||
import {Client} from '@atproto/lex-client'
|
||||
import {interpretLabelValueDefinitions} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
||||
import {useLabelersDetailedInfoQuery} from '../labeler'
|
||||
@@ -13,7 +14,7 @@ export function useMyLabelersQuery({
|
||||
const prefs = usePreferencesQuery()
|
||||
let dids = Array.from(
|
||||
new Set(
|
||||
AtpAgent.appLabelers.concat(
|
||||
(Client.appLabelers as readonly string[]).concat(
|
||||
prefs.data?.moderationPrefs.labelers.map(l => l.did) || [],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {type BskyFeedViewPreference, type BskyPreferences} from '@atproto/api'
|
||||
import {type BskyFeedViewPreference, type BskyPreferences} from '@bsky.app/sdk'
|
||||
|
||||
export type UsePreferencesQueryResponse = Omit<
|
||||
BskyPreferences,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import {
|
||||
type AppBskyFeedGetActorFeeds,
|
||||
moderateFeedGenerator,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryKey,
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
@@ -24,25 +23,25 @@ export function useProfileFeedgensQuery(
|
||||
) {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedGetActorFeeds.OutputSchema,
|
||||
app.bsky.feed.getActorFeeds.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyFeedGetActorFeeds.OutputSchema>,
|
||||
InfiniteData<app.bsky.feed.getActorFeeds.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(did),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.feed.getActorFeeds({
|
||||
actor: did,
|
||||
const data = await client.call(app.bsky.feed.getActorFeeds, {
|
||||
actor: did as AtIdentifierString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
res.data.feeds.sort((a, b) => {
|
||||
data.feeds.sort((a, b) => {
|
||||
return (b.likeCount || 0) - (a.likeCount || 0)
|
||||
})
|
||||
return res.data
|
||||
return data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphGetFollowers,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -9,8 +6,9 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const DEFAULT_SORT = 'latest'
|
||||
const PAGE_SIZE = 30
|
||||
@@ -33,26 +31,25 @@ export function useProfileFollowersQuery(
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const isSortEnabled = ax.features.enabled(ax.features.FollowSortEnable)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
const sortParam = isSortEnabled ? sort || DEFAULT_SORT : undefined
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetFollowers.OutputSchema,
|
||||
app.bsky.graph.getFollowers.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetFollowers.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getFollowers.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(did || '', sortParam),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getFollowers({
|
||||
actor: did || '',
|
||||
return await client.call(app.bsky.graph.getFollowers, {
|
||||
actor: (did || '') as AtIdentifierString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
sort: sortParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -63,9 +60,9 @@ export function useProfileFollowersQuery(
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetFollowers.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getFollowers.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {type AppBskyActorDefs, type AppBskyGraphGetFollows} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const DEFAULT_SORT = 'latest'
|
||||
const PAGE_SIZE = 30
|
||||
@@ -34,27 +35,26 @@ export function useProfileFollowsQuery(
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const isSortEnabled = ax.features.enabled(ax.features.FollowSortEnable)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
const sortParam = isSortEnabled ? sort || DEFAULT_SORT : undefined
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetFollows.OutputSchema,
|
||||
app.bsky.graph.getFollows.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetFollows.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getFollows.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
queryKey: RQKEY(did || '', sortParam),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getFollows({
|
||||
actor: did || '',
|
||||
return await client.call(app.bsky.graph.getFollows, {
|
||||
actor: (did || '') as AtIdentifierString,
|
||||
limit: limit || PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
sort: sortParam,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
@@ -65,9 +65,9 @@ export function useProfileFollowsQuery(
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyGraphGetFollows.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getFollows.$OutputBody>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {type AppBskyGraphGetLists, moderateUserList} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {moderateUserList} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryKey,
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
@@ -17,23 +19,21 @@ export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||
export function useProfileListsQuery(did: string, opts?: {enabled?: boolean}) {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetLists.OutputSchema,
|
||||
app.bsky.graph.getLists.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyGraphGetLists.OutputSchema>,
|
||||
InfiniteData<app.bsky.graph.getLists.$OutputBody>,
|
||||
QueryKey,
|
||||
RQPageParam
|
||||
>({
|
||||
queryKey: RQKEY(did),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
const res = await agent.app.bsky.graph.getLists({
|
||||
actor: did,
|
||||
return await client.call(app.bsky.graph.getLists, {
|
||||
actor: did as AtIdentifierString,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
})
|
||||
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type Un$Typed} from '@atproto/lex'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyActorGetProfile,
|
||||
type AppBskyActorGetProfiles,
|
||||
type AppBskyActorProfile,
|
||||
type AppBskyGraphGetFollows,
|
||||
AtUri,
|
||||
type ComAtprotoRepoUploadBlob,
|
||||
type Un$Typed,
|
||||
} from '@atproto/api'
|
||||
type AtIdentifierString,
|
||||
type Client,
|
||||
type DidString,
|
||||
} from '@atproto/lex-client'
|
||||
import {type DatetimeString} from '@atproto/lex-schema'
|
||||
import {AtUri, type AtUriString} from '@atproto/syntax'
|
||||
import {
|
||||
deleteFollow,
|
||||
follow,
|
||||
muteActor,
|
||||
unmuteActor,
|
||||
upsertProfile,
|
||||
} from '@bsky.app/sdk'
|
||||
import {
|
||||
type InfiniteData,
|
||||
keepPreviousData,
|
||||
@@ -32,10 +37,11 @@ import {
|
||||
useUnstableProfileViewCache,
|
||||
} from '#/state/queries/unstable-profile-cache'
|
||||
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
|
||||
import {type SessionAgent, useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {type Metrics, toClout} from '#/analytics/metrics'
|
||||
import {app} from '#/lexicons'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {
|
||||
ProgressGuideAction,
|
||||
@@ -67,9 +73,9 @@ export function useProfileQuery({
|
||||
did: string | undefined
|
||||
staleTime?: number
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {getUnstableProfile} = useUnstableProfileViewCache()
|
||||
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
|
||||
return useQuery<app.bsky.actor.defs.ProfileViewDetailed>({
|
||||
// WARNING
|
||||
// this staleTime is load-bearing
|
||||
// if you remove it, the UI infinite-loops
|
||||
@@ -78,12 +84,13 @@ export function useProfileQuery({
|
||||
refetchOnWindowFocus: true,
|
||||
queryKey: RQKEY(did ?? ''),
|
||||
queryFn: async () => {
|
||||
const res = await agent.getProfile({actor: did ?? ''})
|
||||
return res.data
|
||||
return await client.call(app.bsky.actor.getProfile, {
|
||||
actor: (did ?? '') as AtIdentifierString,
|
||||
})
|
||||
},
|
||||
placeholderData: () => {
|
||||
if (!did) return
|
||||
return getUnstableProfile(did) as AppBskyActorDefs.ProfileViewDetailed
|
||||
return getUnstableProfile(did) as app.bsky.actor.defs.ProfileViewDetailed
|
||||
},
|
||||
enabled: !!did,
|
||||
})
|
||||
@@ -96,21 +103,22 @@ export function useProfilesQuery({
|
||||
handles: string[]
|
||||
maintainData?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
enabled: handles.length > 0,
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
queryKey: profilesQueryKey(handles),
|
||||
queryFn: async () => {
|
||||
const res = await agent.getProfiles({actors: handles})
|
||||
return res.data
|
||||
return await client.call(app.bsky.actor.getProfiles, {
|
||||
actors: handles as AtIdentifierString[],
|
||||
})
|
||||
},
|
||||
placeholderData: maintainData ? keepPreviousData : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
export function usePrefetchProfileQuery() {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
const prefetchProfileQuery = useCallback(
|
||||
async (did: string) => {
|
||||
@@ -118,30 +126,32 @@ export function usePrefetchProfileQuery() {
|
||||
staleTime: STALE.SECONDS.THIRTY,
|
||||
queryKey: RQKEY(did),
|
||||
queryFn: async () => {
|
||||
const res = await agent.getProfile({actor: did || ''})
|
||||
return res.data
|
||||
return await client.call(app.bsky.actor.getProfile, {
|
||||
actor: (did || '') as AtIdentifierString,
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
[queryClient, agent],
|
||||
[queryClient, client],
|
||||
)
|
||||
return prefetchProfileQuery
|
||||
}
|
||||
|
||||
interface ProfileUpdateParams {
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
profile: app.bsky.actor.defs.ProfileViewDetailed
|
||||
updates:
|
||||
| Un$Typed<AppBskyActorProfile.Record>
|
||||
| Un$Typed<app.bsky.actor.profile.Main>
|
||||
| ((
|
||||
existing: Un$Typed<AppBskyActorProfile.Record>,
|
||||
) => Un$Typed<AppBskyActorProfile.Record>)
|
||||
existing: Un$Typed<app.bsky.actor.profile.Main>,
|
||||
) => Un$Typed<app.bsky.actor.profile.Main>)
|
||||
newUserAvatar?: ImageMeta | undefined | null
|
||||
newUserBanner?: ImageMeta | undefined | null
|
||||
checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean
|
||||
checkCommitted?: (res: app.bsky.actor.getProfile.$OutputBody) => boolean
|
||||
}
|
||||
export function useProfileUpdateMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const appviewClient = useAppviewClient()
|
||||
const updateProfileVerificationCache = useUpdateProfileVerificationCache()
|
||||
return useMutation<void, Error, ProfileUpdateParams>({
|
||||
mutationFn: async ({
|
||||
@@ -151,28 +161,24 @@ export function useProfileUpdateMutation() {
|
||||
newUserBanner,
|
||||
checkCommitted,
|
||||
}) => {
|
||||
let newUserAvatarPromise:
|
||||
| Promise<ComAtprotoRepoUploadBlob.Response>
|
||||
| undefined
|
||||
let newUserAvatarPromise: ReturnType<typeof uploadBlob> | undefined
|
||||
if (newUserAvatar) {
|
||||
newUserAvatarPromise = uploadBlob(
|
||||
agent,
|
||||
pdsClient,
|
||||
newUserAvatar.path,
|
||||
newUserAvatar.mime,
|
||||
)
|
||||
}
|
||||
let newUserBannerPromise:
|
||||
| Promise<ComAtprotoRepoUploadBlob.Response>
|
||||
| undefined
|
||||
let newUserBannerPromise: ReturnType<typeof uploadBlob> | undefined
|
||||
if (newUserBanner) {
|
||||
newUserBannerPromise = uploadBlob(
|
||||
agent,
|
||||
pdsClient,
|
||||
newUserBanner.path,
|
||||
newUserBanner.mime,
|
||||
)
|
||||
}
|
||||
await agent.upsertProfile(async existing => {
|
||||
let next: Un$Typed<AppBskyActorProfile.Record> = existing || {}
|
||||
await pdsClient.call(upsertProfile, async existing => {
|
||||
let next: Un$Typed<app.bsky.actor.profile.Main> = existing || {}
|
||||
if (typeof updates === 'function') {
|
||||
next = updates(next)
|
||||
} else {
|
||||
@@ -184,37 +190,37 @@ export function useProfileUpdateMutation() {
|
||||
}
|
||||
if (newUserAvatarPromise) {
|
||||
const res = await newUserAvatarPromise
|
||||
next.avatar = res.data.blob
|
||||
next.avatar = res.blob
|
||||
} else if (newUserAvatar === null) {
|
||||
next.avatar = undefined
|
||||
}
|
||||
if (newUserBannerPromise) {
|
||||
const res = await newUserBannerPromise
|
||||
next.banner = res.data.blob
|
||||
next.banner = res.blob
|
||||
} else if (newUserBanner === null) {
|
||||
next.banner = undefined
|
||||
}
|
||||
return next
|
||||
})
|
||||
await whenAppViewReady(
|
||||
agent,
|
||||
appviewClient,
|
||||
profile.did,
|
||||
checkCommitted ||
|
||||
(res => {
|
||||
if (typeof newUserAvatar !== 'undefined') {
|
||||
if (newUserAvatar === null && res.data.avatar) {
|
||||
if (newUserAvatar === null && res.avatar) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.avatar === profile.avatar) {
|
||||
} else if (res.avatar === profile.avatar) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (typeof newUserBanner !== 'undefined') {
|
||||
if (newUserBanner === null && res.data.banner) {
|
||||
if (newUserBanner === null && res.banner) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.banner === profile.banner) {
|
||||
} else if (res.banner === profile.banner) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
@@ -223,8 +229,8 @@ export function useProfileUpdateMutation() {
|
||||
return true
|
||||
}
|
||||
return (
|
||||
res.data.displayName === updates.displayName &&
|
||||
res.data.description === updates.description
|
||||
res.displayName === updates.displayName &&
|
||||
res.description === updates.description
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -248,7 +254,7 @@ export function useProfileFollowMutationQueue(
|
||||
position?: number,
|
||||
contextProfileDid?: string,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {currentAccount} = useSession()
|
||||
const did = profile.did
|
||||
@@ -290,7 +296,7 @@ export function useProfileFollowMutationQueue(
|
||||
// Optimistically update profile follows cache for avatar displays
|
||||
if (currentAccount?.did) {
|
||||
type FollowsQueryData =
|
||||
InfiniteData<AppBskyGraphGetFollows.OutputSchema>
|
||||
InfiniteData<app.bsky.graph.getFollows.$OutputBody>
|
||||
queryClient.setQueryData<FollowsQueryData>(
|
||||
PROFILE_FOLLOWS_RQKEY(currentAccount.did),
|
||||
old => {
|
||||
@@ -307,7 +313,7 @@ export function useProfileFollowMutationQueue(
|
||||
{
|
||||
...old.pages[0],
|
||||
follows: [
|
||||
profile as AppBskyActorDefs.ProfileView,
|
||||
profile as app.bsky.actor.defs.ProfileView,
|
||||
...old.pages[0].follows,
|
||||
],
|
||||
},
|
||||
@@ -329,12 +335,12 @@ export function useProfileFollowMutationQueue(
|
||||
}
|
||||
|
||||
if (finalFollowingUri) {
|
||||
void agent.app.bsky.graph
|
||||
.getSuggestedFollowsByActor({
|
||||
actor: did,
|
||||
void client
|
||||
.call(app.bsky.graph.getSuggestedFollowsByActor, {
|
||||
actor: did as AtIdentifierString,
|
||||
})
|
||||
.then(res => {
|
||||
const dids = res.data.suggestions
|
||||
const dids = res.suggestions
|
||||
.filter(a => !a.viewer?.following)
|
||||
.map(a => a.did)
|
||||
.slice(0, 8)
|
||||
@@ -371,13 +377,13 @@ function useProfileFollowMutation(
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const {captureAction} = useProgressGuideControls()
|
||||
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||
let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined
|
||||
if (currentAccount) {
|
||||
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||
}
|
||||
@@ -396,7 +402,7 @@ function useProfileFollowMutation(
|
||||
position,
|
||||
contextProfileDid,
|
||||
})
|
||||
return await agent.follow(did)
|
||||
return await pdsClient.call(follow, {did: did as DidString})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -405,11 +411,11 @@ function useProfileUnfollowMutation(
|
||||
logContext: Metrics['profile:unfollow']['logContext'],
|
||||
) {
|
||||
const ax = useAnalytics()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {did: string; followUri: string}>({
|
||||
mutationFn: async ({followUri}) => {
|
||||
ax.metric('profile:unfollow', {logContext})
|
||||
return await agent.deleteFollow(followUri)
|
||||
return await pdsClient.call(deleteFollow, followUri as AtUriString)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -468,10 +474,10 @@ export function useProfileMuteMutationQueue(
|
||||
|
||||
function useProfileMuteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
await agent.mute(did)
|
||||
await pdsClient.call(muteActor, {actor: did as AtIdentifierString})
|
||||
},
|
||||
onSuccess() {
|
||||
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
||||
@@ -481,10 +487,10 @@ function useProfileMuteMutation() {
|
||||
|
||||
function useProfileUnmuteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
return useMutation<void, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
await agent.unmute(did)
|
||||
await pdsClient.call(unmuteActor, {actor: did as AtIdentifierString})
|
||||
},
|
||||
onSuccess() {
|
||||
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
||||
@@ -556,17 +562,17 @@ export function useProfileBlockMutationQueue(
|
||||
|
||||
function useProfileBlockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
return await agent.app.bsky.graph.block.create(
|
||||
{repo: currentAccount.did},
|
||||
{subject: did, createdAt: new Date().toISOString()},
|
||||
)
|
||||
return await pdsClient.create(app.bsky.graph.block, {
|
||||
subject: did as DidString,
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
})
|
||||
},
|
||||
onSuccess(_, {did}) {
|
||||
void queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
|
||||
@@ -577,7 +583,7 @@ function useProfileBlockMutation() {
|
||||
|
||||
function useProfileUnblockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, Error, {did: string; blockUri: string}>({
|
||||
mutationFn: async ({blockUri}) => {
|
||||
@@ -585,10 +591,7 @@ function useProfileUnblockMutation() {
|
||||
throw new Error('Not signed in')
|
||||
}
|
||||
const {rkey} = new AtUri(blockUri)
|
||||
await agent.app.bsky.graph.block.delete({
|
||||
repo: currentAccount.did,
|
||||
rkey,
|
||||
})
|
||||
await pdsClient.delete(app.bsky.graph.block, {rkey})
|
||||
},
|
||||
onSuccess(_, {did}) {
|
||||
resetProfilePostsQueries(queryClient, did, 1000)
|
||||
@@ -597,24 +600,27 @@ function useProfileUnblockMutation() {
|
||||
}
|
||||
|
||||
async function whenAppViewReady(
|
||||
agent: SessionAgent,
|
||||
client: Client,
|
||||
actor: string,
|
||||
fn: (res: AppBskyActorGetProfile.Response) => boolean,
|
||||
fn: (res: app.bsky.actor.getProfile.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
() => agent.app.bsky.actor.getProfile({actor}),
|
||||
() =>
|
||||
client.call(app.bsky.actor.getProfile, {
|
||||
actor: actor as AtIdentifierString,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileViewDetailed, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileViewDetailed, void> {
|
||||
const profileQueryDatas =
|
||||
queryClient.getQueriesData<AppBskyActorDefs.ProfileViewDetailed>({
|
||||
queryClient.getQueriesData<app.bsky.actor.defs.ProfileViewDetailed>({
|
||||
queryKey: [RQKEY_ROOT],
|
||||
})
|
||||
for (const [_queryKey, queryData] of profileQueryDatas) {
|
||||
@@ -626,7 +632,7 @@ export function* findAllProfilesInQueryData(
|
||||
}
|
||||
}
|
||||
const profilesQueryDatas =
|
||||
queryClient.getQueriesData<AppBskyActorGetProfiles.OutputSchema>({
|
||||
queryClient.getQueriesData<app.bsky.actor.getProfiles.$OutputBody>({
|
||||
queryKey: [profilesQueryKeyRoot],
|
||||
})
|
||||
for (const [_queryKey, queryData] of profilesQueryDatas) {
|
||||
@@ -644,8 +650,8 @@ export function* findAllProfilesInQueryData(
|
||||
export function findProfileQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): AppBskyActorDefs.ProfileViewDetailed | undefined {
|
||||
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewDetailed>(
|
||||
): app.bsky.actor.defs.ProfileViewDetailed | undefined {
|
||||
return queryClient.getQueryData<app.bsky.actor.defs.ProfileViewDetailed>(
|
||||
RQKEY(did),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import {type QueryClient, queryOptions, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {type ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve'
|
||||
import {
|
||||
type ResolveClients,
|
||||
type ResolvedLink,
|
||||
resolveGif,
|
||||
resolveLink,
|
||||
} from '#/lib/api/resolve'
|
||||
import {STALE} from '#/state/queries/index'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {useAgent, useChatClient, useLexClient} from '#/state/session'
|
||||
import {type Gif} from '#/features/gifPicker/types'
|
||||
|
||||
export const RQKEY_LINK_ROOT = 'resolve-link'
|
||||
@@ -11,24 +16,37 @@ export const RQKEY_LINK = (url: string) => [RQKEY_LINK_ROOT, url]
|
||||
export const RQKEY_GIF_ROOT = 'resolve-gif'
|
||||
export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url]
|
||||
|
||||
export function resolveLinkQueryOptions(agent: SessionAgent, url: string) {
|
||||
export function resolveLinkQueryOptions(clients: ResolveClients, url: string) {
|
||||
return queryOptions({
|
||||
staleTime: STALE.HOURS.ONE,
|
||||
queryKey: RQKEY_LINK(url),
|
||||
queryFn: () => resolveLink(agent, url),
|
||||
queryFn: () => resolveLink(clients, url),
|
||||
})
|
||||
}
|
||||
|
||||
export function useResolveLinkQuery(url: string) {
|
||||
/**
|
||||
* Bundle the clients the link resolver needs from the session hooks. The
|
||||
* appview client serves the `app.bsky.*` reads and handle resolution, the chat
|
||||
* client serves group join-link previews, and the bridge agent is still passed
|
||||
* through to the not-yet-migrated `getLinkMeta`.
|
||||
*/
|
||||
export function useResolveClients(): ResolveClients {
|
||||
const appview = useLexClient()
|
||||
const chat = useChatClient()
|
||||
const agent = useAgent()
|
||||
return useQuery(resolveLinkQueryOptions(agent, url))
|
||||
return {appview, chat, agent}
|
||||
}
|
||||
|
||||
export function useResolveLinkQuery(url: string) {
|
||||
const clients = useResolveClients()
|
||||
return useQuery(resolveLinkQueryOptions(clients, url))
|
||||
}
|
||||
export function fetchResolveLinkQuery(
|
||||
queryClient: QueryClient,
|
||||
agent: SessionAgent,
|
||||
clients: ResolveClients,
|
||||
url: string,
|
||||
) {
|
||||
return queryClient.fetchQuery(resolveLinkQueryOptions(agent, url))
|
||||
return queryClient.fetchQuery(resolveLinkQueryOptions(clients, url))
|
||||
}
|
||||
export function precacheResolveLinkQuery(
|
||||
queryClient: QueryClient,
|
||||
@@ -39,25 +57,20 @@ export function precacheResolveLinkQuery(
|
||||
}
|
||||
|
||||
export function useResolveGifQuery(gif: Gif) {
|
||||
const agent = useAgent()
|
||||
return useQuery({
|
||||
staleTime: STALE.HOURS.ONE,
|
||||
queryKey: RQKEY_GIF(gif.url),
|
||||
queryFn: async () => {
|
||||
return await resolveGif(agent, gif)
|
||||
return await resolveGif(gif)
|
||||
},
|
||||
})
|
||||
}
|
||||
export function fetchResolveGifQuery(
|
||||
queryClient: QueryClient,
|
||||
agent: SessionAgent,
|
||||
gif: Gif,
|
||||
) {
|
||||
export function fetchResolveGifQuery(queryClient: QueryClient, gif: Gif) {
|
||||
return queryClient.fetchQuery({
|
||||
staleTime: STALE.HOURS.ONE,
|
||||
queryKey: RQKEY_GIF(gif.url),
|
||||
queryFn: async () => {
|
||||
return await resolveGif(agent, gif)
|
||||
return await resolveGif(gif)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {AtUri, type HandleString} from '@atproto/syntax'
|
||||
import {type QueryClient, queryOptions, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {com} from '#/lexicons'
|
||||
import {useUnstableProfileViewCache} from './profile'
|
||||
|
||||
const RQKEY_ROOT = 'resolved-did'
|
||||
export const RQKEY = (didOrHandle: string) => [RQKEY_ROOT, didOrHandle]
|
||||
|
||||
const resolvedDidQueryOptions = (
|
||||
agent: SessionAgent,
|
||||
client: Client,
|
||||
getUnstableProfile: (did: string) => {did: string} | undefined,
|
||||
didOrHandle: string | undefined,
|
||||
) =>
|
||||
@@ -21,8 +23,10 @@ const resolvedDidQueryOptions = (
|
||||
// Just return the did if it's already one
|
||||
if (didOrHandle.startsWith('did:')) return didOrHandle
|
||||
|
||||
const res = await agent.resolveHandle({handle: didOrHandle})
|
||||
return res.data.did
|
||||
const {did} = await client.call(com.atproto.identity.resolveHandle, {
|
||||
handle: didOrHandle as HandleString,
|
||||
})
|
||||
return did
|
||||
},
|
||||
initialData: () => {
|
||||
// Return undefined if no did or handle
|
||||
@@ -37,11 +41,11 @@ export function useResolveUriQuery(uri: string | undefined) {
|
||||
const urip = new AtUri(uri || '')
|
||||
const host = urip.host
|
||||
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {getUnstableProfile} = useUnstableProfileViewCache()
|
||||
|
||||
return useQuery({
|
||||
...resolvedDidQueryOptions(agent, getUnstableProfile, host),
|
||||
...resolvedDidQueryOptions(client, getUnstableProfile, host),
|
||||
select: did => ({
|
||||
did,
|
||||
uri: AtUri.make(did, urip.collection, urip.rkey).toString(),
|
||||
@@ -50,11 +54,11 @@ export function useResolveUriQuery(uri: string | undefined) {
|
||||
}
|
||||
|
||||
export function useResolveDidQuery(didOrHandle: string | undefined) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const {getUnstableProfile} = useUnstableProfileViewCache()
|
||||
|
||||
return useQuery(
|
||||
resolvedDidQueryOptions(agent, getUnstableProfile, didOrHandle),
|
||||
resolvedDidQueryOptions(client, getUnstableProfile, didOrHandle),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,22 @@
|
||||
* tested in isolation (the search-posts query hook re-exports these).
|
||||
*/
|
||||
|
||||
import {type AppBskyFeedSearchPostsV2} from '@atproto/api'
|
||||
import {type l} from '@atproto/lex'
|
||||
|
||||
import {
|
||||
filtersToApiParams,
|
||||
type SearchFilters,
|
||||
} from '#/screens/Search/searchParams'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
/**
|
||||
* Input params for `app.bsky.feed.searchPostsV2`. Uses the schema's input type
|
||||
* (not `$Params`, which is the output type with defaults applied and `limit`
|
||||
* required) since these are the params we build up and pass to `client.call`.
|
||||
*/
|
||||
type SearchPostsV2Params = l.InferInput<
|
||||
typeof app.bsky.feed.searchPostsV2.$params
|
||||
>
|
||||
|
||||
const DATE_RE = /^\d{4}-\d{2}-\d{2}/
|
||||
|
||||
@@ -204,21 +214,27 @@ function mergeList(a?: string[], b?: string[]): string[] | undefined {
|
||||
export function buildSearchPostsV2Filters(
|
||||
embedded: Omit<ExtractedSearchParams, 'q'>,
|
||||
filters?: SearchFilters,
|
||||
): AppBskyFeedSearchPostsV2.QueryParams {
|
||||
): SearchPostsV2Params {
|
||||
const apiFilters = filters ? filtersToApiParams(filters) : {}
|
||||
const params: AppBskyFeedSearchPostsV2.QueryParams = {}
|
||||
const params: SearchPostsV2Params = {}
|
||||
|
||||
/*
|
||||
* The values below originate from free-text operators and the advanced-search
|
||||
* dialog as plain strings; the lexicon input params brand them by format
|
||||
* (at-identifier, uri, etc.). The backend validates these, so we assert the
|
||||
* branded types at assignment rather than validating client-side.
|
||||
*/
|
||||
const authors = mergeList(
|
||||
embedded.author ? [embedded.author] : undefined,
|
||||
apiFilters.authors,
|
||||
)
|
||||
if (authors) params.authors = authors
|
||||
if (authors) params.authors = authors as SearchPostsV2Params['authors']
|
||||
|
||||
const mentions = mergeList(
|
||||
embedded.mentions ? [embedded.mentions] : undefined,
|
||||
apiFilters.mentions,
|
||||
)
|
||||
if (mentions) params.mentions = mentions
|
||||
if (mentions) params.mentions = mentions as SearchPostsV2Params['mentions']
|
||||
|
||||
const domains = mergeList(
|
||||
embedded.domain ? [embedded.domain] : undefined,
|
||||
@@ -230,7 +246,7 @@ export function buildSearchPostsV2Filters(
|
||||
embedded.url ? [embedded.url] : undefined,
|
||||
apiFilters.urls,
|
||||
)
|
||||
if (urls) params.urls = urls
|
||||
if (urls) params.urls = urls as SearchPostsV2Params['urls']
|
||||
|
||||
const hashtags = mergeList(embedded.tag, apiFilters.hashtags)
|
||||
if (hashtags) params.hashtags = hashtags
|
||||
@@ -250,12 +266,16 @@ export function buildSearchPostsV2Filters(
|
||||
* are always include), so they pass straight through from the dialog filters.
|
||||
*/
|
||||
if (apiFilters.excludeAuthors)
|
||||
params.excludeAuthors = apiFilters.excludeAuthors
|
||||
params.excludeAuthors =
|
||||
apiFilters.excludeAuthors as SearchPostsV2Params['excludeAuthors']
|
||||
if (apiFilters.excludeMentions)
|
||||
params.excludeMentions = apiFilters.excludeMentions
|
||||
params.excludeMentions =
|
||||
apiFilters.excludeMentions as SearchPostsV2Params['excludeMentions']
|
||||
if (apiFilters.excludeDomains)
|
||||
params.excludeDomains = apiFilters.excludeDomains
|
||||
if (apiFilters.excludeUrls) params.excludeUrls = apiFilters.excludeUrls
|
||||
if (apiFilters.excludeUrls)
|
||||
params.excludeUrls =
|
||||
apiFilters.excludeUrls as SearchPostsV2Params['excludeUrls']
|
||||
if (apiFilters.excludeHashtags)
|
||||
params.excludeHashtags = apiFilters.excludeHashtags
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {useCallback, useMemo, useRef} from 'react'
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedSearchPostsV2,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -13,8 +9,9 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type SearchFilters} from '#/screens/Search/searchParams'
|
||||
import {app} from '#/lexicons'
|
||||
import {
|
||||
appendFromMe,
|
||||
buildSearchPostsV2Filters,
|
||||
@@ -48,7 +45,7 @@ export function useSearchPostsV2Query({
|
||||
enabled?: boolean
|
||||
filters?: SearchFilters
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const selectArgs = useMemo(
|
||||
() => ({
|
||||
@@ -59,15 +56,15 @@ export function useSearchPostsV2Query({
|
||||
[query, filters?.author, filters?.from, moderationOpts],
|
||||
)
|
||||
const lastRun = useRef<{
|
||||
data: InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>
|
||||
data: InfiniteData<app.bsky.feed.searchPostsV2.$OutputBody>
|
||||
args: typeof selectArgs
|
||||
result: InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>
|
||||
result: InfiniteData<app.bsky.feed.searchPostsV2.$OutputBody>
|
||||
} | null>(null)
|
||||
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedSearchPostsV2.OutputSchema,
|
||||
app.bsky.feed.searchPostsV2.$OutputBody,
|
||||
Error,
|
||||
InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>,
|
||||
InfiniteData<app.bsky.feed.searchPostsV2.$OutputBody>,
|
||||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
@@ -81,7 +78,7 @@ export function useSearchPostsV2Query({
|
||||
const {q, ...embedded} = extractSearchPostsParams(query)
|
||||
const builtFilters = buildSearchPostsV2Filters(embedded, filters)
|
||||
const finalQuery = appendFromMe(q, filters?.from === 'me')
|
||||
const res = await agent.app.bsky.feed.searchPostsV2({
|
||||
return await client.call(app.bsky.feed.searchPostsV2, {
|
||||
...builtFilters,
|
||||
query: finalQuery,
|
||||
limit: 25,
|
||||
@@ -93,13 +90,12 @@ export function useSearchPostsV2Query({
|
||||
sort: sort === 'latest' ? 'recent' : sort,
|
||||
allTime: true,
|
||||
})
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
enabled: enabled ?? !!moderationOpts,
|
||||
select: useCallback(
|
||||
(data: InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>) => {
|
||||
(data: InfiniteData<app.bsky.feed.searchPostsV2.$OutputBody>) => {
|
||||
const {moderationOpts, isSearchingSpecificUser} = selectArgs
|
||||
|
||||
/*
|
||||
@@ -175,9 +171,9 @@ export function useSearchPostsV2Query({
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, undefined> {
|
||||
): Generator<app.bsky.feed.defs.PostView, undefined> {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyFeedSearchPostsV2.OutputSchema>
|
||||
InfiniteData<app.bsky.feed.searchPostsV2.$OutputBody>
|
||||
>({
|
||||
queryKey: [searchPostsQueryKeyRoot],
|
||||
})
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyGraphDefs,
|
||||
type AppBskyGraphGetStarterPack,
|
||||
AppBskyGraphStarterpack,
|
||||
type AppBskyRichtextFacet,
|
||||
AtUri,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {AtUri, type AtUriString, type DatetimeString} from '@atproto/syntax'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {
|
||||
type QueryClient,
|
||||
useMutation,
|
||||
@@ -25,7 +19,8 @@ import {
|
||||
import {invalidateActorStarterPacksQuery} from '#/state/queries/actor-starter-packs'
|
||||
import {STALE} from '#/state/queries/index'
|
||||
import {invalidateListMembersQuery} from '#/state/queries/list-members'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient} from '#/state/session'
|
||||
import {app, com} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
const RQKEY_ROOT = 'starter-pack'
|
||||
@@ -55,9 +50,9 @@ export function useStarterPackQuery({
|
||||
did?: string
|
||||
rkey?: string
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
|
||||
return useQuery<AppBskyGraphDefs.StarterPackView>({
|
||||
return useQuery<app.bsky.graph.defs.StarterPackView>({
|
||||
queryKey: RQKEY(uri ? {uri} : {did, rkey}),
|
||||
queryFn: async () => {
|
||||
if (!uri) {
|
||||
@@ -66,10 +61,10 @@ export function useStarterPackQuery({
|
||||
uri = httpStarterPackUriToAtUri(uri) as string
|
||||
}
|
||||
|
||||
const res = await agent.app.bsky.graph.getStarterPack({
|
||||
starterPack: uri,
|
||||
const res = await client.call(app.bsky.graph.getStarterPack, {
|
||||
starterPack: uri as AtUriString,
|
||||
})
|
||||
return res.data.starterPack
|
||||
return res.starterPack
|
||||
},
|
||||
enabled: Boolean(uri) || Boolean(did && rkey),
|
||||
staleTime: STALE.MINUTES.FIVE,
|
||||
@@ -92,7 +87,7 @@ interface UseCreateStarterPackMutationParams {
|
||||
name: string
|
||||
description?: string
|
||||
profiles: bsky.profile.AnyProfileView[]
|
||||
feeds?: AppBskyFeedDefs.GeneratorView[]
|
||||
feeds?: app.bsky.feed.defs.GeneratorView[]
|
||||
}
|
||||
|
||||
export function useCreateStarterPackMutation({
|
||||
@@ -103,7 +98,8 @@ export function useCreateStarterPackMutation({
|
||||
onError: (e: Error) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation<
|
||||
{uri: string; cid: string},
|
||||
@@ -111,10 +107,10 @@ export function useCreateStarterPackMutation({
|
||||
UseCreateStarterPackMutationParams
|
||||
>({
|
||||
mutationFn: async ({name, description, feeds, profiles}) => {
|
||||
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
|
||||
let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||
if (description) {
|
||||
const rt = new RichText({text: description})
|
||||
await rt.detectFacets(agent)
|
||||
await rt.detectFacets(pdsClient)
|
||||
descriptionFacets = rt.facets
|
||||
}
|
||||
|
||||
@@ -124,30 +120,25 @@ export function useCreateStarterPackMutation({
|
||||
description,
|
||||
profiles,
|
||||
descriptionFacets,
|
||||
agent,
|
||||
client: pdsClient,
|
||||
})
|
||||
|
||||
return await agent.app.bsky.graph.starterpack.create(
|
||||
{
|
||||
repo: agent.assertDid,
|
||||
},
|
||||
{
|
||||
name,
|
||||
description,
|
||||
descriptionFacets,
|
||||
list: listRes?.uri,
|
||||
feeds: feeds?.map(f => ({uri: f.uri})),
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
return await pdsClient.create(app.bsky.graph.starterpack, {
|
||||
name,
|
||||
description,
|
||||
descriptionFacets,
|
||||
list: listRes?.uri as AtUriString,
|
||||
feeds: feeds?.map(f => ({uri: f.uri})),
|
||||
createdAt: new Date().toISOString() as DatetimeString,
|
||||
})
|
||||
},
|
||||
onSuccess: async data => {
|
||||
await whenAppViewReady(agent, data.uri, v => {
|
||||
return typeof v?.data.starterPack.uri === 'string'
|
||||
await whenAppViewReady(appviewClient, data.uri, v => {
|
||||
return typeof v?.starterPack.uri === 'string'
|
||||
})
|
||||
await invalidateActorStarterPacksQuery({
|
||||
queryClient,
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
})
|
||||
onSuccess(data)
|
||||
},
|
||||
@@ -165,14 +156,15 @@ export function useEditStarterPackMutation({
|
||||
onError: (error: Error) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useMutation<
|
||||
void,
|
||||
Error,
|
||||
UseCreateStarterPackMutationParams & {
|
||||
currentStarterPack: AppBskyGraphDefs.StarterPackView
|
||||
currentListItems: AppBskyGraphDefs.ListItemView[]
|
||||
currentStarterPack: app.bsky.graph.defs.StarterPackView
|
||||
currentListItems: app.bsky.graph.defs.ListItemView[]
|
||||
}
|
||||
>({
|
||||
mutationFn: async ({
|
||||
@@ -183,32 +175,36 @@ export function useEditStarterPackMutation({
|
||||
currentStarterPack,
|
||||
currentListItems,
|
||||
}) => {
|
||||
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
|
||||
let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||
if (description) {
|
||||
const rt = new RichText({text: description})
|
||||
await rt.detectFacets(agent)
|
||||
await rt.detectFacets(pdsClient)
|
||||
descriptionFacets = rt.facets
|
||||
}
|
||||
|
||||
if (!AppBskyGraphStarterpack.isRecord(currentStarterPack.record)) {
|
||||
if (!bsky.isType(app.bsky.graph.starterpack, currentStarterPack.record)) {
|
||||
throw new Error('Invalid starter pack')
|
||||
}
|
||||
|
||||
const removedItems = currentListItems.filter(
|
||||
i =>
|
||||
i.subject.did !== agent.session?.did &&
|
||||
i.subject.did !== pdsClient.did &&
|
||||
!profiles.find(p => p.did === i.subject.did && p.did),
|
||||
)
|
||||
if (removedItems.length !== 0) {
|
||||
const chunks = chunk(removedItems, 50)
|
||||
for (const chunk of chunks) {
|
||||
await agent.com.atproto.repo.applyWrites({
|
||||
repo: agent.session!.did,
|
||||
writes: chunk.map(i => ({
|
||||
$type: 'com.atproto.repo.applyWrites#delete',
|
||||
collection: 'app.bsky.graph.listitem',
|
||||
rkey: new AtUri(i.uri).rkey,
|
||||
})),
|
||||
await pdsClient.call(com.atproto.repo.applyWrites, {
|
||||
repo: pdsClient.assertDid,
|
||||
writes: chunk.map(
|
||||
(
|
||||
i,
|
||||
): com.atproto.repo.applyWrites.$InputBody['writes'][number] => ({
|
||||
$type: 'com.atproto.repo.applyWrites#delete',
|
||||
collection: 'app.bsky.graph.listitem',
|
||||
rkey: new AtUri(i.uri).rkey,
|
||||
}),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -219,28 +215,33 @@ export function useEditStarterPackMutation({
|
||||
if (addedProfiles.length > 0) {
|
||||
const chunks = chunk(addedProfiles, 50)
|
||||
for (const chunk of chunks) {
|
||||
await agent.com.atproto.repo.applyWrites({
|
||||
repo: agent.session!.did,
|
||||
writes: chunk.map(p => ({
|
||||
$type: 'com.atproto.repo.applyWrites#create',
|
||||
collection: 'app.bsky.graph.listitem',
|
||||
value: {
|
||||
$type: 'app.bsky.graph.listitem',
|
||||
subject: p.did,
|
||||
list: currentStarterPack.list?.uri,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
})),
|
||||
await pdsClient.call(com.atproto.repo.applyWrites, {
|
||||
repo: pdsClient.assertDid,
|
||||
writes: chunk.map(
|
||||
(
|
||||
p,
|
||||
): com.atproto.repo.applyWrites.$InputBody['writes'][number] => ({
|
||||
$type: 'com.atproto.repo.applyWrites#create',
|
||||
collection: 'app.bsky.graph.listitem',
|
||||
value: {
|
||||
$type: 'app.bsky.graph.listitem',
|
||||
subject: p.did,
|
||||
list: currentStarterPack.list?.uri,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
}),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const rkey = parseStarterPackUri(currentStarterPack.uri)!.rkey
|
||||
await agent.com.atproto.repo.putRecord({
|
||||
repo: agent.session!.did,
|
||||
await pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo: pdsClient.assertDid,
|
||||
collection: 'app.bsky.graph.starterpack',
|
||||
rkey,
|
||||
record: {
|
||||
$type: 'app.bsky.graph.starterpack',
|
||||
name,
|
||||
description,
|
||||
descriptionFacets,
|
||||
@@ -253,12 +254,12 @@ export function useEditStarterPackMutation({
|
||||
},
|
||||
onSuccess: async (_, {currentStarterPack}) => {
|
||||
const parsed = parseStarterPackUri(currentStarterPack.uri)
|
||||
await whenAppViewReady(agent, currentStarterPack.uri, v => {
|
||||
return currentStarterPack.cid !== v?.data.starterPack.cid
|
||||
await whenAppViewReady(appviewClient, currentStarterPack.uri, v => {
|
||||
return currentStarterPack.cid !== v?.starterPack.cid
|
||||
})
|
||||
await invalidateActorStarterPacksQuery({
|
||||
queryClient,
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
})
|
||||
if (currentStarterPack.list) {
|
||||
await invalidateListMembersQuery({
|
||||
@@ -268,7 +269,7 @@ export function useEditStarterPackMutation({
|
||||
}
|
||||
await invalidateStarterPack({
|
||||
queryClient,
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
rkey: parsed!.rkey,
|
||||
})
|
||||
onSuccess()
|
||||
@@ -286,35 +287,34 @@ export function useDeleteStarterPackMutation({
|
||||
onSuccess: () => void
|
||||
onError: (error: Error) => void
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({listUri, rkey}: {listUri?: string; rkey: string}) => {
|
||||
if (!agent.session) {
|
||||
throw new Error(`Requires signed in user`)
|
||||
}
|
||||
const did = pdsClient.assertDid
|
||||
|
||||
if (listUri) {
|
||||
await agent.app.bsky.graph.list.delete({
|
||||
repo: agent.session.did,
|
||||
await pdsClient.delete(app.bsky.graph.list, {
|
||||
repo: did,
|
||||
rkey: new AtUri(listUri).rkey,
|
||||
})
|
||||
}
|
||||
await agent.app.bsky.graph.starterpack.delete({
|
||||
repo: agent.session.did,
|
||||
await pdsClient.delete(app.bsky.graph.starterpack, {
|
||||
repo: did,
|
||||
rkey,
|
||||
})
|
||||
},
|
||||
onSuccess: async (_, {listUri, rkey}) => {
|
||||
const uri = createStarterPackUri({
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
rkey,
|
||||
})
|
||||
|
||||
if (uri) {
|
||||
await whenAppViewReady(agent, uri, v => {
|
||||
return Boolean(v?.data?.starterPack) === false
|
||||
await whenAppViewReady(appviewClient, uri, v => {
|
||||
return Boolean(v?.starterPack) === false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -323,11 +323,11 @@ export function useDeleteStarterPackMutation({
|
||||
}
|
||||
await invalidateActorStarterPacksQuery({
|
||||
queryClient,
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
})
|
||||
await invalidateStarterPack({
|
||||
queryClient,
|
||||
did: agent.session!.did,
|
||||
did: pdsClient.assertDid,
|
||||
rkey,
|
||||
})
|
||||
onSuccess()
|
||||
@@ -339,48 +339,51 @@ export function useDeleteStarterPackMutation({
|
||||
}
|
||||
|
||||
async function whenAppViewReady(
|
||||
agent: SessionAgent,
|
||||
client: Client,
|
||||
uri: string,
|
||||
fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean,
|
||||
fn: (res?: app.bsky.graph.getStarterPack.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
() => agent.app.bsky.graph.getStarterPack({starterPack: uri}),
|
||||
() =>
|
||||
client.call(app.bsky.graph.getStarterPack, {
|
||||
starterPack: uri as AtUriString,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function precacheStarterPack(
|
||||
queryClient: QueryClient,
|
||||
starterPack:
|
||||
| AppBskyGraphDefs.StarterPackViewBasic
|
||||
| AppBskyGraphDefs.StarterPackView,
|
||||
| app.bsky.graph.defs.StarterPackViewBasic
|
||||
| app.bsky.graph.defs.StarterPackView,
|
||||
) {
|
||||
if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) {
|
||||
if (!bsky.isType(app.bsky.graph.starterpack, starterPack.record)) {
|
||||
return
|
||||
}
|
||||
|
||||
let starterPackView: AppBskyGraphDefs.StarterPackView | undefined
|
||||
if (AppBskyGraphDefs.isStarterPackView(starterPack)) {
|
||||
let starterPackView: app.bsky.graph.defs.StarterPackView | undefined
|
||||
if (bsky.isType(app.bsky.graph.defs.starterPackView, starterPack)) {
|
||||
starterPackView = starterPack
|
||||
} else if (
|
||||
AppBskyGraphDefs.isStarterPackViewBasic(starterPack) &&
|
||||
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
|
||||
bsky.isType(app.bsky.graph.defs.starterPackViewBasic, starterPack) &&
|
||||
bsky.matches(app.bsky.graph.starterpack, starterPack.record)
|
||||
) {
|
||||
let feeds: AppBskyFeedDefs.GeneratorView[] | undefined
|
||||
let feeds: app.bsky.feed.defs.GeneratorView[] | undefined
|
||||
if (starterPack.record.feeds) {
|
||||
feeds = []
|
||||
for (const feed of starterPack.record.feeds) {
|
||||
// note: types are wrong? claims to be `FeedItem`, but we actually
|
||||
// get un$typed `GeneratorView` objects here -sfn
|
||||
if (bsky.validate(feed, AppBskyFeedDefs.validateGeneratorView)) {
|
||||
if (bsky.matches(app.bsky.feed.defs.generatorView, feed)) {
|
||||
feeds.push(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listView: AppBskyGraphDefs.ListViewBasic = {
|
||||
const listView: app.bsky.graph.defs.ListViewBasic = {
|
||||
uri: starterPack.record.list,
|
||||
// This will be populated once the data from server is fetched
|
||||
cid: '',
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyActorGetSuggestions,
|
||||
type AppBskyGraphGetSuggestedFollowsByActor,
|
||||
} from '@atproto/api'
|
||||
import {type AtIdentifierString} from '@atproto/syntax'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
@@ -12,8 +8,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
|
||||
|
||||
@@ -32,18 +28,21 @@ export function useSuggestedFollowsByActorQuery({
|
||||
enabled?: boolean
|
||||
staleTime?: number
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const client = useAppviewClient()
|
||||
return useQuery({
|
||||
staleTime,
|
||||
queryKey: suggestedFollowsByActorQueryKey(did),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
|
||||
actor: did,
|
||||
})
|
||||
const suggestions = res.data.suggestions.filter(
|
||||
const data = await client.call(
|
||||
app.bsky.graph.getSuggestedFollowsByActor,
|
||||
{
|
||||
actor: did as AtIdentifierString,
|
||||
},
|
||||
)
|
||||
const suggestions = data.suggestions.filter(
|
||||
profile => !profile.viewer?.following,
|
||||
)
|
||||
return {suggestions, recId: res.data.recIdStr}
|
||||
return {suggestions, recId: data.recIdStr}
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
@@ -102,7 +101,7 @@ export function useSuggestedFollowsByActorWithDismiss({
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did)
|
||||
yield* findAllProfilesInSuggestedFollowsByActorQueryData(queryClient, did)
|
||||
}
|
||||
@@ -112,7 +111,7 @@ function* findAllProfilesInSuggestedFollowsQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyActorGetSuggestions.OutputSchema>
|
||||
InfiniteData<app.bsky.actor.getSuggestions.$OutputBody>
|
||||
>({
|
||||
queryKey: [suggestedFollowsQueryKeyRoot],
|
||||
})
|
||||
@@ -135,7 +134,7 @@ function* findAllProfilesInSuggestedFollowsByActorQueryData(
|
||||
did: string,
|
||||
) {
|
||||
const queryDatas =
|
||||
queryClient.getQueriesData<AppBskyGraphGetSuggestedFollowsByActor.OutputSchema>(
|
||||
queryClient.getQueriesData<app.bsky.graph.getSuggestedFollowsByActor.$OutputBody>(
|
||||
{
|
||||
queryKey: [suggestedFollowsByActorQueryKeyRoot],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {type AppBskyFeedDefs, AppBskyFeedThreadgate, AtUri} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex-client'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry, retry} from '#/lib/async/retry'
|
||||
@@ -12,8 +13,9 @@ import {
|
||||
threadgateViewToAllowUISetting,
|
||||
} from '#/state/queries/threadgate/util'
|
||||
import {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread'
|
||||
import {type SessionAgent, useAgent} from '#/state/session'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies'
|
||||
import {app, com} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export * from '#/state/queries/threadgate/types'
|
||||
@@ -35,9 +37,9 @@ export function useThreadgateRecordQuery({
|
||||
initialData,
|
||||
}: {
|
||||
postUri?: string
|
||||
initialData?: AppBskyFeedThreadgate.Record
|
||||
initialData?: app.bsky.feed.threadgate.Main
|
||||
} = {}) {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
|
||||
return useQuery({
|
||||
enabled: !!postUri,
|
||||
@@ -46,7 +48,7 @@ export function useThreadgateRecordQuery({
|
||||
staleTime: STALE.MINUTES.ONE,
|
||||
async queryFn() {
|
||||
return getThreadgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri: postUri!,
|
||||
})
|
||||
},
|
||||
@@ -63,7 +65,7 @@ export function useThreadgateViewQuery({
|
||||
initialData,
|
||||
}: {
|
||||
postUri?: string
|
||||
initialData?: AppBskyFeedDefs.ThreadgateView
|
||||
initialData?: app.bsky.feed.defs.ThreadgateView
|
||||
} = {}) {
|
||||
const getPost = useGetPost()
|
||||
|
||||
@@ -80,24 +82,23 @@ export function useThreadgateViewQuery({
|
||||
}
|
||||
|
||||
export async function getThreadgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
}): Promise<AppBskyFeedThreadgate.Record | null> {
|
||||
}): Promise<app.bsky.feed.threadgate.Main | null> {
|
||||
const urip = new AtUri(postUri)
|
||||
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
const {did} = await pdsClient.call(com.atproto.identity.resolveHandle, {
|
||||
handle: urip.host as `${string}.${string}`,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
urip.host = did
|
||||
}
|
||||
|
||||
try {
|
||||
const {data} = await retry(
|
||||
const data = await retry(
|
||||
2,
|
||||
e => {
|
||||
/*
|
||||
@@ -111,17 +112,14 @@ export async function getThreadgateRecord({
|
||||
return true
|
||||
},
|
||||
() =>
|
||||
agent.api.com.atproto.repo.getRecord({
|
||||
pdsClient.call(com.atproto.repo.getRecord, {
|
||||
repo: urip.host,
|
||||
collection: 'app.bsky.feed.threadgate',
|
||||
rkey: urip.rkey,
|
||||
}),
|
||||
)
|
||||
|
||||
if (
|
||||
data.value &&
|
||||
bsky.validate(data.value, AppBskyFeedThreadgate.validateRecord)
|
||||
) {
|
||||
if (data.value && bsky.matches(app.bsky.feed.threadgate, data.value)) {
|
||||
return data.value
|
||||
} else {
|
||||
return null
|
||||
@@ -141,13 +139,13 @@ export async function getThreadgateRecord({
|
||||
}
|
||||
|
||||
export async function writeThreadgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
threadgate,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
threadgate: AppBskyFeedThreadgate.Record
|
||||
threadgate: app.bsky.feed.threadgate.Main
|
||||
}) {
|
||||
const postUrip = new AtUri(postUri)
|
||||
const record = createThreadgateRecord({
|
||||
@@ -157,8 +155,8 @@ export async function writeThreadgateRecord({
|
||||
})
|
||||
|
||||
await networkRetry(2, () =>
|
||||
agent.api.com.atproto.repo.putRecord({
|
||||
repo: agent.session!.did,
|
||||
pdsClient.call(com.atproto.repo.putRecord, {
|
||||
repo: pdsClient.assertDid,
|
||||
collection: 'app.bsky.feed.threadgate',
|
||||
rkey: postUrip.rkey,
|
||||
record,
|
||||
@@ -168,25 +166,25 @@ export async function writeThreadgateRecord({
|
||||
|
||||
export async function upsertThreadgate(
|
||||
{
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
},
|
||||
callback: (
|
||||
threadgate: AppBskyFeedThreadgate.Record | null,
|
||||
) => Promise<AppBskyFeedThreadgate.Record | undefined>,
|
||||
threadgate: app.bsky.feed.threadgate.Main | null,
|
||||
) => Promise<app.bsky.feed.threadgate.Main | undefined>,
|
||||
) {
|
||||
const prev = await getThreadgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
})
|
||||
const next = await callback(prev)
|
||||
if (!next) return
|
||||
validateThreadgateRecordOrThrow(next)
|
||||
await writeThreadgateRecord({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
threadgate: next,
|
||||
})
|
||||
@@ -196,15 +194,15 @@ export async function upsertThreadgate(
|
||||
* Update the allow list for a threadgate record.
|
||||
*/
|
||||
export async function updateThreadgateAllow({
|
||||
agent,
|
||||
pdsClient,
|
||||
postUri,
|
||||
allow,
|
||||
}: {
|
||||
agent: SessionAgent
|
||||
pdsClient: Client
|
||||
postUri: string
|
||||
allow: ThreadgateAllowUISetting[]
|
||||
}) {
|
||||
return upsertThreadgate({agent, postUri}, async prev => {
|
||||
return upsertThreadgate({pdsClient, postUri}, async prev => {
|
||||
if (prev) {
|
||||
return {
|
||||
...prev,
|
||||
@@ -220,7 +218,7 @@ export async function updateThreadgateAllow({
|
||||
}
|
||||
|
||||
export function useSetThreadgateAllowMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const getPost = useGetPost()
|
||||
const updatePostThreadThreadgate = useUpdatePostThreadThreadgateQueryCache()
|
||||
@@ -233,7 +231,7 @@ export function useSetThreadgateAllowMutation() {
|
||||
postUri: string
|
||||
allow: ThreadgateAllowUISetting[]
|
||||
}) => {
|
||||
return upsertThreadgate({agent, postUri}, async prev => {
|
||||
return upsertThreadgate({pdsClient, postUri}, async prev => {
|
||||
if (prev) {
|
||||
return {
|
||||
...prev,
|
||||
@@ -248,7 +246,7 @@ export function useSetThreadgateAllowMutation() {
|
||||
})
|
||||
},
|
||||
async onSuccess(_, {postUri, allow}) {
|
||||
const data = await retry<AppBskyFeedDefs.ThreadgateView | undefined>(
|
||||
const data = await retry<app.bsky.feed.defs.ThreadgateView | undefined>(
|
||||
5, // 5 tries
|
||||
_e => true,
|
||||
async () => {
|
||||
@@ -285,7 +283,7 @@ export function useSetThreadgateAllowMutation() {
|
||||
}
|
||||
|
||||
export function useToggleReplyVisibilityMutation() {
|
||||
const agent = useAgent()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
const hiddenReplies = useThreadgateHiddenReplyUrisAPI()
|
||||
|
||||
@@ -305,7 +303,7 @@ export function useToggleReplyVisibilityMutation() {
|
||||
hiddenReplies.removeHiddenReplyUri(replyUri)
|
||||
}
|
||||
|
||||
await upsertThreadgate({agent, postUri}, async prev => {
|
||||
await upsertThreadgate({pdsClient, postUri}, async prev => {
|
||||
if (prev) {
|
||||
if (action === 'hide') {
|
||||
return mergeThreadgateRecords(prev, {
|
||||
@@ -358,9 +356,9 @@ export class InvalidInteractionSettingsError extends Error {
|
||||
}
|
||||
|
||||
export function validateThreadgateRecordOrThrow(
|
||||
record: AppBskyFeedThreadgate.Record,
|
||||
record: app.bsky.feed.threadgate.Main,
|
||||
) {
|
||||
const result = AppBskyFeedThreadgate.validateRecord(record)
|
||||
const result = bsky.safeParse(app.bsky.feed.threadgate, record)
|
||||
|
||||
if (result.success) {
|
||||
if ((result.value.hiddenReplies?.length ?? 0) > MAX_HIDDEN_REPLIES) {
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import {type AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api'
|
||||
import {type AtUriString, toDatetimeString} from '@atproto/syntax'
|
||||
|
||||
import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function threadgateViewToAllowUISetting(
|
||||
threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
|
||||
threadgateView: app.bsky.feed.defs.ThreadgateView | undefined,
|
||||
): ThreadgateAllowUISetting[] {
|
||||
// Validate the record for clarity, since backwards compat code is a little confusing
|
||||
const threadgate =
|
||||
threadgateView &&
|
||||
bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord)
|
||||
bsky.matches(app.bsky.feed.threadgate, threadgateView.record)
|
||||
? threadgateView.record
|
||||
: undefined
|
||||
return threadgateRecordToAllowUISetting(threadgate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a full {@link AppBskyFeedThreadgate.Record} to a list of
|
||||
* Converts a full {@link app.bsky.feed.threadgate.Main} to a list of
|
||||
* {@link ThreadgateAllowUISetting}, for use by app UI.
|
||||
*/
|
||||
export function threadgateRecordToAllowUISetting(
|
||||
threadgate: AppBskyFeedThreadgate.Record | undefined,
|
||||
threadgate: app.bsky.feed.threadgate.Main | undefined,
|
||||
): ThreadgateAllowUISetting[] {
|
||||
/*
|
||||
* If `threadgate` doesn't exist (default), or if `threadgate.allow === undefined`, it means
|
||||
@@ -40,13 +41,13 @@ export function threadgateRecordToAllowUISetting(
|
||||
const settings: ThreadgateAllowUISetting[] = threadgate.allow
|
||||
.map(allow => {
|
||||
let setting: ThreadgateAllowUISetting | undefined
|
||||
if (AppBskyFeedThreadgate.isMentionRule(allow)) {
|
||||
if (bsky.isType(app.bsky.feed.threadgate.mentionRule, allow)) {
|
||||
setting = {type: 'mention'}
|
||||
} else if (AppBskyFeedThreadgate.isFollowingRule(allow)) {
|
||||
} else if (bsky.isType(app.bsky.feed.threadgate.followingRule, allow)) {
|
||||
setting = {type: 'following'}
|
||||
} else if (AppBskyFeedThreadgate.isListRule(allow)) {
|
||||
} else if (bsky.isType(app.bsky.feed.threadgate.listRule, allow)) {
|
||||
setting = {type: 'list', list: allow.list}
|
||||
} else if (AppBskyFeedThreadgate.isFollowerRule(allow)) {
|
||||
} else if (bsky.isType(app.bsky.feed.threadgate.followerRule, allow)) {
|
||||
setting = {type: 'followers'}
|
||||
}
|
||||
return setting
|
||||
@@ -57,7 +58,7 @@ export function threadgateRecordToAllowUISetting(
|
||||
|
||||
/**
|
||||
* Converts an array of {@link ThreadgateAllowUISetting} to the `allow` prop on
|
||||
* {@link AppBskyFeedThreadgate.Record}.
|
||||
* {@link app.bsky.feed.threadgate.Main}.
|
||||
*
|
||||
* If the `allow` property on the record is undefined, we infer that to mean
|
||||
* that everyone can reply. If it's an empty array, we infer that to mean that
|
||||
@@ -65,12 +66,12 @@ export function threadgateRecordToAllowUISetting(
|
||||
*/
|
||||
export function threadgateAllowUISettingToAllowRecordValue(
|
||||
threadgate: ThreadgateAllowUISetting[],
|
||||
): AppBskyFeedThreadgate.Record['allow'] {
|
||||
): app.bsky.feed.threadgate.Main['allow'] {
|
||||
if (threadgate.find(v => v.type === 'everybody')) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let allow: Exclude<AppBskyFeedThreadgate.Record['allow'], undefined> = []
|
||||
let allow: Exclude<app.bsky.feed.threadgate.Main['allow'], undefined> = []
|
||||
|
||||
if (!threadgate.find(v => v.type === 'nobody')) {
|
||||
for (const rule of threadgate) {
|
||||
@@ -83,7 +84,7 @@ export function threadgateAllowUISettingToAllowRecordValue(
|
||||
} else if (rule.type === 'list') {
|
||||
allow.push({
|
||||
$type: 'app.bsky.feed.threadgate#listRule',
|
||||
list: rule.list,
|
||||
list: rule.list as AtUriString,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -93,18 +94,20 @@ export function threadgateAllowUISettingToAllowRecordValue(
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two {@link AppBskyFeedThreadgate.Record} objects, combining their
|
||||
* Merges two {@link app.bsky.feed.threadgate.Main} objects, combining their
|
||||
* `allow` and `hiddenReplies` arrays and de-deduplicating them.
|
||||
*
|
||||
* Note: `allow` can be undefined here, be sure you don't accidentally set it
|
||||
* to an empty array. See other comments in this file.
|
||||
*/
|
||||
export function mergeThreadgateRecords(
|
||||
prev: AppBskyFeedThreadgate.Record,
|
||||
next: Partial<AppBskyFeedThreadgate.Record>,
|
||||
): AppBskyFeedThreadgate.Record {
|
||||
prev: app.bsky.feed.threadgate.Main,
|
||||
next: Omit<Partial<app.bsky.feed.threadgate.Main>, 'hiddenReplies'> & {
|
||||
hiddenReplies?: string[]
|
||||
},
|
||||
): app.bsky.feed.threadgate.Main {
|
||||
// can be undefined if everyone can reply!
|
||||
const allow: AppBskyFeedThreadgate.Record['allow'] | undefined =
|
||||
const allow: app.bsky.feed.threadgate.Main['allow'] | undefined =
|
||||
prev.allow || next.allow
|
||||
? [...(prev.allow || []), ...(next.allow || [])].filter(
|
||||
(v, i, a) => a.findIndex(t => t.$type === v.$type) === i,
|
||||
@@ -112,7 +115,7 @@ export function mergeThreadgateRecords(
|
||||
: undefined
|
||||
const hiddenReplies = Array.from(
|
||||
new Set([...(prev.hiddenReplies || []), ...(next.hiddenReplies || [])]),
|
||||
)
|
||||
) as AtUriString[]
|
||||
|
||||
return createThreadgateRecord({
|
||||
post: prev.post,
|
||||
@@ -122,21 +125,28 @@ export function mergeThreadgateRecords(
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AppBskyFeedThreadgate.Record} object with the given
|
||||
* properties.
|
||||
* Create a new {@link app.bsky.feed.threadgate.Main} object with the given
|
||||
* properties. `post` is accepted as a plain string (callers hold raw AT-URIs)
|
||||
* and asserted to the branded `AtUriString` here.
|
||||
*/
|
||||
export function createThreadgateRecord(
|
||||
threadgate: Partial<AppBskyFeedThreadgate.Record>,
|
||||
): AppBskyFeedThreadgate.Record {
|
||||
threadgate: Omit<
|
||||
Partial<app.bsky.feed.threadgate.Main>,
|
||||
'post' | 'hiddenReplies'
|
||||
> & {
|
||||
post?: string
|
||||
hiddenReplies?: string[]
|
||||
},
|
||||
): app.bsky.feed.threadgate.Main {
|
||||
if (!threadgate.post) {
|
||||
throw new Error('Cannot create a threadgate record without a post URI')
|
||||
}
|
||||
|
||||
return {
|
||||
$type: 'app.bsky.feed.threadgate',
|
||||
post: threadgate.post,
|
||||
createdAt: new Date().toISOString(),
|
||||
post: threadgate.post as AtUriString,
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
allow: threadgate.allow, // can be undefined!
|
||||
hiddenReplies: threadgate.hiddenReplies || [],
|
||||
hiddenReplies: (threadgate.hiddenReplies || []) as AtUriString[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyUnspeccedGetSuggestedOnboardingUsers,
|
||||
} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {createBskyTopicsHeader} from '#/lib/api/feed/utils'
|
||||
@@ -10,6 +6,7 @@ import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -66,13 +63,12 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
const responses =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedOnboardingUsers.OutputSchema>(
|
||||
{
|
||||
queryKey: [getSuggestedOnboardingUsersQueryKeyRoot],
|
||||
},
|
||||
)
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const responses = queryClient.getQueriesData<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
}>({
|
||||
queryKey: [getSuggestedOnboardingUsersQueryKeyRoot],
|
||||
})
|
||||
for (const [_key, response] of responses) {
|
||||
if (!response) {
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyUnspeccedGetSuggestedUsersForDiscover,
|
||||
} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -13,6 +9,7 @@ import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
limit?: number
|
||||
@@ -58,13 +55,12 @@ export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
const responses =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForDiscover.OutputSchema>(
|
||||
{
|
||||
queryKey: [getSuggestedUsersForDiscoverQueryKeyRoot],
|
||||
},
|
||||
)
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const responses = queryClient.getQueriesData<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
}>({
|
||||
queryKey: [getSuggestedUsersForDiscoverQueryKeyRoot],
|
||||
})
|
||||
for (const [_key, response] of responses) {
|
||||
if (!response) {
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyUnspeccedGetSuggestedUsersForExplore,
|
||||
} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -13,6 +9,7 @@ import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -60,13 +57,12 @@ export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
const responses =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForExplore.OutputSchema>(
|
||||
{
|
||||
queryKey: [getSuggestedUsersForExploreQueryKeyRoot],
|
||||
},
|
||||
)
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const responses = queryClient.getQueriesData<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
}>({
|
||||
queryKey: [getSuggestedUsersForExploreQueryKeyRoot],
|
||||
})
|
||||
for (const [_key, response] of responses) {
|
||||
if (!response) {
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyUnspeccedGetSuggestedUsersForSeeMore,
|
||||
} from '@atproto/api'
|
||||
import {type QueryClient, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -13,6 +9,7 @@ import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type QueryProps = {
|
||||
category?: string | null
|
||||
@@ -66,13 +63,12 @@ export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) {
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
const responses =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForSeeMore.OutputSchema>(
|
||||
{
|
||||
queryKey: [getSuggestedUsersForSeeMoreQueryKeyRoot],
|
||||
},
|
||||
)
|
||||
): Generator<app.bsky.actor.defs.ProfileView, void> {
|
||||
const responses = queryClient.getQueriesData<{
|
||||
actors: app.bsky.actor.defs.ProfileView[]
|
||||
}>({
|
||||
queryKey: [getSuggestedUsersForSeeMoreQueryKeyRoot],
|
||||
})
|
||||
for (const [_key, response] of responses) {
|
||||
if (!response) {
|
||||
continue
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {hasMutedWord} from '@atproto/api'
|
||||
import {hasMutedWord} from '@bsky.app/sdk/moderation'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
||||
|
||||
/**
|
||||
* See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams}
|
||||
* See the `below` param on `app.bsky.unspecced.getPostThreadV2.$Params`
|
||||
*/
|
||||
export const LINEAR_VIEW_BELOW = 10
|
||||
|
||||
/**
|
||||
* See the `branchingFactor` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams}
|
||||
* See the `branchingFactor` param on `app.bsky.unspecced.getPostThreadV2.$Params`
|
||||
*/
|
||||
export const LINEAR_VIEW_BF = 1
|
||||
|
||||
/**
|
||||
* See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams}
|
||||
* See the `below` param on `app.bsky.unspecced.getPostThreadV2.$Params`
|
||||
*/
|
||||
export const TREE_VIEW_BELOW = 4
|
||||
|
||||
/**
|
||||
* See the `branchingFactor` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams}
|
||||
* See the `branchingFactor` param on `app.bsky.unspecced.getPostThreadV2.$Params`
|
||||
*/
|
||||
export const TREE_VIEW_BF = undefined
|
||||
|
||||
/**
|
||||
* See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams}
|
||||
* See the `below` param on `app.bsky.unspecced.getPostThreadV2.$Params`
|
||||
*/
|
||||
export const TREE_VIEW_BELOW_DESKTOP = 6
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
@@ -27,10 +28,11 @@ import {
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
|
||||
import * as views from '#/state/queries/usePostThread/views'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {useBreakpoints} from '#/alf'
|
||||
import {IS_WEB} from '#/env'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
export * from '#/state/queries/usePostThread/context'
|
||||
export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache'
|
||||
@@ -38,7 +40,7 @@ export * from '#/state/queries/usePostThread/types'
|
||||
|
||||
export function usePostThread({anchor}: {anchor?: string}) {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const {hasSession} = useSession()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -71,12 +73,15 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
||||
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
||||
queryKey: postThreadQueryKey,
|
||||
async queryFn(ctx) {
|
||||
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
|
||||
anchor: anchor!,
|
||||
branchingFactor: view === 'linear' ? LINEAR_VIEW_BF : TREE_VIEW_BF,
|
||||
below,
|
||||
sort: sort,
|
||||
})
|
||||
const data = await appviewClient.call(
|
||||
app.bsky.unspecced.getPostThreadV2,
|
||||
{
|
||||
anchor: anchor! as AtUriString,
|
||||
branchingFactor: view === 'linear' ? LINEAR_VIEW_BF : TREE_VIEW_BF,
|
||||
below,
|
||||
sort: sort,
|
||||
},
|
||||
)
|
||||
|
||||
/*
|
||||
* Initialize `ctx.meta` to track if we know we have additional replies
|
||||
@@ -161,10 +166,9 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
||||
enabled: additionalQueryEnabled,
|
||||
queryKey: postThreadOtherQueryKey,
|
||||
async queryFn() {
|
||||
const {data} = await agent.app.bsky.unspecced.getPostThreadOtherV2({
|
||||
anchor: anchor!,
|
||||
return await appviewClient.call(app.bsky.unspecced.getPostThreadOtherV2, {
|
||||
anchor: anchor! as AtUriString,
|
||||
})
|
||||
return data
|
||||
},
|
||||
})
|
||||
const serverOtherThreadItems: ThreadItem[] = useMemo(() => {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import {useCallback} from 'react'
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyUnspeccedDefs,
|
||||
type AppBskyUnspeccedGetPostThreadOtherV2,
|
||||
type AppBskyUnspeccedGetPostThreadV2,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {type QueryClient, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
@@ -36,6 +29,8 @@ import {
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '#/state/queries/util'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function createCacheMutator({
|
||||
queryClient,
|
||||
@@ -51,18 +46,18 @@ export function createCacheMutator({
|
||||
return {
|
||||
insertReplies(
|
||||
parentUri: string,
|
||||
replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[],
|
||||
replies: app.bsky.unspecced.getPostThreadV2.ThreadItem[],
|
||||
) {
|
||||
/*
|
||||
* Main thread query mutator.
|
||||
*/
|
||||
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
||||
queryClient.setQueryData<app.bsky.unspecced.getPostThreadV2.$OutputBody>(
|
||||
postThreadQueryKey,
|
||||
data => {
|
||||
if (!data) return
|
||||
return {
|
||||
...data,
|
||||
thread: mutator<AppBskyUnspeccedGetPostThreadV2.ThreadItem>([
|
||||
thread: mutator<app.bsky.unspecced.getPostThreadV2.ThreadItem>([
|
||||
...data.thread,
|
||||
]),
|
||||
}
|
||||
@@ -72,15 +67,15 @@ export function createCacheMutator({
|
||||
/*
|
||||
* Additional replies query mutator.
|
||||
*/
|
||||
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadOtherV2.OutputSchema>(
|
||||
queryClient.setQueryData<app.bsky.unspecced.getPostThreadOtherV2.$OutputBody>(
|
||||
postThreadOtherQueryKey,
|
||||
data => {
|
||||
if (!data) return
|
||||
return {
|
||||
...data,
|
||||
thread: mutator<AppBskyUnspeccedGetPostThreadOtherV2.ThreadItem>([
|
||||
...data.thread,
|
||||
]),
|
||||
thread: mutator<app.bsky.unspecced.getPostThreadOtherV2.ThreadItem>(
|
||||
[...data.thread],
|
||||
),
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -89,7 +84,10 @@ export function createCacheMutator({
|
||||
for (let i = 0; i < thread.length; i++) {
|
||||
const parent = thread[i]
|
||||
|
||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) continue
|
||||
if (
|
||||
!bsky.isType(app.bsky.unspecced.defs.threadItemPost, parent.value)
|
||||
)
|
||||
continue
|
||||
if (parent.uri !== parentUri) continue
|
||||
|
||||
/*
|
||||
@@ -124,7 +122,8 @@ export function createCacheMutator({
|
||||
const isParentRoot = parent.depth === 0
|
||||
const isParentBelowRoot = parent.depth > 0
|
||||
const optimisticReply = replies.at(0)
|
||||
const opIsReplier = AppBskyUnspeccedDefs.isThreadItemPost(
|
||||
const opIsReplier = bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemPost,
|
||||
optimisticReply?.value,
|
||||
)
|
||||
? opDid === optimisticReply.value.post.author.did
|
||||
@@ -172,8 +171,8 @@ export function createCacheMutator({
|
||||
* Unused atm, post shadow does the trick, but it would be nice to clean up
|
||||
* the whole sub-tree on deletes.
|
||||
*/
|
||||
deletePost(post: AppBskyUnspeccedGetPostThreadV2.ThreadItem) {
|
||||
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
||||
deletePost(post: app.bsky.unspecced.getPostThreadV2.ThreadItem) {
|
||||
queryClient.setQueryData<app.bsky.unspecced.getPostThreadV2.$OutputBody>(
|
||||
postThreadQueryKey,
|
||||
queryData => {
|
||||
if (!queryData) return
|
||||
@@ -182,7 +181,10 @@ export function createCacheMutator({
|
||||
|
||||
for (let i = 0; i < thread.length; i++) {
|
||||
const existingPost = thread[i]
|
||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(post.value)) continue
|
||||
if (
|
||||
!bsky.isType(app.bsky.unspecced.defs.threadItemPost, post.value)
|
||||
)
|
||||
continue
|
||||
|
||||
if (existingPost.uri === post.uri) {
|
||||
const branch = getBranch(thread, i, existingPost.depth)
|
||||
@@ -204,7 +206,7 @@ export function createCacheMutator({
|
||||
export function getThreadPlaceholder(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItem> | void {
|
||||
): $Typed<app.bsky.unspecced.getPostThreadV2.ThreadItem> | void {
|
||||
let partial
|
||||
for (let item of getThreadPlaceholderCandidates(queryClient, uri)) {
|
||||
/*
|
||||
@@ -231,8 +233,8 @@ export function* getThreadPlaceholderCandidates(
|
||||
uri: string,
|
||||
): Generator<
|
||||
$Typed<
|
||||
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
|
||||
value: $Typed<AppBskyUnspeccedDefs.ThreadItemPost>
|
||||
Omit<app.bsky.unspecced.getPostThreadV2.ThreadItem, 'value'> & {
|
||||
value: $Typed<app.bsky.unspecced.defs.ThreadItemPost>
|
||||
}
|
||||
>,
|
||||
void
|
||||
@@ -253,33 +255,37 @@ export function* getThreadPlaceholderCandidates(
|
||||
for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
}
|
||||
/*
|
||||
* TODO(phase4): drop toLex once the feed/quote/search/bookmarks/explore
|
||||
* `findAllPostsInQueryData` generators are migrated to `#/lexicons` types.
|
||||
*/
|
||||
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
yield postViewToThreadPlaceholder(bsky.toLex(post))
|
||||
}
|
||||
for (let post of findAllPostsInQuoteQueryData(queryClient, uri)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
yield postViewToThreadPlaceholder(bsky.toLex(post))
|
||||
}
|
||||
for (let post of findAllPostsInSearchQueryData(queryClient, uri)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
yield postViewToThreadPlaceholder(bsky.toLex(post))
|
||||
}
|
||||
for (let post of findAllPostsInBookmarksQueryData(queryClient, uri)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
yield postViewToThreadPlaceholder(bsky.toLex(post))
|
||||
}
|
||||
for (let post of findAllPostsInExploreFeedPreviewsQueryData(
|
||||
queryClient,
|
||||
uri,
|
||||
)) {
|
||||
yield postViewToThreadPlaceholder(post)
|
||||
yield postViewToThreadPlaceholder(bsky.toLex(post))
|
||||
}
|
||||
}
|
||||
|
||||
export function* findAllPostsInQueryData(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, void> {
|
||||
): Generator<app.bsky.feed.defs.PostView, void> {
|
||||
const atUri = new AtUri(uri)
|
||||
const queryDatas =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>({
|
||||
queryClient.getQueriesData<app.bsky.unspecced.getPostThreadV2.$OutputBody>({
|
||||
queryKey: [postThreadQueryKeyRoot],
|
||||
})
|
||||
|
||||
@@ -289,14 +295,18 @@ export function* findAllPostsInQueryData(
|
||||
const {thread} = queryData
|
||||
|
||||
for (const item of thread) {
|
||||
if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) {
|
||||
if (didOrHandleUriMatches(atUri, item.value.post)) {
|
||||
yield item.value.post
|
||||
}
|
||||
|
||||
const qp = getEmbeddedPost(item.value.post.embed)
|
||||
if (qp && didOrHandleUriMatches(atUri, qp)) {
|
||||
yield embedViewRecordToPostView(qp)
|
||||
/*
|
||||
* TODO(phase4): drop toLex once `embedViewRecordToPostView` in
|
||||
* `#/state/queries/util` is migrated to `#/lexicons` types.
|
||||
*/
|
||||
yield bsky.toLex(embedViewRecordToPostView(qp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,9 +316,9 @@ export function* findAllPostsInQueryData(
|
||||
export function* findAllProfilesInQueryData(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileViewBasic, void> {
|
||||
): Generator<app.bsky.actor.defs.ProfileViewBasic, void> {
|
||||
const queryDatas =
|
||||
queryClient.getQueriesData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>({
|
||||
queryClient.getQueriesData<app.bsky.unspecced.getPostThreadV2.$OutputBody>({
|
||||
queryKey: [postThreadQueryKeyRoot],
|
||||
})
|
||||
|
||||
@@ -318,14 +328,18 @@ export function* findAllProfilesInQueryData(
|
||||
const {thread} = queryData
|
||||
|
||||
for (const item of thread) {
|
||||
if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) {
|
||||
if (item.value.post.author.did === did) {
|
||||
yield item.value.post.author
|
||||
}
|
||||
|
||||
const qp = getEmbeddedPost(item.value.post.embed)
|
||||
if (qp && qp.author.did === did) {
|
||||
yield qp.author
|
||||
/*
|
||||
* TODO(phase4): drop toLex once `getEmbeddedPost` in
|
||||
* `#/state/queries/util` is migrated to `#/lexicons` types.
|
||||
*/
|
||||
yield bsky.toLex(qp.author)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,14 +351,15 @@ export function useUpdatePostThreadThreadgateQueryCache() {
|
||||
const context = usePostThreadContext()
|
||||
|
||||
return useCallback(
|
||||
(threadgate: AppBskyFeedDefs.ThreadgateView) => {
|
||||
(threadgate: app.bsky.feed.defs.ThreadgateView) => {
|
||||
if (!context) return
|
||||
|
||||
function mutator<T>(thread: ApiThreadItem[]): T[] {
|
||||
for (let i = 0; i < thread.length; i++) {
|
||||
const item = thread[i]
|
||||
|
||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) continue
|
||||
if (!bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value))
|
||||
continue
|
||||
|
||||
if (item.depth === 0) {
|
||||
thread.splice(i, 1, {
|
||||
@@ -363,13 +378,13 @@ export function useUpdatePostThreadThreadgateQueryCache() {
|
||||
return thread as T[]
|
||||
}
|
||||
|
||||
qc.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
||||
qc.setQueryData<app.bsky.unspecced.getPostThreadV2.$OutputBody>(
|
||||
context.postThreadQueryKey,
|
||||
data => {
|
||||
if (!data) return
|
||||
return {
|
||||
...data,
|
||||
thread: mutator<AppBskyUnspeccedGetPostThreadV2.ThreadItem>([
|
||||
thread: mutator<app.bsky.unspecced.getPostThreadV2.ThreadItem>([
|
||||
...data.thread,
|
||||
]),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {
|
||||
type ApiThreadItem,
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
storeTraversalMetadata,
|
||||
} from '#/state/queries/usePostThread/utils'
|
||||
import * as views from '#/state/queries/usePostThread/views'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function sortAndAnnotateThreadItems(
|
||||
thread: ApiThreadItem[],
|
||||
@@ -45,7 +47,7 @@ export function sortAndAnnotateThreadItems(
|
||||
let parentMetadata: TraversalMetadata | undefined
|
||||
let metadata: TraversalMetadata | undefined
|
||||
|
||||
if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) {
|
||||
parentMetadata = metadatas.get(
|
||||
getPostRecord(item.value.post).reply?.parent?.uri || '',
|
||||
)
|
||||
@@ -64,13 +66,24 @@ export function sortAndAnnotateThreadItems(
|
||||
* _up_ from there.
|
||||
*/
|
||||
} else if (item.depth === 0) {
|
||||
if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value)) {
|
||||
if (
|
||||
bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemNoUnauthenticated,
|
||||
item.value,
|
||||
)
|
||||
) {
|
||||
threadItems.push(views.threadPostNoUnauthenticated(item))
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(item.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemNotFound, item.value)
|
||||
) {
|
||||
threadItems.push(views.threadPostNotFound(item))
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, item.value)
|
||||
) {
|
||||
threadItems.push(views.threadPostBlocked(item))
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)
|
||||
) {
|
||||
const post = views.threadPost({
|
||||
uri: item.uri,
|
||||
depth: item.depth,
|
||||
@@ -84,7 +97,10 @@ export function sortAndAnnotateThreadItems(
|
||||
const parent = thread[pi]
|
||||
|
||||
if (
|
||||
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(parent.value)
|
||||
bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemNoUnauthenticated,
|
||||
parent.value,
|
||||
)
|
||||
) {
|
||||
const post = views.threadPostNoUnauthenticated(parent)
|
||||
post.ui = getThreadPostNoUnauthenticatedUI({
|
||||
@@ -96,13 +112,22 @@ export function sortAndAnnotateThreadItems(
|
||||
threadItems.unshift(post)
|
||||
// for now, break parent traversal at first no-unauthed
|
||||
break parentTraversal
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(parent.value)) {
|
||||
} else if (
|
||||
bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemNotFound,
|
||||
parent.value,
|
||||
)
|
||||
) {
|
||||
threadItems.unshift(views.threadPostNotFound(parent))
|
||||
break parentTraversal
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(parent.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, parent.value)
|
||||
) {
|
||||
threadItems.unshift(views.threadPostBlocked(parent))
|
||||
break parentTraversal
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemPost, parent.value)
|
||||
) {
|
||||
threadItems.unshift(
|
||||
views.threadPost({
|
||||
uri: parent.uri,
|
||||
@@ -122,16 +147,21 @@ export function sortAndAnnotateThreadItems(
|
||||
* we could.
|
||||
*/
|
||||
const shouldBreak =
|
||||
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value) ||
|
||||
AppBskyUnspeccedDefs.isThreadItemNotFound(item.value) ||
|
||||
AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)
|
||||
bsky.isType(
|
||||
app.bsky.unspecced.defs.threadItemNoUnauthenticated,
|
||||
item.value,
|
||||
) ||
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemNotFound, item.value) ||
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, item.value)
|
||||
|
||||
if (shouldBreak) {
|
||||
const branch = getBranch(thread, i, item.depth)
|
||||
// could insert tombstone
|
||||
i = branch.end
|
||||
continue traversal
|
||||
} else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
} else if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)
|
||||
) {
|
||||
if (parentMetadata) {
|
||||
/*
|
||||
* Set this value before incrementing the `repliesSeenCounter` later
|
||||
@@ -179,7 +209,9 @@ export function sortAndAnnotateThreadItems(
|
||||
for (let ci = startIndex; ci <= branch.end; ci++) {
|
||||
const child = thread[ci]
|
||||
|
||||
if (AppBskyUnspeccedDefs.isThreadItemPost(child.value)) {
|
||||
if (
|
||||
bsky.isType(app.bsky.unspecced.defs.threadItemPost, child.value)
|
||||
) {
|
||||
const childParentMetadata = metadatas.get(
|
||||
getPostRecord(child.value.post).reply?.parent?.uri || '',
|
||||
)
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPost,
|
||||
type AppBskyFeedThreadgate,
|
||||
type AppBskyUnspeccedDefs,
|
||||
type AppBskyUnspeccedGetPostThreadOtherV2,
|
||||
type AppBskyUnspeccedGetPostThreadV2,
|
||||
type ModerationDecision,
|
||||
} from '@atproto/api'
|
||||
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export type ApiThreadItem =
|
||||
| AppBskyUnspeccedGetPostThreadV2.ThreadItem
|
||||
| AppBskyUnspeccedGetPostThreadOtherV2.ThreadItem
|
||||
| app.bsky.unspecced.getPostThreadV2.ThreadItem
|
||||
| app.bsky.unspecced.getPostThreadOtherV2.ThreadItem
|
||||
|
||||
export const postThreadQueryKeyRoot = 'post-thread-v2' as const
|
||||
|
||||
@@ -18,13 +12,13 @@ export const createPostThreadQueryKey = (props: PostThreadParams) =>
|
||||
[postThreadQueryKeyRoot, props] as const
|
||||
|
||||
export const createPostThreadOtherQueryKey = (
|
||||
props: Omit<AppBskyUnspeccedGetPostThreadOtherV2.QueryParams, 'anchor'> & {
|
||||
props: Omit<app.bsky.unspecced.getPostThreadOtherV2.$Params, 'anchor'> & {
|
||||
anchor?: string
|
||||
},
|
||||
) => [postThreadQueryKeyRoot, 'other', props] as const
|
||||
|
||||
export type PostThreadParams = Pick<
|
||||
AppBskyUnspeccedGetPostThreadV2.QueryParams,
|
||||
app.bsky.unspecced.getPostThreadV2.$Params,
|
||||
'sort'
|
||||
> & {
|
||||
anchor?: string
|
||||
@@ -33,9 +27,9 @@ export type PostThreadParams = Pick<
|
||||
|
||||
export type UsePostThreadQueryResult = {
|
||||
hasOtherReplies: boolean
|
||||
thread: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
|
||||
threadgate?: Omit<AppBskyFeedDefs.ThreadgateView, 'record'> & {
|
||||
record: AppBskyFeedThreadgate.Record
|
||||
thread: app.bsky.unspecced.getPostThreadV2.ThreadItem[]
|
||||
threadgate?: Omit<app.bsky.feed.defs.ThreadgateView, 'record'> & {
|
||||
record: app.bsky.feed.threadgate.Main
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +39,9 @@ export type ThreadItem =
|
||||
key: string
|
||||
uri: string
|
||||
depth: number
|
||||
value: Omit<AppBskyUnspeccedDefs.ThreadItemPost, 'post'> & {
|
||||
post: Omit<AppBskyFeedDefs.PostView, 'record'> & {
|
||||
record: AppBskyFeedPost.Record
|
||||
value: Omit<app.bsky.unspecced.defs.ThreadItemPost, 'post'> & {
|
||||
post: Omit<app.bsky.feed.defs.PostView, 'record'> & {
|
||||
record: app.bsky.feed.post.Main
|
||||
}
|
||||
}
|
||||
isBlurred: boolean
|
||||
@@ -67,7 +61,7 @@ export type ThreadItem =
|
||||
key: string
|
||||
uri: string
|
||||
depth: number
|
||||
value: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated
|
||||
value: app.bsky.unspecced.defs.ThreadItemNoUnauthenticated
|
||||
ui: {
|
||||
showParentReplyLine: boolean
|
||||
showChildReplyLine: boolean
|
||||
@@ -78,14 +72,14 @@ export type ThreadItem =
|
||||
key: string
|
||||
uri: string
|
||||
depth: number
|
||||
value: AppBskyUnspeccedDefs.ThreadItemNotFound
|
||||
value: app.bsky.unspecced.defs.ThreadItemNotFound
|
||||
}
|
||||
| {
|
||||
type: 'threadPostBlocked'
|
||||
key: string
|
||||
uri: string
|
||||
depth: number
|
||||
value: AppBskyUnspeccedDefs.ThreadItemBlocked
|
||||
value: app.bsky.unspecced.defs.ThreadItemBlocked
|
||||
}
|
||||
| {
|
||||
type: 'replyComposer'
|
||||
|
||||
@@ -1,38 +1,24 @@
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedThreadgate,
|
||||
AppBskyUnspeccedDefs,
|
||||
type AppBskyUnspeccedGetPostThreadV2,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
|
||||
import {
|
||||
type ApiThreadItem,
|
||||
type ThreadItem,
|
||||
type TraversalMetadata,
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
import {app} from '#/lexicons'
|
||||
import {isDevMode} from '#/storage/hooks/dev-mode'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
export function getThreadgateRecord(
|
||||
view: AppBskyUnspeccedGetPostThreadV2.OutputSchema['threadgate'],
|
||||
view: app.bsky.unspecced.getPostThreadV2.$OutputBody['threadgate'],
|
||||
) {
|
||||
return bsky.dangerousIsType<AppBskyFeedThreadgate.Record>(
|
||||
view?.record,
|
||||
AppBskyFeedThreadgate.isRecord,
|
||||
)
|
||||
return bsky.isType(app.bsky.feed.threadgate, view?.record)
|
||||
? view?.record
|
||||
: undefined
|
||||
}
|
||||
|
||||
export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) {
|
||||
if (
|
||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||
post.record,
|
||||
AppBskyFeedPost.isRecord,
|
||||
)
|
||||
) {
|
||||
export function getRootPostAtUri(post: app.bsky.feed.defs.PostView) {
|
||||
if (bsky.isType(app.bsky.feed.post, post.record)) {
|
||||
/**
|
||||
* If the record has no `reply` field, it is a root post.
|
||||
*/
|
||||
@@ -45,8 +31,8 @@ export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getPostRecord(post: AppBskyFeedDefs.PostView) {
|
||||
return post.record as AppBskyFeedPost.Record
|
||||
export function getPostRecord(post: app.bsky.feed.defs.PostView) {
|
||||
return post.record as app.bsky.feed.post.Main
|
||||
}
|
||||
|
||||
export function getTraversalMetadata({
|
||||
@@ -60,7 +46,7 @@ export function getTraversalMetadata({
|
||||
nextItem?: ApiThreadItem
|
||||
parentMetadata?: TraversalMetadata
|
||||
}): TraversalMetadata {
|
||||
if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
|
||||
if (!bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) {
|
||||
throw new Error(`Expected thread item to be a post`)
|
||||
}
|
||||
const repliesCount = item.value.post.replyCount || 0
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
type $Typed,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPost,
|
||||
type AppBskyUnspeccedDefs,
|
||||
type AppBskyUnspeccedGetPostThreadV2,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
type ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {
|
||||
@@ -15,6 +8,7 @@ import {
|
||||
type ThreadItem,
|
||||
type TraversalMetadata,
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
export function threadPostNoUnauthenticated({
|
||||
uri,
|
||||
@@ -26,7 +20,7 @@ export function threadPostNoUnauthenticated({
|
||||
key: uri,
|
||||
uri,
|
||||
depth,
|
||||
value: value as AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated,
|
||||
value: value as app.bsky.unspecced.defs.ThreadItemNoUnauthenticated,
|
||||
// @ts-ignore populated by the traversal
|
||||
ui: {},
|
||||
}
|
||||
@@ -42,7 +36,7 @@ export function threadPostNotFound({
|
||||
key: uri,
|
||||
uri,
|
||||
depth,
|
||||
value: value as AppBskyUnspeccedDefs.ThreadItemNotFound,
|
||||
value: value as app.bsky.unspecced.defs.ThreadItemNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +50,7 @@ export function threadPostBlocked({
|
||||
key: uri,
|
||||
uri,
|
||||
depth,
|
||||
value: value as AppBskyUnspeccedDefs.ThreadItemBlocked,
|
||||
value: value as app.bsky.unspecced.defs.ThreadItemBlocked,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +63,7 @@ export function threadPost({
|
||||
}: {
|
||||
uri: string
|
||||
depth: number
|
||||
value: $Typed<AppBskyUnspeccedDefs.ThreadItemPost>
|
||||
value: $Typed<app.bsky.unspecced.defs.ThreadItemPost>
|
||||
moderationOpts: ModerationOpts
|
||||
threadgateHiddenReplies: Set<string>
|
||||
}): Extract<ThreadItem, {type: 'threadPost'}> {
|
||||
@@ -91,8 +85,8 @@ export function threadPost({
|
||||
* Do not spread anything here, load bearing for post shadow strict
|
||||
* equality reference checks.
|
||||
*/
|
||||
post: value.post as Omit<AppBskyFeedDefs.PostView, 'record'> & {
|
||||
record: AppBskyFeedPost.Record
|
||||
post: value.post as Omit<app.bsky.feed.defs.PostView, 'record'> & {
|
||||
record: app.bsky.feed.post.Main
|
||||
},
|
||||
},
|
||||
isBlurred,
|
||||
@@ -161,10 +155,10 @@ export function skeleton({
|
||||
}
|
||||
|
||||
export function postViewToThreadPlaceholder(
|
||||
post: AppBskyFeedDefs.PostView,
|
||||
post: app.bsky.feed.defs.PostView,
|
||||
): $Typed<
|
||||
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
|
||||
value: $Typed<AppBskyUnspeccedDefs.ThreadItemPost>
|
||||
Omit<app.bsky.unspecced.getPostThreadV2.ThreadItem, 'value'> & {
|
||||
value: $Typed<app.bsky.unspecced.defs.ThreadItemPost>
|
||||
}
|
||||
> {
|
||||
return {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user