Clean it up
This commit is contained in:
@@ -24,25 +24,25 @@ export function ReadMore({
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const isTreeView = view === 'tree'
|
||||
const indent = Math.max(0, item.indent - 1)
|
||||
const indent = Math.max(0, item.depth - 1)
|
||||
|
||||
const spacers = isTreeView
|
||||
? Array.from(Array(indent)).map((_, n: number) => {
|
||||
const isSkipped = item.skippedIndents.has(n)
|
||||
return (
|
||||
<View
|
||||
key={`${item.key}-padding-${n}`}
|
||||
style={[
|
||||
t.atoms.border_contrast_low,
|
||||
{
|
||||
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
|
||||
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
|
||||
left: 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
const isSkipped = item.skippedIndentIndices.has(n)
|
||||
return (
|
||||
<View
|
||||
key={`${item.key}-padding-${n}`}
|
||||
style={[
|
||||
t.atoms.border_contrast_low,
|
||||
{
|
||||
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
|
||||
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
|
||||
left: 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null
|
||||
|
||||
return (
|
||||
@@ -79,7 +79,11 @@ export function ReadMore({
|
||||
]}>
|
||||
<Trans>
|
||||
Read {item.moreReplies} more{' '}
|
||||
<Plural one="reply" other="replies" value={item.moreReplies} />
|
||||
<Plural
|
||||
one="reply"
|
||||
other="replies"
|
||||
value={item.moreReplies}
|
||||
/>
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
|
||||
@@ -193,7 +193,7 @@ let PostThreadItemLoaded = ({
|
||||
],
|
||||
]}>
|
||||
{Array.from(Array(indents)).map((_, n: number) => {
|
||||
const isSkipped = item.ui.skippedIndents.has(n)
|
||||
const isSkipped = item.ui.skippedIndentIndices.has(n)
|
||||
return (
|
||||
<View
|
||||
key={`${post.uri}-padding-${n}`}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getPostRecord,
|
||||
getThreadPostUI,
|
||||
getTraversalMetadata,
|
||||
storeTraversalMetadata,
|
||||
} from '#/state/queries/usePostThread/utils'
|
||||
import * as views from '#/state/queries/usePostThread/views'
|
||||
|
||||
@@ -23,7 +24,6 @@ export function flatten(
|
||||
hasSession,
|
||||
showMuted,
|
||||
showHidden,
|
||||
view,
|
||||
}: {
|
||||
hasSession: boolean
|
||||
showMuted: boolean
|
||||
@@ -107,9 +107,6 @@ export function sort(
|
||||
const muted: Slice[] = []
|
||||
const metadatas = new Map<string, TraversalMetadata>()
|
||||
|
||||
// @ts-ignore
|
||||
window.__data = metadatas // for debugging
|
||||
|
||||
traversal: for (let i = 0; i < thread.length; i++) {
|
||||
const item = thread[i]
|
||||
let parentMetadata: TraversalMetadata | undefined
|
||||
@@ -125,8 +122,7 @@ export function sort(
|
||||
prevItem: thread.at(i - 1),
|
||||
nextItem: thread.at(i + 1),
|
||||
})
|
||||
metadatas.set(item.uri, metadata)
|
||||
metadatas.set(metadata.text, metadata) // TODO debugging
|
||||
storeTraversalMetadata(metadatas, metadata)
|
||||
}
|
||||
|
||||
if (item.depth < 0) {
|
||||
@@ -213,10 +209,10 @@ export function sort(
|
||||
} else if (AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(item.value)) {
|
||||
if (parentMetadata) {
|
||||
/*
|
||||
* Set this value before incrementing the parent's seenReplies
|
||||
* Set this value before incrementing the parent's repliesSeenCount
|
||||
*/
|
||||
metadata!.replyIndex = parentMetadata.seenReplies
|
||||
parentMetadata.seenReplies += 1
|
||||
metadata!.repliesIndex = parentMetadata.repliesSeenCount
|
||||
parentMetadata.repliesSeenCount += 1
|
||||
}
|
||||
|
||||
const post = views.threadPost({
|
||||
@@ -268,15 +264,15 @@ export function sort(
|
||||
nextItem: thread[ci + 1],
|
||||
parentMetadata: childParentMetadata,
|
||||
})
|
||||
storeTraversalMetadata(metadatas, childMetadata)
|
||||
if (childParentMetadata) {
|
||||
/*
|
||||
* Set this value before incrementing the parent's seenReplies
|
||||
* Set this value before incrementing the parent's repliesSeenCount
|
||||
*/
|
||||
childMetadata!.replyIndex = childParentMetadata.seenReplies
|
||||
childParentMetadata.seenReplies += 1
|
||||
childMetadata!.repliesIndex =
|
||||
childParentMetadata.repliesSeenCount
|
||||
childParentMetadata.repliesSeenCount += 1
|
||||
}
|
||||
metadatas.set(item.uri, childMetadata)
|
||||
metadatas.set(childMetadata.text, childMetadata) // TODO debugging
|
||||
|
||||
const childPost = views.threadPost({
|
||||
uri: child.uri,
|
||||
@@ -332,8 +328,8 @@ export function sort(
|
||||
/*
|
||||
* Copy in the parent's skipped indents
|
||||
*/
|
||||
metadata.skippedIndents = new Set([
|
||||
...metadata.parentMetadata.skippedIndents,
|
||||
metadata.skippedIndentIndices = new Set([
|
||||
...metadata.parentMetadata.skippedIndentIndices,
|
||||
])
|
||||
|
||||
/*
|
||||
@@ -341,20 +337,22 @@ export function sort(
|
||||
* we've seen.
|
||||
*/
|
||||
metadata.isLastSibling =
|
||||
metadata.replyIndex === metadata.parentMetadata.seenReplies - 1
|
||||
metadata.repliesIndex ===
|
||||
metadata.parentMetadata.repliesSeenCount - 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
|
||||
metadata.isPartOfLastBranchFromDepth = 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 (metadata.parentMetadata.isPartOfLastBranchFromDepth) {
|
||||
metadata.isPartOfLastBranchFromDepth =
|
||||
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +361,7 @@ export function sort(
|
||||
* at some point down the line we will need to show a "read more".
|
||||
*/
|
||||
if (
|
||||
metadata.parentMetadata.unhydratedReplies > 0 &&
|
||||
metadata.parentMetadata.repliesUnhydrated > 0 &&
|
||||
metadata.isLastSibling
|
||||
) {
|
||||
metadata.upcomingParentReadMore = metadata.parentMetadata
|
||||
@@ -383,7 +381,7 @@ export function sort(
|
||||
* replies, then we know we can skip an indent line.
|
||||
*/
|
||||
if (
|
||||
metadata.parentMetadata.unhydratedReplies <= 0 &&
|
||||
metadata.parentMetadata.repliesUnhydrated <= 0 &&
|
||||
metadata.isLastSibling
|
||||
) {
|
||||
/**
|
||||
@@ -391,7 +389,7 @@ export function sort(
|
||||
* 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)
|
||||
metadata.skippedIndentIndices.add(item.depth - 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +397,7 @@ export function sort(
|
||||
* 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) {
|
||||
if (metadata.repliesUnhydrated > 0 && metadata.isLastChild) {
|
||||
items.splice(i + 1, 0, views.readMore(metadata))
|
||||
i++ // skip next iteration
|
||||
}
|
||||
@@ -411,7 +409,8 @@ export function sort(
|
||||
*/
|
||||
if (
|
||||
metadata.upcomingParentReadMore &&
|
||||
metadata.isPartOfLastBranchAtDepth === metadata.upcomingParentReadMore.depth &&
|
||||
metadata.isPartOfLastBranchFromDepth ===
|
||||
metadata.upcomingParentReadMore.depth &&
|
||||
metadata.isLastChild
|
||||
) {
|
||||
items.splice(
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPost,
|
||||
type AppBskyUnspeccedGetPostThreadV2,
|
||||
type AtUri,
|
||||
type ModerationDecision,
|
||||
} from '@atproto/api'
|
||||
|
||||
@@ -50,9 +49,8 @@ export type Slice =
|
||||
showParentReplyLine: boolean
|
||||
showChildReplyLine: boolean
|
||||
indent: number
|
||||
parentHasBranchingReplies: boolean
|
||||
isLastChild: boolean
|
||||
skippedIndents: Set<number>
|
||||
skippedIndentIndices: Set<number>
|
||||
/**
|
||||
* Populated during the final traversal of the thread. Denotes whether
|
||||
* there is a "Read more" link for the parent immediately following
|
||||
@@ -100,45 +98,82 @@ export type Slice =
|
||||
| {
|
||||
type: 'readMore'
|
||||
key: string
|
||||
indent: number
|
||||
depth: number
|
||||
href: string
|
||||
moreReplies: number
|
||||
skippedIndents: Set<number>
|
||||
skippedIndentIndices: Set<number>
|
||||
}
|
||||
|
||||
export type TraversalMetadata = {
|
||||
/**
|
||||
* The depth of the post in the reply tree, where 0 is the root post. This is
|
||||
* calculated on the server.
|
||||
*/
|
||||
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.
|
||||
* Indicates if the post is the last reply beneath its parent post.
|
||||
*/
|
||||
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.
|
||||
* Indicates if the post is the left/lower-most branch of the reply tree.
|
||||
* Value corresponds to the depth at which this branch started.
|
||||
*/
|
||||
parentMetadata?: TraversalMetadata
|
||||
isPartOfLastBranchFromDepth?: number
|
||||
/**
|
||||
* The depth of the slice immediately following this one, if it exists.
|
||||
*/
|
||||
nextItemDepth?: number
|
||||
/**
|
||||
* 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.
|
||||
* This is a live reference to the parent metadata object. Mutations to this
|
||||
* are available for later use in children.
|
||||
*/
|
||||
nextItemDepth?: number
|
||||
skippedIndents: Set<number>
|
||||
[key: string]: any
|
||||
parentMetadata?: TraversalMetadata
|
||||
/**
|
||||
* Any data needed to be passed along to the "read more" items. Keep this
|
||||
* trim for better memory usage.
|
||||
*/
|
||||
postData: {
|
||||
uri: string
|
||||
authorHandle: string
|
||||
}
|
||||
/**
|
||||
* The total number of replies to this post, including those not hydrated
|
||||
* and returned by the response.
|
||||
*/
|
||||
repliesCount: number
|
||||
/**
|
||||
* The number of replies to this post not hydrated and returned by the
|
||||
* response.
|
||||
*/
|
||||
repliesUnhydrated: 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.
|
||||
*/
|
||||
repliesSeenCount: number
|
||||
/**
|
||||
* The index-0-based index of this reply in the parent post's replies.
|
||||
*/
|
||||
repliesIndex: number
|
||||
/**
|
||||
* Each slice is responsible for rendering reply lines based on its depth.
|
||||
* This value corresponds to any line indices that can be skipped e.g.
|
||||
* because there are no further replies below this sub-tree to render.
|
||||
*/
|
||||
skippedIndentIndices: Set<number>
|
||||
/**
|
||||
* Indicates and stores parent data IF that parent has additional unhydrated
|
||||
* replies. This value is passed down to children along the left/lower-most
|
||||
* branch of the tree. When the end is reached, a "read more" is inserted.
|
||||
*/
|
||||
upcomingParentReadMore?: TraversalMetadata
|
||||
}
|
||||
|
||||
@@ -54,36 +54,10 @@ export function getTraversalMetadata({
|
||||
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
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const hasBranchingReplies = replies > 1 && replies - unhydratedReplies > 1
|
||||
|
||||
return {
|
||||
uri: item.uri,
|
||||
const repliesCount = item.value.post.replyCount || 0
|
||||
const repliesUnhydrated = item.value.moreReplies || 0
|
||||
const metadata = {
|
||||
depth: item.depth,
|
||||
authorHandle: item.value.post.author.handle,
|
||||
replies,
|
||||
unhydratedReplies,
|
||||
seenReplies: 0,
|
||||
replyIndex: 0,
|
||||
hasBranchingReplies,
|
||||
parentMetadata,
|
||||
isTopLevelReply: item.depth === 1,
|
||||
skippedIndents: new Set(),
|
||||
prevItemDepth: prevItem?.depth,
|
||||
nextItemDepth: nextItem?.depth,
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
@@ -95,35 +69,74 @@ export function getTraversalMetadata({
|
||||
* Unknown until after traversal
|
||||
*/
|
||||
isLastSibling: false,
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
isPartOfLastBranchFromDepth: item.depth === 1 ? 1 : undefined,
|
||||
nextItemDepth: nextItem?.depth,
|
||||
prevItemDepth: prevItem?.depth,
|
||||
parentMetadata,
|
||||
postData: {
|
||||
uri: item.uri,
|
||||
authorHandle: item.value.post.author.handle,
|
||||
},
|
||||
repliesCount,
|
||||
repliesUnhydrated,
|
||||
repliesSeenCount: 0,
|
||||
repliesIndex: 0,
|
||||
skippedIndentIndices: new Set<number>(),
|
||||
}
|
||||
|
||||
// TODO non-spec
|
||||
text: getPostRecord(item.value.post).text,
|
||||
if (__DEV__) {
|
||||
// @ts-ignore dev only for debugging
|
||||
metadata.postData.text = getPostRecord(item.value.post).text
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
export function storeTraversalMetadata(
|
||||
metadatas: Map<string, TraversalMetadata>,
|
||||
metadata: TraversalMetadata,
|
||||
) {
|
||||
metadatas.set(metadata.postData.uri, metadata)
|
||||
|
||||
if (__DEV__) {
|
||||
// @ts-ignore dev only for debugging
|
||||
metadatas.set(metadata.postData.text, metadata)
|
||||
// @ts-ignore
|
||||
window.__thread = metadatas
|
||||
}
|
||||
}
|
||||
|
||||
export function getThreadPostUI({
|
||||
depth,
|
||||
replies,
|
||||
parentMetadata,
|
||||
repliesCount,
|
||||
prevItemDepth,
|
||||
isLastChild,
|
||||
skippedIndents,
|
||||
seenReplies,
|
||||
unhydratedReplies,
|
||||
skippedIndentIndices,
|
||||
repliesSeenCount,
|
||||
repliesUnhydrated,
|
||||
}: TraversalMetadata): Extract<Slice, {type: 'threadPost'}>['ui'] {
|
||||
const isReplyAndHasReplies = depth > 0 && replies > 0 && ((replies - unhydratedReplies) === seenReplies || seenReplies > 0)
|
||||
// TODO might be able to simplify this
|
||||
const isReplyAndHasReplies =
|
||||
depth > 0 &&
|
||||
repliesCount > 0 &&
|
||||
(repliesCount - repliesUnhydrated === repliesSeenCount ||
|
||||
repliesSeenCount > 0)
|
||||
return {
|
||||
isAnchor: depth === 0,
|
||||
showParentReplyLine:
|
||||
!!prevItemDepth && prevItemDepth !== 0 && prevItemDepth < depth,
|
||||
showChildReplyLine: depth < 0 || isReplyAndHasReplies,
|
||||
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.
|
||||
*/
|
||||
isLastChild, //nextItemDepth === undefined || nextItemDepth < depth,
|
||||
skippedIndents,
|
||||
skippedIndentIndices,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from '@atproto/api'
|
||||
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {type Slice, type TraversalMetadata} from '#/state/queries/usePostThread/types'
|
||||
import {
|
||||
type Slice,
|
||||
type TraversalMetadata,
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
|
||||
export function threadPostNoUnauthenticated({
|
||||
uri,
|
||||
@@ -82,7 +85,7 @@ export function threadPost({
|
||||
...value,
|
||||
/*
|
||||
* Do not spread anything here, load bearing for post shadow strict
|
||||
* equality checks.
|
||||
* equality reference checks.
|
||||
*/
|
||||
post: value.post as Omit<AppBskyFeedDefs.PostView, 'record'> & {
|
||||
record: AppBskyFeedPost.Record
|
||||
@@ -95,28 +98,27 @@ export function threadPost({
|
||||
}
|
||||
|
||||
export function readMore({
|
||||
uri,
|
||||
authorHandle,
|
||||
unhydratedReplies: moreReplies,
|
||||
depth: indent,
|
||||
skippedIndents,
|
||||
depth,
|
||||
repliesUnhydrated,
|
||||
skippedIndentIndices,
|
||||
postData,
|
||||
}: TraversalMetadata): Extract<Slice, {type: 'readMore'}> {
|
||||
const urip = new AtUri(uri)
|
||||
const urip = new AtUri(postData.uri)
|
||||
const href = makeProfileLink(
|
||||
{
|
||||
did: urip.host,
|
||||
handle: authorHandle,
|
||||
handle: postData.authorHandle,
|
||||
},
|
||||
'post',
|
||||
urip.rkey,
|
||||
)
|
||||
return {
|
||||
type: 'readMore' as const,
|
||||
key: `readMore:${uri}`,
|
||||
key: `readMore:${postData.uri}`,
|
||||
href,
|
||||
moreReplies,
|
||||
indent,
|
||||
skippedIndents,
|
||||
moreReplies: repliesUnhydrated,
|
||||
depth,
|
||||
skippedIndentIndices,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user