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) // distance from top of the list (screen)
const anchorOffsetTop = anchorElement.getBoundingClientRect().top const anchorOffsetTop = anchorElement.getBoundingClientRect().top
const headerHeight = headerElement.getBoundingClientRect().height const headerHeight = headerElement.getBoundingClientRect().height
// don't scroll past 0 const scrollPosition = anchorOffsetTop - headerHeight
const scrollPosition = Math.max(0, anchorOffsetTop - headerHeight) /*
listRef.current?.scrollToOffset({ * If scroll position is negative, it means the anchor post is above the
animated: false, * top of the screen, meaning the user scrolled the list. In that case,
offset: scrollPosition, * 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. * scroll in onContentSizeChange instead.
*/ */
const [deferParents, setDeferParents] = useState(isNative) 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}) => { const renderItem = ({item, index}: {item: Slice; index: number}) => {
if (item.type === 'threadPost') { if (item.type === 'threadPost') {
if (item.depth < 0) { if (item.depth < 0) {
if (deferParents) return null
return ( return (
<ThreadPost <ThreadPost
item={item} item={item}
@@ -275,7 +277,7 @@ export function Inner({uri}: {uri: string | undefined}) {
> >
<List <List
ref={listRef} ref={listRef}
data={items} data={data?.items || []}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
onContentSizeChange={onContentSizeChangeWebOnly} onContentSizeChange={onContentSizeChangeWebOnly}
+12 -8
View File
@@ -1,3 +1,4 @@
import {useMemo} from 'react'
import {useQuery, useQueryClient} from '@tanstack/react-query' import {useQuery, useQueryClient} from '@tanstack/react-query'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -5,7 +6,7 @@ import {
createCacheMutator, createCacheMutator,
getThreadPlaceholder, getThreadPlaceholder,
} from '#/state/queries/usePostThread/queryCache' } from '#/state/queries/usePostThread/queryCache'
import {flatten, sort} from '#/state/queries/usePostThread/traversal' import {traverse} from '#/state/queries/usePostThread/traversal'
import { import {
createPostThreadQueryKey, createPostThreadQueryKey,
HiddenReplyKind, HiddenReplyKind,
@@ -69,20 +70,23 @@ export function usePostThread({
// TODO map over pages, just like feeds // TODO map over pages, just like feeds
const items = flatten( const items = useMemo(() => {
sort(query.data?.thread || [], { return traverse(query.data?.thread || [], {
threadgateHiddenReplies: mergeThreadgateHiddenReplies( threadgateHiddenReplies: mergeThreadgateHiddenReplies(
query.data?.threadgate?.record, query.data?.threadgate?.record,
), ),
moderationOpts: moderationOpts!, moderationOpts: moderationOpts!,
}),
{
hasSession, hasSession,
showMuted: state.shownHiddenReplyKinds.has(HiddenReplyKind.Muted), showMuted: state.shownHiddenReplyKinds.has(HiddenReplyKind.Muted),
showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden), showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden),
view: params.view, })
}, }, [
) query.data,
mergeThreadgateHiddenReplies,
moderationOpts,
hasSession,
state.shownHiddenReplyKinds,
])
const mutator = createCacheMutator({ const mutator = createCacheMutator({
params, params,
+53 -80
View File
@@ -6,7 +6,6 @@ import {
import { import {
HiddenReplyKind, HiddenReplyKind,
type PostThreadParams,
type Slice, type Slice,
type TraversalMetadata, type TraversalMetadata,
} from '#/state/queries/usePostThread/types' } from '#/state/queries/usePostThread/types'
@@ -18,88 +17,20 @@ import {
} 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'
export function flatten( export function traverse(
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(
thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'], thread: AppBskyUnspeccedGetPostThreadV2.OutputSchema['thread'],
{ {
threadgateHiddenReplies, threadgateHiddenReplies,
moderationOpts, moderationOpts,
hasSession,
showMuted,
showHidden,
}: { }: {
threadgateHiddenReplies: Set<string> threadgateHiddenReplies: Set<string>
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hasSession: boolean
showMuted: boolean
showHidden: boolean
}, },
) { ) {
const items: Slice[] = [] const items: Slice[] = []
@@ -321,6 +252,18 @@ export function sort(
const item = items[i] const item = items[i]
if (item.type === 'threadPost') { 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) const metadata = metadatas.get(item.uri)
if (metadata) { if (metadata) {
@@ -429,11 +372,41 @@ export function sort(
} }
} }
return { if (hidden.length) {
items, if (showHidden) {
hidden, items.push(...hidden)
muted,
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
} }
/** /**