From 1f73c0519560492e20d459d14b1c88cabe0a9216 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 27 May 2025 20:50:04 -0500 Subject: [PATCH] Fix up traversal metadata --- .../PostThread/components/ReadMore.tsx | 4 +-- src/state/queries/usePostThread/traversal.ts | 20 +++++++++--- src/state/queries/usePostThread/types.ts | 5 +++ src/state/queries/usePostThread/utils.ts | 25 ++++++++++++++- src/state/queries/usePostThread/views.ts | 31 +++++++------------ 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/screens/PostThread/components/ReadMore.tsx b/src/screens/PostThread/components/ReadMore.tsx index f575eb48cd..3b0cfc0c68 100644 --- a/src/screens/PostThread/components/ReadMore.tsx +++ b/src/screens/PostThread/components/ReadMore.tsx @@ -4,7 +4,7 @@ import {useLingui} from '@lingui/react' import {makeProfileLink} from '#/lib/routes/links' import {type PostThreadParams, type Slice} from '#/state/queries/usePostThread' -import {TREE_AVI_WIDTH,TREE_INDENT} from '#/screens/PostThread/const' +import {TREE_AVI_WIDTH, TREE_INDENT} from '#/screens/PostThread/const' import {atoms as a, useTheme} from '#/alf' import {CirclePlus_Stroke2_Corner0_Rounded as CirclePlus} from '#/components/icons/CirclePlus' import {Link} from '#/components/Link' @@ -20,7 +20,7 @@ export function ReadMore({ const t = useTheme() const {_} = useLingui() const isTreeView = view === 'tree' - const indentCount = item.indent - 1 + const indentCount = Math.max(0, item.indent - 1) const treeIndents = isTreeView ? ( Array.from(Array(indentCount)).map((_, n: number) => ( diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index deeaa16c7b..190ee6775f 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -8,7 +8,12 @@ import { HiddenReplyKind, type PostThreadParams, type Slice, + type TraversalMetadata, } from '#/state/queries/usePostThread/types' +import { + getPostRecord, + getPostTraversalMetadata, +} from '#/state/queries/usePostThread/utils' import * as views from '#/state/queries/usePostThread/views' export function flatten( @@ -168,6 +173,7 @@ export function sort( const items: Slice[] = [] const hidden: Slice[] = [] const muted: Slice[] = [] + const postDataMap = new Map() traversal: for (let i = 0; i < thread.length; i++) { const item = thread[i] @@ -193,6 +199,8 @@ export function sort( ) { items.push(views.threadPostBlocked(item)) } else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) { + postDataMap.set(item.uri, getPostTraversalMetadata(item)) + items.push( views.threadPost({ uri: item.uri, @@ -261,14 +269,16 @@ export function sort( i = branch.end continue traversal } else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) { + postDataMap.set(item.uri, getPostTraversalMetadata(item)) + const oneUp = thread.at(i - 1) const oneDown = thread.at(i + 1) const post = views.threadPost({ uri: item.uri, depth: item.depth, value: item.value, - parent: thread.find( - p => p.uri === item.value.post.record.reply.parent.uri, + traversalMetadata: postDataMap.get( + getPostRecord(item.value.post)?.reply?.parent?.uri || '', ), oneUp, oneDown, @@ -308,12 +318,14 @@ export function sort( if ( AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(child.value) ) { + postDataMap.set(child.uri, getPostTraversalMetadata(child)) + const childPost = views.threadPost({ uri: child.uri, depth: child.depth, value: child.value, - parent: thread.find( - p => p.uri === child.value.post.record.reply.parent.uri, + traversalMetadata: postDataMap.get( + getPostRecord(child.value.post)?.reply?.parent?.uri || '', ), oneUp: thread[ci - 1], oneDown: thread[ci + 1], diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index 20e9b64623..567596209a 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -91,3 +91,8 @@ export type Slice = nextAnchor: Extract nextAnchorUri: AtUri } + +export type TraversalMetadata = { + depth: number + hasBranchingReplies: boolean +} diff --git a/src/state/queries/usePostThread/utils.ts b/src/state/queries/usePostThread/utils.ts index efcb45b02d..a8e283b60b 100644 --- a/src/state/queries/usePostThread/utils.ts +++ b/src/state/queries/usePostThread/utils.ts @@ -2,10 +2,11 @@ import { type AppBskyFeedDefs, AppBskyFeedPost, AppBskyFeedThreadgate, - type AppBskyUnspeccedGetPostThreadV2, + AppBskyUnspeccedGetPostThreadV2, AtUri, } from '@atproto/api' +import {type TraversalMetadata} from '#/state/queries/usePostThread/types' import * as bsky from '#/types/bsky' export function getThreadgateRecord( @@ -31,3 +32,25 @@ export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) { } } } + +export function getPostRecord(post: AppBskyFeedDefs.PostView) { + return post.record as AppBskyFeedPost.Record +} + +export function getPostTraversalMetadata( + item: AppBskyUnspeccedGetPostThreadV2.ThreadItem, +): TraversalMetadata | undefined { + if (!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) return + const replyCount = item.value.post.replyCount || 0 + const unhydratedReplies = item.value.moreReplies || 0 + return { + depth: item.depth, + /** + * If the post has more than a single reply, and the total reply count + * minus the number of replies not present in the response is greater than + * 1, then we must have more than a single branch of replies present in the + * response, which can affect how we render tree view. + */ + hasBranchingReplies: replyCount > 1 && replyCount - unhydratedReplies > 1, + } +} diff --git a/src/state/queries/usePostThread/views.ts b/src/state/queries/usePostThread/views.ts index 9cab23943b..eadcb36fcb 100644 --- a/src/state/queries/usePostThread/views.ts +++ b/src/state/queries/usePostThread/views.ts @@ -8,7 +8,10 @@ import { type ModerationOpts, } from '@atproto/api' -import {type Slice} from '#/state/queries/usePostThread/types' +import { + type Slice, + type TraversalMetadata, +} from '#/state/queries/usePostThread/types' export function threadPostNoUnauthenticated({ uri, @@ -65,32 +68,18 @@ export function threadPost({ uri, depth, value, - parent: up, oneUp, moderationOpts, + traversalMetadata, }: { uri: string depth: number value: $Typed - parent?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number] oneUp?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number] oneDown?: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'][number] moderationOpts: ModerationOpts + traversalMetadata?: TraversalMetadata }): Extract { - const parent = (up && 'post' in up?.value - ? up.value - : undefined) as unknown as AppBskyUnspeccedGetPostThreadV2.ThreadItemPost - const parentReplyCount = parent?.post?.replyCount || 0 - const parentAdditionalReplies = parent?.moreReplies || 0 - const parentHasBranchingReplies = - parentReplyCount > 1 && parentReplyCount - parentAdditionalReplies > 1 - // if (!parentHasBranchingReplies) { - // console.log(value.post.record.text, { - // parentReplyCount, - // parentAdditionalReplies, - // }) - // } - return { type: 'threadPost', key: uri, @@ -111,8 +100,10 @@ export function threadPost({ isAnchor: depth === 0, showParentReplyLine: !!oneUp && oneUp.depth !== 0 && oneUp.depth < depth, showChildReplyLine: (value.post.replyCount || 0) > 0, - indent: parentHasBranchingReplies ? depth : up?.depth || depth, - parentHasBranchingReplies, + indent: traversalMetadata?.hasBranchingReplies + ? depth + : traversalMetadata?.depth || depth, + parentHasBranchingReplies: !!traversalMetadata?.hasBranchingReplies, }, } } @@ -127,7 +118,7 @@ export function readMore({ key: `readMore:${parent.uri}`, indent: parent.ui.parentHasBranchingReplies ? parent.depth - : parent.depth - 1, + : Math.max(0, parent.depth - 1), replyCount: parent.value.moreReplies, nextAnchor: parent, nextAnchorUri: new AtUri(parent.uri),