Update types

This commit is contained in:
Eric Bailey
2025-05-31 13:59:52 -05:00
parent 8289d625c7
commit 45538dc0d8
9 changed files with 100 additions and 96 deletions
+15 -1
View File
@@ -114,6 +114,11 @@ export function Inner({uri}: {uri: string | undefined}) {
const anchorOffsetTop = anchorElement.getBoundingClientRect().top
const headerHeight = headerElement.getBoundingClientRect().height
const scrollPosition = anchorOffsetTop - headerHeight
console.log({
anchorOffsetTop,
headerHeight,
scrollPosition,
})
/*
* If scroll position is negative, it means the anchor post is above the
* top of the screen, meaning the user scrolled the list. In that case,
@@ -292,7 +297,16 @@ export function Inner({uri}: {uri: string | undefined}) {
return (
<PostThreadShowHiddenReplies
type="hidden"
onPress={() => item.onLoad()}
onPress={() => {
item.onLoad()
/*
* Bit of a hack. This resets the ref value for the anchor so that
* the next time `onContentSizeChangeWebOnly` fires, it won't
* adjust scroll. However, on the next render cycle, it will, which
* will give us time to insert the skeleton state and handle scroll.
*/
anchorRef.current = null
}}
/>
)
} else if (item.type === 'skeleton') {
+42 -21
View File
@@ -1,6 +1,7 @@
import {useCallback, useMemo, useRef, useState} from 'react'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {wait} from '#/lib/async/wait'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {
createCacheMutator,
@@ -8,8 +9,8 @@ import {
} from '#/state/queries/usePostThread/queryCache'
import {traverse} from '#/state/queries/usePostThread/traversal'
import {
createPostThreadQueryKey,
createPostThreadHiddenQueryKey,
createPostThreadQueryKey,
type ThreadItem,
type UsePostThreadProps,
} from '#/state/queries/usePostThread/types'
@@ -39,13 +40,16 @@ export function usePostThread({
queryKey,
gcTime: 0,
async queryFn() {
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
anchor: params.anchor!,
branchingFactor: params.view === 'linear' ? 1 : undefined,
below: 4,
sort: params.sort,
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
})
const {data} = await wait(
400,
agent.app.bsky.unspecced.getPostThreadV2({
anchor: params.anchor!,
branchingFactor: params.view === 'linear' ? 1 : undefined,
below: 4,
sort: params.sort,
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
}),
)
return data
},
placeholderData() {
@@ -84,22 +88,29 @@ export function usePostThread({
const [hiddenReplies, setHiddenReplies] = useState<ThreadItem[]>([])
const loadHiddenReplies = useCallback(async () => {
setShowHiddenReplies(true)
setHiddenReplies(Array.from({length: 2}).map((_, i) => ({
type: 'skeleton',
key: `${params.anchor!}-reply-${i}`,
item: 'reply',
})))
setHiddenReplies(
Array.from({length: 2}).map((_, i) => ({
type: 'skeleton',
key: `${params.anchor!}-reply-${i}`,
item: 'reply',
})),
)
const queryParams = {
anchor: params.anchor!,
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
}
const data = await qc.fetchQuery({
queryKey: createPostThreadHiddenQueryKey(queryParams),
async queryFn() {
const {data} = await agent.app.bsky.unspecced.getPostThreadHiddenV2(queryParams)
return data.thread || []
},
})
const data = await wait(
400,
qc.fetchQuery({
queryKey: createPostThreadHiddenQueryKey(queryParams),
async queryFn() {
const {data} = await agent.app.bsky.unspecced.getPostThreadHiddenV2(
queryParams,
)
return data.thread || []
},
}),
)
const items = traverse(data || [], {
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
query.data?.threadgate?.record,
@@ -113,7 +124,17 @@ export function usePostThread({
loadHiddenReplies,
})
setHiddenReplies(items)
}, [params, setShowHiddenReplies])
}, [
agent,
params,
hasSession,
mergeThreadgateHiddenReplies,
moderationOpts,
qc,
query.data?.threadgate?.record,
showHiddenReplies,
setShowHiddenReplies,
])
const items = useMemo(() => {
const results = traverse(query.data?.thread || [], {
+12 -18
View File
@@ -1,8 +1,8 @@
import {
type $Typed,
type AppBskyFeedDefs,
AppBskyUnspeccedGetPostThreadV2,
AppBskyUnspeccedDefs,
type AppBskyUnspeccedGetPostThreadV2,
AtUri,
} from '@atproto/api'
import {type QueryClient} from '@tanstack/react-query'
@@ -35,7 +35,7 @@ export function createCacheMutator({
return {
insertReplies(
parentUri: string,
replies: AppBskyUnspeccedDefs.ThreadItem[],
replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[],
) {
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
queryKey,
@@ -46,11 +46,7 @@ export function createCacheMutator({
for (let i = 0; i < thread.length; i++) {
const existingParent = thread[i]
if (
!AppBskyUnspeccedDefs.isThreadItemPost(
existingParent.value,
)
)
if (!AppBskyUnspeccedDefs.isThreadItemPost(existingParent.value))
continue
if (existingParent.uri !== parentUri) continue
@@ -68,12 +64,11 @@ export function createCacheMutator({
const isEndOfReplyChain =
!nextItem || nextItem.depth <= existingParent.depth
const firstReply = replies.at(0)
const opIsReplier =
AppBskyUnspeccedDefs.isThreadItemPost(
firstReply?.value,
)
? opDid === firstReply.value.post.author.did
: false
const opIsReplier = AppBskyUnspeccedDefs.isThreadItemPost(
firstReply?.value,
)
? opDid === firstReply.value.post.author.did
: false
/*
* Always insert replies if the following conditions are met.
@@ -119,7 +114,7 @@ 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: AppBskyUnspeccedDefs.ThreadItem) {
deletePost(post: AppBskyUnspeccedGetPostThreadV2.ThreadItem) {
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
queryKey,
queryData => {
@@ -129,8 +124,7 @@ export function createCacheMutator({
for (let i = 0; i < thread.length; i++) {
const existingPost = thread[i]
if (!AppBskyUnspeccedDefs.isThreadItemPost(post.value))
continue
if (!AppBskyUnspeccedDefs.isThreadItemPost(post.value)) continue
if (existingPost.uri === post.uri) {
const branch = getBranch(thread, i, existingPost.depth)
@@ -152,7 +146,7 @@ export function createCacheMutator({
export function getThreadPlaceholder(
queryClient: QueryClient,
uri: string,
): $Typed<AppBskyUnspeccedDefs.ThreadItem> | void {
): $Typed<AppBskyUnspeccedGetPostThreadV2.ThreadItem> | void {
let partial
for (let item of getThreadPlaceholderCandidates(queryClient, uri)) {
/*
@@ -179,7 +173,7 @@ export function* getThreadPlaceholderCandidates(
uri: string,
): Generator<
$Typed<
Omit<AppBskyUnspeccedDefs.ThreadItem, 'value'> & {
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
value: $Typed<AppBskyUnspeccedDefs.ThreadItemPost>
}
>,
+12 -31
View File
@@ -5,6 +5,7 @@ import {
} from '@atproto/api'
import {
type ApiThreadItem,
type PostThreadParams,
type ThreadItem,
type TraversalMetadata,
@@ -18,7 +19,7 @@ import {
import * as views from '#/state/queries/usePostThread/views'
export function traverse(
thread: AppBskyUnspeccedDefs.ThreadItem[],
thread: ApiThreadItem[],
{
threadgateHiddenReplies,
moderationOpts,
@@ -67,19 +68,11 @@ export function traverse(
* _up_ from there.
*/
} else if (item.depth === 0) {
if (
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(
item.value,
)
) {
if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value)) {
items.push(views.threadPostNoUnauthenticated(item))
} else if (
AppBskyUnspeccedDefs.isThreadItemNotFound(item.value)
) {
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(item.value)) {
items.push(views.threadPostNotFound(item))
} else if (
AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)
) {
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)) {
items.push(views.threadPostBlocked(item))
} else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
const post = views.threadPost({
@@ -94,25 +87,17 @@ export function traverse(
const parent = thread[pi]
if (
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(
parent.value,
)
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(parent.value)
) {
items.unshift(views.threadPostNoUnauthenticated(parent))
break parentTraversal
} else if (
AppBskyUnspeccedDefs.isThreadItemNotFound(parent.value)
) {
} else if (AppBskyUnspeccedDefs.isThreadItemNotFound(parent.value)) {
items.unshift(views.threadPostNotFound(parent))
break parentTraversal
} else if (
AppBskyUnspeccedDefs.isThreadItemBlocked(parent.value)
) {
} else if (AppBskyUnspeccedDefs.isThreadItemBlocked(parent.value)) {
items.unshift(views.threadPostBlocked(parent))
break parentTraversal
} else if (
AppBskyUnspeccedDefs.isThreadItemPost(parent.value)
) {
} else if (AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) {
items.unshift(
views.threadPost({
uri: parent.uri,
@@ -131,9 +116,7 @@ export function traverse(
* we could.
*/
const shouldBreak =
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(
item.value,
) ||
AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value) ||
AppBskyUnspeccedDefs.isThreadItemNotFound(item.value) ||
AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)
@@ -193,9 +176,7 @@ export function traverse(
for (let ci = startIndex; ci <= branch.end; ci++) {
const child = thread[ci]
if (
AppBskyUnspeccedDefs.isThreadItemPost(child.value)
) {
if (AppBskyUnspeccedDefs.isThreadItemPost(child.value)) {
const childParentMetadata = metadatas.get(
getPostRecord(child.value.post).reply?.parent?.uri || '',
)
@@ -432,7 +413,7 @@ export function traverse(
* const { start: 1, end: 3 } = getBranch(items, 1, 1)
*/
export function getBranch(
thread: AppBskyUnspeccedDefs.ThreadItem[],
thread: ApiThreadItem[],
branchStartIndex: number,
branchStartDepth: number,
) {
+5 -1
View File
@@ -1,12 +1,16 @@
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyUnspeccedGetPostThreadV2,
type AppBskyUnspeccedDefs,
type AppBskyUnspeccedGetPostThreadHiddenV2,
type AppBskyUnspeccedGetPostThreadV2,
type ModerationDecision,
} from '@atproto/api'
export type ApiThreadItem =
| AppBskyUnspeccedGetPostThreadV2.ThreadItem
| AppBskyUnspeccedGetPostThreadHiddenV2.ThreadHiddenItem
export const postThreadQueryKeyRoot = 'getPostThreadV2' as const
export const postThreadHiddenQueryKeyRoot = 'getPostThreadHiddenV2' as const
+5 -4
View File
@@ -2,12 +2,13 @@ import {
type AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyFeedThreadgate,
AppBskyUnspeccedGetPostThreadV2,
AppBskyUnspeccedDefs,
type AppBskyUnspeccedGetPostThreadV2,
AtUri,
} from '@atproto/api'
import {
type ApiThreadItem,
type ThreadItem,
type TraversalMetadata,
} from '#/state/queries/usePostThread/types'
@@ -47,9 +48,9 @@ export function getTraversalMetadata({
nextItem,
parentMetadata,
}: {
item: AppBskyUnspeccedDefs.ThreadItem
prevItem?: AppBskyUnspeccedDefs.ThreadItem
nextItem?: AppBskyUnspeccedDefs.ThreadItem
item: ApiThreadItem
prevItem?: ApiThreadItem
nextItem?: ApiThreadItem
parentMetadata?: TraversalMetadata
}): TraversalMetadata {
if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
+7 -14
View File
@@ -3,6 +3,7 @@ import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyUnspeccedDefs,
type AppBskyUnspeccedGetPostThreadV2,
AtUri,
moderatePost,
type ModerationOpts,
@@ -10,6 +11,7 @@ import {
import {makeProfileLink} from '#/lib/routes/links'
import {
type ApiThreadItem,
type ThreadItem,
type TraversalMetadata,
} from '#/state/queries/usePostThread/types'
@@ -18,10 +20,7 @@ export function threadPostNoUnauthenticated({
uri,
depth,
value,
}: AppBskyUnspeccedDefs.ThreadItem): Extract<
ThreadItem,
{type: 'threadPostNoUnauthenticated'}
> {
}: ApiThreadItem): Extract<ThreadItem, {type: 'threadPostNoUnauthenticated'}> {
return {
type: 'threadPostNoUnauthenticated',
key: uri,
@@ -35,10 +34,7 @@ export function threadPostNotFound({
uri,
depth,
value,
}: AppBskyUnspeccedDefs.ThreadItem): Extract<
ThreadItem,
{type: 'threadPostNotFound'}
> {
}: ApiThreadItem): Extract<ThreadItem, {type: 'threadPostNotFound'}> {
return {
type: 'threadPostNotFound',
key: uri,
@@ -52,10 +48,7 @@ export function threadPostBlocked({
uri,
depth,
value,
}: AppBskyUnspeccedDefs.ThreadItem): Extract<
ThreadItem,
{type: 'threadPostBlocked'}
> {
}: ApiThreadItem): Extract<ThreadItem, {type: 'threadPostBlocked'}> {
return {
type: 'threadPostBlocked',
key: uri,
@@ -125,12 +118,12 @@ export function readMore({
export function postViewToThreadPlaceholder(
post: AppBskyFeedDefs.PostView,
): $Typed<
Omit<AppBskyUnspeccedDefs.ThreadItem, 'value'> & {
Omit<AppBskyUnspeccedGetPostThreadV2.ThreadItem, 'value'> & {
value: $Typed<AppBskyUnspeccedDefs.ThreadItemPost>
}
> {
return {
$type: 'app.bsky.unspecced.defs#threadItem',
$type: 'app.bsky.unspecced.getPostThreadV2#threadItem',
uri: post.uri,
depth: 0, // reset to 0 for highlighted post
value: {
+2 -2
View File
@@ -45,7 +45,7 @@ import {type ImagePickerAsset} from 'expo-image-picker'
import {
AppBskyFeedDefs,
type AppBskyFeedGetPostThread,
AppBskyUnspeccedGetPostThreadV2,
AppBskyUnspeccedDefs,
type BskyAgent,
type RichText,
} from '@atproto/api'
@@ -427,7 +427,7 @@ export const ComposePost = ({
}
if (
!res.data.thread.every(p =>
AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(p.value),
AppBskyUnspeccedDefs.isThreadItemPost(p.value),
)
) {
throw new Error(`composer: app view returned non-post items`)
-4
View File
@@ -326,8 +326,6 @@ export function PostThread({uri}: {uri: string | undefined}) {
return arr
}, [skeleton, deferParents, maxParents, maxReplies])
console.log({anchorIndex: posts.findIndex(p => p.ctx?.isHighlightedPost)})
// This is only used on the web to keep the post in view when its parents load.
// On native, we rely on `maintainVisibleContentPosition` instead.
const didAdjustScrollWeb = useRef<boolean>(false)
@@ -423,8 +421,6 @@ export function PostThread({uri}: {uri: string | undefined}) {
(skeleton.highlightedPost.ctx.isParentLoading ||
Boolean(skeleton?.parents && skeleton.parents.length > 0))
console.log({hasParents})
const renderItem = ({item, index}: {item: RowItem; index: number}) => {
if (item === REPLY_PROMPT && hasSession) {
return (