collapse the dual-world type layer and delete the widening shims
The types/bsky post/profile/starterPack unions drop their @atproto/api arms, and dangerousIsType/validate go with the old-world guards they wrapped. The moderation subjects.ts widening shim and rich-text-helpers' asSdkFacets both existed only to bridge branded and unbranded views, so their 55 and 14 callers now go straight to @bsky.app/sdk/moderation and the raw facets. Boundary fallout: lexicon token defs are camelCase schema objects needing .value, and the branded string slots that the widening used to absorb are now cast or branded at their producers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+22
-5
@@ -1,3 +1,4 @@
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
@@ -15,9 +16,13 @@ import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/stat
|
||||
import {castAsShadow, type Shadow} from './types'
|
||||
export type {Shadow} from './types'
|
||||
|
||||
/**
|
||||
* `'pending'` is the optimistic sentinel written before the real record uri
|
||||
* comes back, so the two uri slots admit it alongside a real at-uri.
|
||||
*/
|
||||
export interface PostShadow {
|
||||
likeUri: string | undefined
|
||||
repostUri: string | undefined
|
||||
likeUri: AtUriString | 'pending' | undefined
|
||||
repostUri: AtUriString | 'pending' | undefined
|
||||
isDeleted: boolean
|
||||
embed:
|
||||
| app.bsky.embed.record.View
|
||||
@@ -130,7 +135,12 @@ function mergeShadow(
|
||||
(bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed) &&
|
||||
bsky.isType(app.bsky.embed.recordWithMedia.view, shadow.embed))
|
||||
) {
|
||||
embed = shadow.embed
|
||||
/*
|
||||
* `isType` asserts a present, matching `$type` at runtime but narrows to
|
||||
* the schema's input type, whose `$type` is optional - so the value does
|
||||
* not satisfy the `$Typed` arm of `PostView['embed']` without this cast.
|
||||
*/
|
||||
embed = shadow.embed as typeof post.embed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +153,15 @@ function mergeShadow(
|
||||
bookmarkCount: bookmarkCount,
|
||||
viewer: {
|
||||
...(post.viewer || {}),
|
||||
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
||||
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
||||
/*
|
||||
* The optimistic `'pending'` sentinel is not a real at-uri; consumers only
|
||||
* test these for presence while the write is in flight.
|
||||
*/
|
||||
like: (('likeUri' in shadow ? shadow.likeUri : post.viewer?.like) ??
|
||||
undefined) as AtUriString | undefined,
|
||||
repost: (('repostUri' in shadow
|
||||
? shadow.repostUri
|
||||
: post.viewer?.repost) ?? undefined) as AtUriString | undefined,
|
||||
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
|
||||
bookmarked:
|
||||
'bookmarked' in shadow ? shadow.bookmarked : post.viewer?.bookmarked,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {type DidString, toDatetimeString} from '@atproto/syntax'
|
||||
import {type Client, XrpcResponseError, type $Typed} from '@atproto/lex'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
@@ -97,7 +98,7 @@ export class Convo {
|
||||
|
||||
private chatClient: Client
|
||||
private events: MessagesEventBus
|
||||
private senderUserDid: string
|
||||
private senderUserDid: DidString
|
||||
|
||||
private status: ConvoStatus = ConvoStatus.Uninitialized
|
||||
private error: ConvoError | undefined
|
||||
@@ -1420,7 +1421,7 @@ export class Convo {
|
||||
$type: 'chat.bsky.convo.defs#messageView',
|
||||
id: nanoid(),
|
||||
rev: '__fake__',
|
||||
sentAt: new Date().toISOString(),
|
||||
sentAt: toDatetimeString(new Date()),
|
||||
sender: {
|
||||
$type: 'chat.bsky.convo.defs#messageViewSender',
|
||||
did: this.senderUserDid,
|
||||
@@ -1462,10 +1463,10 @@ 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(),
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
}
|
||||
let restore: null | (() => void) = null
|
||||
if (this.pastMessages.has(messageId)) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useMemo, useRef} from 'react'
|
||||
import {type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {app} from '#/lexicons'
|
||||
import {CustomFeedAPI} from '#/lib/api/feed/custom'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner} from '#/lib/api/feed-manip'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -13,10 +14,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
|
||||
import {moderateFeedGenerator} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {GCTIME, STALE} from '#/state/queries'
|
||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
@@ -99,7 +98,7 @@ export function hydrateFeedGenerator(
|
||||
|
||||
const description = new RichText({
|
||||
text: view.description || '',
|
||||
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||
facets: (view.descriptionFacets || [])?.slice(),
|
||||
})
|
||||
|
||||
if (!view.descriptionFacets) {
|
||||
@@ -142,7 +141,7 @@ export function hydrateList(
|
||||
|
||||
const description = new RichText({
|
||||
text: view.description || '',
|
||||
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||
facets: (view.descriptionFacets || [])?.slice(),
|
||||
})
|
||||
|
||||
if (!view.descriptionFacets) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {useCallback} from 'react'
|
||||
import {type Client, type $Typed} from '@atproto/lex'
|
||||
import {type $Typed, type Client, type Unknown$TypedObject} from '@atproto/lex'
|
||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
@@ -22,10 +23,10 @@ export type KnownChatInvitePreview =
|
||||
| $Typed<chat.bsky.group.defs.InvalidJoinLinkPreviewView>
|
||||
|
||||
/**
|
||||
* The full open-union shape, including the `{$type: string}` fallback for
|
||||
* future variants.
|
||||
* The full open-union shape, including lex's `Unknown$TypedObject` fallback for
|
||||
* unrecognized future variants.
|
||||
*/
|
||||
export type ChatInvitePreview = KnownChatInvitePreview | {$type: string}
|
||||
export type ChatInvitePreview = KnownChatInvitePreview | Unknown$TypedObject
|
||||
|
||||
/**
|
||||
* Narrows a preview to one of the three known variants, filtering out the
|
||||
@@ -112,7 +113,9 @@ export function setJoinLinkPreviewRequestedForCode(
|
||||
...preview,
|
||||
viewer: {
|
||||
...preview.viewer,
|
||||
requestedAt: requested ? new Date().toISOString() : undefined,
|
||||
requestedAt: requested
|
||||
? toDatetimeString(new Date())
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ export function useListMembershipAddMutation({
|
||||
* Needed for optimistic update of starter pack query
|
||||
*/
|
||||
subject?: bsky.profile.AnyProfileView
|
||||
onSuccess?: (data: {uri: string; cid: string}) => void
|
||||
onSuccess?: (data: {uri: AtUriString; cid: string}) => void
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const {currentAccount} = useSession()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<
|
||||
{uri: string; cid: string},
|
||||
{uri: AtUriString; cid: string},
|
||||
Error,
|
||||
{listUri: string; actorDid: string}
|
||||
>({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
@@ -52,7 +53,7 @@ export function useCreateJoinLink(
|
||||
enabledStatus: 'enabled',
|
||||
joinRule,
|
||||
requireApproval,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -742,11 +742,18 @@ export function ListConvosProviderInner({
|
||||
return {
|
||||
...convo,
|
||||
members: [...convo.members, ...relatedProfilesSansMembers],
|
||||
/*
|
||||
* `logAddReaction.message` is the wider message union (it can
|
||||
* carry a deleted message too), while the reaction view's
|
||||
* `message` slot is only the live `messageView` - the server
|
||||
* never pairs a reaction with a deleted message, so the arm is
|
||||
* asserted rather than re-narrowed.
|
||||
*/
|
||||
lastReaction: {
|
||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||
reaction: log.reaction,
|
||||
message: log.message,
|
||||
},
|
||||
} as chat.bsky.convo.defs.ConvoView['lastReaction'],
|
||||
rev: log.rev,
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {describe, expect, it, jest} from '@jest/globals'
|
||||
|
||||
import {groupNotifications} from '../util'
|
||||
import {app} from '#/lexicons'
|
||||
import {type Notification} from '../types'
|
||||
import {groupNotifications} from '../util'
|
||||
|
||||
jest.mock('#/state/queries/profile', () => ({precacheProfile: jest.fn()}))
|
||||
|
||||
type Notification = app.bsky.notification.listNotifications.Notification
|
||||
|
||||
/*
|
||||
* Fixture builder. The generated view brands `did`/`uri`/`cid`/`indexedAt`, and
|
||||
* these are plain test strings, so the whole literal is asserted once - the
|
||||
* grouping logic under test only compares them as strings.
|
||||
*/
|
||||
function makeFollowNotification(
|
||||
did: string,
|
||||
did: DidString,
|
||||
starterPackUri?: string,
|
||||
): Notification {
|
||||
return {
|
||||
@@ -31,7 +36,7 @@ function makeFollowNotification(
|
||||
: undefined,
|
||||
isRead: false,
|
||||
indexedAt: '2026-07-28T12:00:00.000Z',
|
||||
}
|
||||
} as Notification
|
||||
}
|
||||
|
||||
describe('groupNotifications', () => {
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
/**
|
||||
* A `listNotifications` notification, widened with the `starterPack` the server
|
||||
* attaches to a `follow` notification sourced from a starter pack. The pinned
|
||||
* lexicon does not declare the field yet, so it is added here rather than read
|
||||
* off an `unknown`. Drop this alias once the generated type carries it.
|
||||
*/
|
||||
export type Notification =
|
||||
app.bsky.notification.listNotifications.Notification & {
|
||||
starterPack?: app.bsky.graph.defs.StarterPackViewBasic
|
||||
}
|
||||
|
||||
export type NotificationType =
|
||||
| StarterPackNotificationType
|
||||
| OtherNotificationType
|
||||
@@ -50,8 +61,8 @@ type OtherNotificationType =
|
||||
|
||||
type FeedNotificationBase = {
|
||||
_reactKey: string
|
||||
notification: app.bsky.notification.listNotifications.Notification
|
||||
additional?: app.bsky.notification.listNotifications.Notification[]
|
||||
notification: Notification
|
||||
additional?: Notification[]
|
||||
subjectUri?: string
|
||||
subject?:
|
||||
| app.bsky.feed.defs.PostView
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
hasMutedWord,
|
||||
moderateNotification,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
import chunk from 'lodash.chunk'
|
||||
|
||||
import {labelIsHideableOffense} from '#/lib/moderation'
|
||||
import {hasMutedWord, moderateNotification} from '#/lib/moderation/subjects'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {precacheProfile} from '../profile'
|
||||
import {
|
||||
type FeedNotification,
|
||||
type FeedPage,
|
||||
type Notification,
|
||||
type NotificationType,
|
||||
} from './types'
|
||||
|
||||
@@ -108,7 +112,7 @@ export async function fetchPage({
|
||||
// =
|
||||
|
||||
export function shouldFilterNotif(
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
notif: Notification,
|
||||
moderationOpts: ModerationOpts | undefined,
|
||||
): boolean {
|
||||
const containsImperative = !!notif.author.labels?.some(labelIsHideableOffense)
|
||||
@@ -138,9 +142,7 @@ export function shouldFilterNotif(
|
||||
return moderateNotification(notif, moderationOpts).ui('contentList').filter
|
||||
}
|
||||
|
||||
export function groupNotifications(
|
||||
notifs: app.bsky.notification.listNotifications.Notification[],
|
||||
): FeedNotification[] {
|
||||
export function groupNotifications(notifs: Notification[]): FeedNotification[] {
|
||||
const groupedNotifs: FeedNotification[] = []
|
||||
for (const notif of notifs) {
|
||||
const ts = +new Date(notif.indexedAt)
|
||||
@@ -249,9 +251,7 @@ async function fetchSubjects(
|
||||
}
|
||||
}
|
||||
|
||||
function toKnownType(
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
): NotificationType {
|
||||
function toKnownType(notif: Notification): NotificationType {
|
||||
if (notif.reason === 'like') {
|
||||
if (notif.reasonSubject?.includes('feed.generator')) {
|
||||
return 'feedgen-like'
|
||||
@@ -279,7 +279,7 @@ function toKnownType(
|
||||
|
||||
function getSubjectUri(
|
||||
type: NotificationType,
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
notif: Notification,
|
||||
): string | undefined {
|
||||
if (
|
||||
type === 'reply' ||
|
||||
|
||||
@@ -55,10 +55,10 @@ export function usePinnedPostMutation() {
|
||||
: undefined
|
||||
return existing
|
||||
},
|
||||
checkCommitted: res =>
|
||||
checkCommitted: profile =>
|
||||
pinCurrentPost
|
||||
? res.data.pinnedPost?.uri === postUri
|
||||
: !res.data.pinnedPost,
|
||||
? profile.pinnedPost?.uri === postUri
|
||||
: !profile.pinnedPost,
|
||||
})
|
||||
|
||||
if (pinCurrentPost) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {AppState} from 'react-native'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type AtIdentifierString, type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {
|
||||
moderatePost,
|
||||
type ModerationDecision,
|
||||
type ModerationPrefs,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
@@ -28,7 +29,6 @@ import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
|
||||
import {DISCOVER_FEED_URI} from '#/lib/constants'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
@@ -78,7 +78,7 @@ export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) {
|
||||
|
||||
export interface FeedPostSliceItem {
|
||||
_reactKey: string
|
||||
uri: string
|
||||
uri: AtUriString
|
||||
post: app.bsky.feed.defs.PostView
|
||||
record: app.bsky.feed.post.Main
|
||||
moderation: ModerationDecision
|
||||
|
||||
@@ -190,7 +190,7 @@ function usePostLikeMutation(
|
||||
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
|
||||
>({
|
||||
@@ -319,7 +319,7 @@ function usePostRepostMutation(
|
||||
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
|
||||
>({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useRef} from 'react'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {AtUri, type HandleString} from '@atproto/syntax'
|
||||
import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry, retry} from '#/lib/async/retry'
|
||||
@@ -177,7 +177,7 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
action,
|
||||
}: {
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quoteUri: string
|
||||
quoteUri: AtUriString
|
||||
action: 'detach' | 'reattach'
|
||||
}) => {
|
||||
// cache here since post shadow mutates original object
|
||||
|
||||
@@ -58,7 +58,7 @@ export function mergePostgateRecords(
|
||||
export function createEmbedViewDetachedRecord({
|
||||
uri,
|
||||
}: {
|
||||
uri: string
|
||||
uri: AtUriString
|
||||
}): $Typed<app.bsky.embed.record.View> {
|
||||
const record: $Typed<app.bsky.embed.record.ViewDetached> = {
|
||||
$type: 'app.bsky.embed.record#viewDetached',
|
||||
@@ -86,7 +86,7 @@ export function createMaybeDetachedQuoteEmbed({
|
||||
| {
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quote: undefined
|
||||
quoteUri: string
|
||||
quoteUri: AtUriString
|
||||
detached: true
|
||||
}):
|
||||
| app.bsky.embed.record.View
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {moderateFeedGenerator} from '#/lib/moderation/subjects'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {moderateUserList} from '#/lib/moderation/subjects'
|
||||
import {moderateUserList} from '@bsky.app/sdk/moderation'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
@@ -146,7 +146,7 @@ interface ProfileUpdateParams {
|
||||
) => Un$Typed<app.bsky.actor.profile.Main>)
|
||||
newUserAvatar?: ImageMeta | undefined | null
|
||||
newUserBanner?: ImageMeta | undefined | null
|
||||
checkCommitted?: (res: app.bsky.actor.getProfile.Response) => boolean
|
||||
checkCommitted?: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean
|
||||
}
|
||||
export function useProfileUpdateMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
@@ -206,21 +206,21 @@ export function useProfileUpdateMutation() {
|
||||
appviewClient,
|
||||
profile.did,
|
||||
checkCommitted ||
|
||||
(res => {
|
||||
(profile => {
|
||||
if (typeof newUserAvatar !== 'undefined') {
|
||||
if (newUserAvatar === null && res.data.avatar) {
|
||||
if (newUserAvatar === null && profile.avatar) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.avatar === profile.avatar) {
|
||||
} else if (profile.avatar === profile.avatar) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (typeof newUserBanner !== 'undefined') {
|
||||
if (newUserBanner === null && res.data.banner) {
|
||||
if (newUserBanner === null && profile.banner) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.banner === profile.banner) {
|
||||
} else if (profile.banner === profile.banner) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
@@ -229,8 +229,8 @@ export function useProfileUpdateMutation() {
|
||||
return true
|
||||
}
|
||||
return (
|
||||
res.data.displayName === updates.displayName &&
|
||||
res.data.description === updates.description
|
||||
profile.displayName === updates.displayName &&
|
||||
profile.description === updates.description
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -381,7 +381,7 @@ function useProfileFollowMutation(
|
||||
const queryClient = useQueryClient()
|
||||
const {captureAction} = useProgressGuideControls()
|
||||
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined
|
||||
if (currentAccount) {
|
||||
@@ -637,7 +637,7 @@ function useProfileBlockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
@@ -679,23 +679,16 @@ function useProfileUnblockMutation() {
|
||||
async function whenAppViewReady(
|
||||
client: Client,
|
||||
actor: string,
|
||||
fn: (res: app.bsky.actor.getProfile.Response) => boolean,
|
||||
fn: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
/*
|
||||
* `checkCommitted` callbacks are still written against the legacy
|
||||
* `Response` envelope, so wrap the lex output until those consumers move.
|
||||
*/
|
||||
async () => ({
|
||||
success: true,
|
||||
headers: {},
|
||||
data: await client.call(app.bsky.actor.getProfile, {
|
||||
async () =>
|
||||
await client.call(app.bsky.actor.getProfile, {
|
||||
actor: actor as AtIdentifierString,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -200,12 +200,31 @@ function mergeList(a?: string[], b?: string[]): string[] | undefined {
|
||||
* operator. v2 renames v1's singular operators to plural arrays, and `lang` to
|
||||
* `language`.
|
||||
*/
|
||||
/**
|
||||
* The filter subset of the `searchPostsV2` params, with the format-constrained
|
||||
* strings (uri, at-identifier, language, datetime) left unbranded: every value
|
||||
* here is parsed from user input, so the brand is asserted by the caller rather
|
||||
* than re-validated here. `q`, `limit`, `cursor` and `sort` are the caller's.
|
||||
*/
|
||||
type SearchPostsV2FilterParams = {
|
||||
[K in Exclude<
|
||||
keyof app.bsky.feed.searchPostsV2.$Params,
|
||||
'query' | 'limit' | 'cursor' | 'sort'
|
||||
>]?: app.bsky.feed.searchPostsV2.$Params[K] extends
|
||||
| readonly (infer _E)[]
|
||||
| undefined
|
||||
? string[]
|
||||
: app.bsky.feed.searchPostsV2.$Params[K] extends string | undefined
|
||||
? string
|
||||
: app.bsky.feed.searchPostsV2.$Params[K]
|
||||
}
|
||||
|
||||
export function buildSearchPostsV2Filters(
|
||||
embedded: Omit<ExtractedSearchParams, 'q'>,
|
||||
filters?: SearchFilters,
|
||||
): app.bsky.feed.searchPostsV2.$Params {
|
||||
): SearchPostsV2FilterParams {
|
||||
const apiFilters = filters ? filtersToApiParams(filters) : {}
|
||||
const params: app.bsky.feed.searchPostsV2.$Params = {}
|
||||
const params: SearchPostsV2FilterParams = {}
|
||||
|
||||
const authors = mergeList(
|
||||
embedded.author ? [embedded.author] : undefined,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type SearchFilters} from '#/screens/Search/searchParams'
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderatePost, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {
|
||||
type ApiThreadItem,
|
||||
|
||||
Reference in New Issue
Block a user