diff --git a/src/screens/PostThread/components/ThreadReply.tsx b/src/screens/PostThread/components/ThreadReply.tsx index 4b7409c299..65255abe21 100644 --- a/src/screens/PostThread/components/ThreadReply.tsx +++ b/src/screens/PostThread/components/ThreadReply.tsx @@ -220,7 +220,7 @@ let PostThreadItemLoaded = ({ !item.ui.showParentReplyLine && a.pt_lg, !item.ui.showChildReplyLine && a.pb_sm, ], - item.ui.isDeadEnd && + item.ui.isLastChild && !item.ui.precedesParentReadMore && !item.ui.precedesChildReadMore && [a.pb_sm], ]}> diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 6c7d9e331c..140db3d993 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -212,24 +212,11 @@ export function sort( continue traversal } else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) { if (parentMetadata) { - if (metadata) { - metadata.replyIndex = parentMetadata.seenReplies - } - + /* + * Set this value before incrementing the parent's seenReplies + */ + metadata!.replyIndex = parentMetadata.seenReplies parentMetadata.seenReplies += 1 - - if (metadata) { - metadata.isLastSibling = - parentMetadata.replies - parentMetadata.unhydratedReplies === - parentMetadata.seenReplies - - if ( - parentMetadata.unhydratedReplies > 0 && - metadata.isLastSibling - ) { - metadata.upcomingParentReadMore = parentMetadata - } - } } const post = views.threadPost({ @@ -282,18 +269,11 @@ export function sort( parentMetadata: childParentMetadata, }) if (childParentMetadata) { + /* + * Set this value before incrementing the parent's seenReplies + */ + childMetadata!.replyIndex = childParentMetadata.seenReplies childParentMetadata.seenReplies += 1 - childMetadata.isLastSibling = - childParentMetadata.replies - - childParentMetadata.unhydratedReplies === - childParentMetadata.seenReplies - - if ( - childParentMetadata.unhydratedReplies > 0 && - childMetadata.isLastSibling - ) { - childMetadata.upcomingParentReadMore = childParentMetadata - } } metadatas.set(item.uri, childMetadata) metadatas.set(childMetadata.text, childMetadata) // TODO debugging @@ -346,30 +326,94 @@ export function sort( if (item.type === 'threadPost') { const metadata = metadatas.get(item.uri) + if (metadata) { if (metadata.parentMetadata) { + /* + * Copy in the parent's skipped indents + */ metadata.skippedIndents = new Set([ ...metadata.parentMetadata.skippedIndents, ]) + /* + * We can now officially calculate `isLastSibling` based on the actual data that + * we've seen. + */ + metadata.isLastSibling = + metadata.replyIndex === metadata.parentMetadata.seenReplies - 1 + + /* + * If this is the last sibling, it's implicitly part of the last + * branch of this sub-tree. + */ + if (metadata.isLastSibling) { + metadata.isPartOfLastBranchAtDepth = metadata.depth + + /** + * If the parent is part of the last branch of the sub-tree, so is the child. + */ + if (metadata.parentMetadata.isPartOfLastBranchAtDepth) { + metadata.isPartOfLastBranchAtDepth = metadata.parentMetadata.isPartOfLastBranchAtDepth + } + } + + /* + * If this is the last sibling, and the parent has unhydrated replies, + * at some point down the line we will need to show a "read more". + */ if ( - metadata.isLastSibling && - metadata.parentMetadata.unhydratedReplies <= 0 + metadata.parentMetadata.unhydratedReplies > 0 && + metadata.isLastSibling ) { + metadata.upcomingParentReadMore = metadata.parentMetadata + } + + /* + * Copy in the parent's upcoming read more, if it exists. Once we + * reach the bottom, we'll insert a "read more" + */ + if (metadata.parentMetadata.upcomingParentReadMore) { + metadata.upcomingParentReadMore = + metadata.parentMetadata.upcomingParentReadMore + } + + /** + * If this is the last sibling, and the parent has no unhydrated + * replies, then we know we can skip an indent line. + */ + if ( + metadata.parentMetadata.unhydratedReplies <= 0 && + metadata.isLastSibling + ) { + /** + * Depth is 2 more than the 0-index of the indent calculation + * bc of how we render these. So instead of handling that in the + * component, we just adjust that back to 0-index here. + */ metadata.skippedIndents.add(item.depth - 2) } } - if ( - metadata.unhydratedReplies > 0 && - (metadata.nextItemDepth === undefined || - metadata.nextItemDepth <= item.depth) - ) { + /* + * If this post has unhydrated replies, and it is the last child, then + * it itself needs a "read more" + */ + if (metadata.unhydratedReplies > 0 && metadata.isLastChild) { items.splice(i + 1, 0, views.readMore(metadata)) - i++ + i++ // skip next iteration } - if (metadata.upcomingParentReadMore && metadata.isDeadEnd) { + /* + * If there's an upcoming parent read more, this branch is part of the + * last branch of the sub-tree, and the item itself is the last child, + * insert the parent "read more". + */ + if ( + metadata.upcomingParentReadMore && + metadata.isPartOfLastBranchAtDepth === metadata.upcomingParentReadMore.depth && + metadata.isLastChild + ) { items.splice( i + 1, 0, @@ -378,6 +422,9 @@ export function sort( i++ } + /* + * Calculate the final UI state for the thread item. + */ item.ui = getThreadPostUI(metadata) } } diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index 2b9a360f59..4a71345d73 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -51,7 +51,7 @@ export type Slice = showChildReplyLine: boolean indent: number parentHasBranchingReplies: boolean - isDeadEnd: boolean + isLastChild: boolean skippedIndents: Set /** * Populated during the final traversal of the thread. Denotes whether @@ -110,11 +110,34 @@ export type TraversalMetadata = { depth: number replies: number unhydratedReplies: number + /** + * The number of replies that have been seen so far in the traversal. After + * traverssal, we can use this to calculate if we actually got all the + * replies we expected, or if some were blocked, etc. + */ seenReplies: number + /** + * The index-0-based index of this reply in the parent post's replies. + */ + replyIndex: number hasBranchingReplies: boolean isLastSibling: boolean + /** + * Indicates the post is the end-of-the-line for a given branch of replies. + */ + isLastChild: boolean + /** + * This is a live reference to the parent metadata object. Mutations to this + * are available for later use in children. + */ parentMetadata?: TraversalMetadata + /** + * The depth of the slice immediately preceding this one, if it exists. + */ prevItemDepth?: number + /** + * The depth of the slice immediately following this one, if it exists. + */ nextItemDepth?: number skippedIndents: Set [key: string]: any diff --git a/src/state/queries/usePostThread/utils.ts b/src/state/queries/usePostThread/utils.ts index 2800ace92b..00ca21b208 100644 --- a/src/state/queries/usePostThread/utils.ts +++ b/src/state/queries/usePostThread/utils.ts @@ -71,19 +71,30 @@ export function getTraversalMetadata({ replies, unhydratedReplies, seenReplies: 0, + replyIndex: 0, hasBranchingReplies, parentMetadata, - isLastSibling: false, + isTopLevelReply: item.depth === 1, skippedIndents: new Set(), prevItemDepth: prevItem?.depth, nextItemDepth: nextItem?.depth, /* - * If there are no slices below this one, or the next slice is less - * indented than the computed indent for this post. + * If it's a top level reply, bc we render each top-level branch as a + * separate tree, it's implicitly part of the last branch. For subsequent + * replies, we'll override this after traversal. */ - isDeadEnd: nextItem?.depth === undefined || nextItem?.depth < item.depth, - - upcomingParentReadMore: parentMetadata?.upcomingParentReadMore || undefined, + isPartOfLastBranchAtDepth: item.depth === 1 ? 1 : undefined, + /* + * If there are no slices below this one, or the next slice has a depth <= + * than the depth of this post, it's the last child of the reply tree. It + * is not necessarily the last leaf in the parent branch, since it could + * have another sibling. + */ + isLastChild: nextItem?.depth === undefined || nextItem?.depth <= item.depth, + /* + * Unknown until after traversal + */ + isLastSibling: false, // TODO non-spec text: getPostRecord(item.value.post).text, @@ -95,7 +106,7 @@ export function getThreadPostUI({ replies, parentMetadata, prevItemDepth, - isDeadEnd, + isLastChild, skippedIndents, seenReplies, unhydratedReplies, @@ -112,7 +123,7 @@ export function getThreadPostUI({ * If there are no slices below this one, or the next slice is less * indented than the computed indent for this post. */ - isDeadEnd, //nextItemDepth === undefined || nextItemDepth < depth, + isLastChild, //nextItemDepth === undefined || nextItemDepth < depth, skippedIndents, } }