Two passes only

This commit is contained in:
Eric Bailey
2025-05-29 16:36:19 -05:00
parent 98711ebc2b
commit 9ca42f20a4
3 changed files with 80 additions and 101 deletions
+15 -13
View File
@@ -116,12 +116,19 @@ export function Inner({uri}: {uri: string | undefined}) {
// distance from top of the list (screen)
const anchorOffsetTop = anchorElement.getBoundingClientRect().top
const headerHeight = headerElement.getBoundingClientRect().height
// don't scroll past 0
const scrollPosition = Math.max(0, anchorOffsetTop - headerHeight)
listRef.current?.scrollToOffset({
animated: false,
offset: scrollPosition,
})
const scrollPosition = anchorOffsetTop - headerHeight
/*
* 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,
* we want to restore the previous scroll position by not scrolling here
* at all.
*/
if (scrollPosition >= 0) {
listRef.current?.scrollToOffset({
animated: false,
offset: scrollPosition,
})
}
}
})
@@ -137,15 +144,10 @@ export function Inner({uri}: {uri: string | undefined}) {
* scroll in onContentSizeChange instead.
*/
const [deferParents, setDeferParents] = useState(isNative)
const items = useMemo(() => {
return (data?.items ?? []).filter(item => {
return !('depth' in item) || item.depth >= 0 || !deferParents
})
}, [data, deferParents])
const renderItem = ({item, index}: {item: Slice; index: number}) => {
if (item.type === 'threadPost') {
if (item.depth < 0) {
if (deferParents) return null
return (
<ThreadPost
item={item}
@@ -275,7 +277,7 @@ export function Inner({uri}: {uri: string | undefined}) {
>
<List
ref={listRef}
data={items}
data={data?.items || []}
renderItem={renderItem}
keyExtractor={keyExtractor}
onContentSizeChange={onContentSizeChangeWebOnly}
+12 -8
View File
@@ -1,3 +1,4 @@
import {useMemo} from 'react'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -5,7 +6,7 @@ import {
createCacheMutator,
getThreadPlaceholder,
} from '#/state/queries/usePostThread/queryCache'
import {flatten, sort} from '#/state/queries/usePostThread/traversal'
import {traverse} from '#/state/queries/usePostThread/traversal'
import {
createPostThreadQueryKey,
HiddenReplyKind,
@@ -69,20 +70,23 @@ export function usePostThread({
// TODO map over pages, just like feeds
const items = flatten(
sort(query.data?.thread || [], {
const items = useMemo(() => {
return traverse(query.data?.thread || [], {
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
query.data?.threadgate?.record,
),
moderationOpts: moderationOpts!,
}),
{
hasSession,
showMuted: state.shownHiddenReplyKinds.has(HiddenReplyKind.Muted),
showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden),
view: params.view,
},
)
})
}, [
query.data,
mergeThreadgateHiddenReplies,
moderationOpts,
hasSession,
state.shownHiddenReplyKinds,
])
const mutator = createCacheMutator({
params,
+53 -80
View File
@@ -6,7 +6,6 @@ import {
import {
HiddenReplyKind,
type PostThreadParams,
type Slice,
type TraversalMetadata,
} from '#/state/queries/usePostThread/types'
@@ -18,88 +17,20 @@ import {
} from '#/state/queries/usePostThread/utils'
import * as views from '#/state/queries/usePostThread/views'
export function flatten(
sorted: ReturnType<typeof sort>,
{
hasSession,
showMuted,
showHidden,
}: {
hasSession: boolean
showMuted: boolean
showHidden: boolean
view: PostThreadParams['view']
},
) {
const flattened: Slice[] = sorted.items
for (let i = 0; i < flattened.length; i++) {
const item = flattened[i]
if (item.type === 'threadPost') {
// TODO should not insert if not found post etc
if (
item.ui.isAnchor &&
hasSession &&
!item.value.post.viewer?.replyDisabled
) {
flattened.splice(i + 1, 0, {
type: 'replyComposer',
key: 'replyComposer',
})
}
}
}
/*
* Insert hidden items and buttons to show them
*/
if (sorted.hidden.length) {
if (showHidden) {
flattened.push(...sorted.hidden)
if (sorted.muted.length) {
if (showMuted) {
flattened.push(...sorted.muted)
} else {
flattened.push({
type: 'showHiddenReplies',
key: 'showMutedReplies',
kind: HiddenReplyKind.Muted,
})
}
}
} else {
flattened.push({
type: 'showHiddenReplies',
key: 'showHiddenReplies',
kind: HiddenReplyKind.Hidden,
})
}
} else if (sorted.muted.length) {
if (showMuted) {
flattened.push(...sorted.muted)
} else {
flattened.push({
type: 'showHiddenReplies',
key: 'showMutedReplies',
kind: HiddenReplyKind.Muted,
})
}
}
return flattened
}
export function sort(
export function traverse(
thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'],
{
threadgateHiddenReplies,
moderationOpts,
hasSession,
showMuted,
showHidden,
}: {
threadgateHiddenReplies: Set<string>
moderationOpts: ModerationOpts
hasSession: boolean
showMuted: boolean
showHidden: boolean
},
) {
const items: Slice[] = []
@@ -321,6 +252,18 @@ export function sort(
const item = items[i]
if (item.type === 'threadPost') {
if (
item.depth === 0 &&
!item.value.post.viewer?.replyDisabled &&
hasSession
) {
items.splice(i + 1, 0, {
type: 'replyComposer',
key: 'replyComposer',
})
i++ // skip next iteration
}
const metadata = metadatas.get(item.uri)
if (metadata) {
@@ -429,11 +372,41 @@ export function sort(
}
}
return {
items,
hidden,
muted,
if (hidden.length) {
if (showHidden) {
items.push(...hidden)
if (muted.length) {
if (showMuted) {
items.push(...muted)
} else {
items.push({
type: 'showHiddenReplies',
key: 'showMutedReplies',
kind: HiddenReplyKind.Muted,
})
}
}
} else {
items.push({
type: 'showHiddenReplies',
key: 'showHiddenReplies',
kind: HiddenReplyKind.Hidden,
})
}
} else if (muted.length) {
if (showMuted) {
items.push(...muted)
} else {
items.push({
type: 'showHiddenReplies',
key: 'showMutedReplies',
kind: HiddenReplyKind.Muted,
})
}
}
return items
}
/**