Checkpoint new traversal

This commit is contained in:
Eric Bailey
2025-05-28 15:56:02 -05:00
parent 1cc02b8bbd
commit 2c80c1f288
6 changed files with 154 additions and 14 deletions
@@ -193,19 +193,14 @@ let PostThreadItemLoaded = ({
], ],
]}> ]}>
{Array.from(Array(indents)).map((_, n: number) => { {Array.from(Array(indents)).map((_, n: number) => {
const isLastIteration = n === indents - 1 const isSkipped = item.ui.skippedIndents.has(n)
return ( return (
<View <View
key={`${post.uri}-padding-${n}`} key={`${post.uri}-padding-${n}`}
style={[ style={[
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
{ {
borderRightWidth: borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
isLastIteration &&
item.ui.isDeadEnd &&
!item.ui.precedesParentReadMore
? 0
: REPLY_LINE_WIDTH,
width: TREE_INDENT + TREE_AVI_WIDTH / 2, width: TREE_INDENT + TREE_AVI_WIDTH / 2,
left: 1, left: 1,
}, },
@@ -229,7 +224,7 @@ let PostThreadItemLoaded = ({
!item.ui.precedesParentReadMore && !item.ui.precedesParentReadMore &&
!item.ui.precedesChildReadMore && [a.pb_sm], !item.ui.precedesChildReadMore && [a.pb_sm],
]}> ]}>
{item.ui.indent > 1 && item.ui.parentHasBranchingReplies && ( {item.ui.indent > 1 && (
<View <View
style={[ style={[
a.absolute, a.absolute,
+1 -1
View File
@@ -41,7 +41,7 @@ export function usePostThread({
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({ const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
anchor: params.anchor!, anchor: params.anchor!,
branchingFactor: params.view === 'linear' ? 1 : 3, // 100 TODO branchingFactor: params.view === 'linear' ? 1 : 3, // 100 TODO
below: 3, below: 6,
sort: params.sort, sort: params.sort,
prioritizeFollowedUsers: params.prioritizeFollowedUsers, prioritizeFollowedUsers: params.prioritizeFollowedUsers,
}) })
+67 -3
View File
@@ -6,6 +6,7 @@ import {
import { import {
HiddenReplyKind, HiddenReplyKind,
type NTraversalMetadata,
type PostThreadParams, type PostThreadParams,
type Slice, type Slice,
type TraversalMetadata, type TraversalMetadata,
@@ -13,6 +14,8 @@ import {
import { import {
getPostRecord, getPostRecord,
getPostTraversalMetadata, getPostTraversalMetadata,
getThreadPostUI,
getTraversalMetadata,
} from '#/state/queries/usePostThread/utils' } from '#/state/queries/usePostThread/utils'
import * as views from '#/state/queries/usePostThread/views' import * as views from '#/state/queries/usePostThread/views'
@@ -147,7 +150,6 @@ export function flatten(
} }
} }
} }
console.log(flattened)
/* /*
* Insert hidden items and buttons to show them * Insert hidden items and buttons to show them
@@ -205,6 +207,11 @@ export function sort(
const muted: Slice[] = [] const muted: Slice[] = []
const postDataMap = new Map<string, TraversalMetadata | undefined>() const postDataMap = new Map<string, TraversalMetadata | undefined>()
const metadatas = new Map<string, NTraversalMetadata>()
// @ts-ignore
window.__data = metadatas // for debugging
traversal: for (let i = 0; i < thread.length; i++) { traversal: for (let i = 0; i < thread.length; i++) {
const item = thread[i] const item = thread[i]
@@ -297,8 +304,25 @@ export function sort(
i = branch.end i = branch.end
continue traversal continue traversal
} else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) { } else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
const parentMetadata = metadatas.get(
getPostRecord(item.value.post).reply?.parent?.uri || '',
)
const oneUp = thread.at(i - 1) const oneUp = thread.at(i - 1)
const oneDown = thread.at(i + 1) const oneDown = thread.at(i + 1)
const metadata = getTraversalMetadata({
item,
parentMetadata,
prevItem: oneUp,
nextItem: oneDown,
})
if (parentMetadata) {
parentMetadata.seenReplies += 1
metadata.isLastSibling =
parentMetadata.replies === parentMetadata.seenReplies
}
metadatas.set(item.uri, metadata)
metadatas.set(metadata.text, metadata) // TODO debugging
const post = views.threadPost({ const post = views.threadPost({
uri: item.uri, uri: item.uri,
depth: item.depth, depth: item.depth,
@@ -345,6 +369,26 @@ export function sort(
if ( if (
AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(child.value) AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(child.value)
) { ) {
const childParentMetadata = metadatas.get(
getPostRecord(child.value.post).reply?.parent?.uri || '',
)
const prevItem = thread[ci - 1]
const nextItem = thread[ci + 1]
const childMetadata = getTraversalMetadata({
item: child,
prevItem,
nextItem,
parentMetadata: childParentMetadata,
})
if (childParentMetadata) {
childParentMetadata.seenReplies += 1
childMetadata.isLastSibling =
childParentMetadata.replies ===
childParentMetadata.seenReplies
}
metadatas.set(item.uri, childMetadata)
metadatas.set(childMetadata.text, childMetadata) // TODO debugging
const childPost = views.threadPost({ const childPost = views.threadPost({
uri: child.uri, uri: child.uri,
depth: child.depth, depth: child.depth,
@@ -352,8 +396,8 @@ export function sort(
traversalMetadata: postDataMap.get( traversalMetadata: postDataMap.get(
getPostRecord(child.value.post)?.reply?.parent?.uri || '', getPostRecord(child.value.post)?.reply?.parent?.uri || '',
), ),
oneUp: thread[ci - 1], oneUp: prevItem,
oneDown: thread[ci + 1], oneDown: nextItem,
moderationOpts, moderationOpts,
}) })
postDataMap.set(child.uri, getPostTraversalMetadata(childPost)) postDataMap.set(child.uri, getPostTraversalMetadata(childPost))
@@ -394,6 +438,26 @@ export function sort(
} }
} }
for (const item of items) {
if (item.type === 'threadPost') {
const metadata = metadatas.get(item.uri)
if (metadata) {
if (metadata.parentMetadata) {
metadata.skippedIndents = new Set([
...metadata.parentMetadata.skippedIndents,
])
}
if (metadata.isLastSibling) {
metadata.skippedIndents.add(item.depth - 2)
}
item.ui = getThreadPostUI(metadata)
}
}
}
// console.log(metadatas)
return { return {
items, items,
hidden, hidden,
+17
View File
@@ -64,6 +64,8 @@ export type Slice =
* this item. * this item.
*/ */
precedesChildReadMore?: boolean precedesChildReadMore?: boolean
skippedIndents: Set<number>
[key: string]: any
} }
} }
| { | {
@@ -109,3 +111,18 @@ export type TraversalMetadata = {
indent: number indent: number
hasBranchingReplies: boolean hasBranchingReplies: boolean
} }
export type NTraversalMetadata = {
depth: number
indent: number
replies: number
unhydratedReplies: number
seenReplies: number
hasBranchingReplies: boolean
isLastSibling: boolean
parentMetadata?: NTraversalMetadata
prevItemDepth?: number
nextItemDepth?: number
skippedIndents: Set<number>
[key: string]: any
}
+65
View File
@@ -7,6 +7,7 @@ import {
} from '@atproto/api' } from '@atproto/api'
import { import {
type NTraversalMetadata,
type Slice, type Slice,
type TraversalMetadata, type TraversalMetadata,
} from '#/state/queries/usePostThread/types' } from '#/state/queries/usePostThread/types'
@@ -57,3 +58,67 @@ export function getPostTraversalMetadata(
hasBranchingReplies: replyCount > 1 && replyCount - unhydratedReplies > 1, hasBranchingReplies: replyCount > 1 && replyCount - unhydratedReplies > 1,
} }
} }
export function getTraversalMetadata({
item,
prevItem,
nextItem,
parentMetadata,
}: {
item: AppBskyUnspeccedGetPostThreadV2.ThreadItem
prevItem?: AppBskyUnspeccedGetPostThreadV2.ThreadItem
nextItem?: AppBskyUnspeccedGetPostThreadV2.ThreadItem
parentMetadata?: NTraversalMetadata
}): NTraversalMetadata {
if (!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
throw new Error(`Expected thread item to be a post`)
}
const replies = item.value.post.replyCount || 0
const unhydratedReplies = item.value.moreReplies || 0
const hasBranchingReplies = replies > 1 && replies - unhydratedReplies > 1
return {
depth: item.depth,
// TODO maybe not used
indent: parentMetadata?.hasBranchingReplies
? item.depth
: parentMetadata?.indent || item.depth,
replies,
unhydratedReplies,
seenReplies: 0,
hasBranchingReplies,
parentMetadata,
isLastSibling: false,
skippedIndents: new Set(),
prevItemDepth: prevItem?.depth,
nextItemDepth: nextItem?.depth,
// TODO non-spec
text: getPostRecord(item.value.post).text,
}
}
export function getThreadPostUI({
depth,
indent,
replies,
parentMetadata,
prevItemDepth,
nextItemDepth,
skippedIndents,
}: NTraversalMetadata) {
return {
isAnchor: depth === 0,
showParentReplyLine:
!!prevItemDepth && prevItemDepth !== 0 && prevItemDepth < depth,
showChildReplyLine: replies > 0,
indent: depth,
parentHasBranchingReplies: !!parentMetadata?.hasBranchingReplies,
/*
* 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 < indent,
skippedIndents,
}
}
+1 -2
View File
@@ -117,6 +117,7 @@ export function threadPost({
* indented than the computed indent for this post. * indented than the computed indent for this post.
*/ */
isDeadEnd: !oneDown || oneDown?.depth < indent, isDeadEnd: !oneDown || oneDown?.depth < indent,
skippedIndents: new Set(),
}, },
} }
} }
@@ -152,11 +153,9 @@ export function postViewToThreadPlaceholder(
value: { value: {
$type: 'app.bsky.unspecced.getPostThreadV2#threadItemPost', $type: 'app.bsky.unspecced.getPostThreadV2#threadItemPost',
post, post,
hiddenByThreadgate: false,
opThread: false, opThread: false,
moreParents: false, moreParents: false,
moreReplies: 0, moreReplies: 0,
mutedByViewer: false,
}, },
} }
} }