Refactor client-side hidden

This commit is contained in:
Eric Bailey
2025-05-30 13:51:54 -05:00
parent cffdfbe0be
commit 4ecaa951ee
4 changed files with 73 additions and 97 deletions
+15 -31
View File
@@ -9,11 +9,7 @@ import {ScrollProvider} from '#/lib/ScrollContext'
import {cleanError} from '#/lib/strings/errors'
import {isNative} from '#/platform/detection'
import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
import {
HiddenReplyKind,
type ThreadItem,
usePostThread,
} from '#/state/queries/usePostThread'
import {type ThreadItem, usePostThread} from '#/state/queries/usePostThread'
import {type OnPostSuccessData} from '#/state/shell/composer'
import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposePrompt'
import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShowHiddenReplies'
@@ -53,22 +49,16 @@ export function Inner({uri}: {uri: string | undefined}) {
setTreeViewEnabled,
} = useThreadPreferences()
const [shownHiddenReplyKinds, setShownHiddenReplyKinds] = useState<
Set<HiddenReplyKind>
>(new Set())
const {isFetching, error, data, refetch, insertReplies} = usePostThread({
enabled: isThreadPreferencesLoaded,
params: {
anchor: uri,
sort: sortReplies,
view: treeViewEnabled ? 'tree' : 'linear',
prioritizeFollowedUsers,
},
state: {
shownHiddenReplyKinds,
},
})
const {isFetching, error, data, refetch, insertReplies, showHiddenReplies} =
usePostThread({
enabled: isThreadPreferencesLoaded,
params: {
anchor: uri,
sort: sortReplies,
view: treeViewEnabled ? 'tree' : 'linear',
prioritizeFollowedUsers,
},
})
const optimisticOnPostReply = (data: OnPostSuccessData) => {
if (data) {
@@ -242,9 +232,7 @@ export function Inner({uri}: {uri: string | undefined}) {
item={item}
threadgateRecord={data?.threadgate?.record ?? undefined}
overrides={{
moderation:
shownHiddenReplyKinds.has(HiddenReplyKind.Hidden) &&
item.depth > 0,
moderation: showHiddenReplies && item.depth > 0,
}}
onPostSuccess={optimisticOnPostReply}
/>
@@ -255,9 +243,7 @@ export function Inner({uri}: {uri: string | undefined}) {
item={item}
threadgateRecord={data?.threadgate?.record ?? undefined}
overrides={{
moderation:
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) &&
item.depth > 0,
moderation: showHiddenReplies && item.depth > 0,
}}
onPostSuccess={optimisticOnPostReply}
/>
@@ -305,10 +291,8 @@ export function Inner({uri}: {uri: string | undefined}) {
} else if (item.type === 'showHiddenReplies') {
return (
<PostThreadShowHiddenReplies
type={item.kind === 'muted' ? 'muted' : 'hidden'}
onPress={() =>
setShownHiddenReplyKinds(kinds => new Set([...kinds, item.kind]))
}
type="hidden"
onPress={() => item.onLoad()}
/>
)
} else if (item.type === 'skeleton') {
+46 -21
View File
@@ -1,4 +1,4 @@
import {useMemo} from 'react'
import {useCallback, useMemo, useRef, useState} from 'react'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -9,7 +9,6 @@ import {
import {traverse} from '#/state/queries/usePostThread/traversal'
import {
createPostThreadQueryKey,
HiddenReplyKind,
type UsePostThreadProps,
} from '#/state/queries/usePostThread/types'
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
@@ -21,7 +20,6 @@ export * from '#/state/queries/usePostThread/types'
export function usePostThread({
enabled: isEnabled,
params,
state,
}: UsePostThreadProps) {
const qc = useQueryClient()
const agent = useAgent()
@@ -34,6 +32,9 @@ export function usePostThread({
params,
})
const hasHiddenReplies = useRef(false)
const [showHiddenReplies, setShowHiddenReplies] = useState(false)
const query = useQuery({
enabled,
queryKey,
@@ -50,10 +51,17 @@ export function usePostThread({
},
placeholderData() {
if (!params.anchor) return
const placeholder = getThreadPlaceholder(qc, params.anchor)
if (placeholder) {
return {thread: [placeholder], hasHiddenReplies: false}
}
/*
* Return empty data here so that `isPlaceholderData` is always true,
* which we'll use to insert skeletons.
*/
return {thread: [], hasHiddenReplies: false}
},
select(data) {
@@ -68,7 +76,13 @@ export function usePostThread({
},
})
// TODO map over pages, just like feeds
if (query?.data?.hasHiddenReplies) {
hasHiddenReplies.current = true
}
const loadHiddenReplies = useCallback(async () => {
setShowHiddenReplies(true)
}, [setShowHiddenReplies])
const items = useMemo(() => {
return traverse(query.data?.thread || [], {
@@ -77,25 +91,21 @@ export function usePostThread({
),
moderationOpts: moderationOpts!,
hasSession,
showMuted: state.shownHiddenReplyKinds.has(HiddenReplyKind.Muted),
showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden),
view: params.view,
hasHiddenReplies: hasHiddenReplies.current,
showHiddenReplies,
loadHiddenReplies,
})
}, [
query.data,
mergeThreadgateHiddenReplies,
moderationOpts,
hasSession,
state.shownHiddenReplyKinds,
params.view,
showHiddenReplies,
loadHiddenReplies,
])
const mutator = createCacheMutator({
params,
queryKey,
queryClient: qc,
})
if (query.isPlaceholderData) {
const anchor = items.at(0)
const skeletonReplies =
@@ -128,12 +138,27 @@ export function usePostThread({
}
}
return {
...query,
data: {
items,
threadgate: query.data?.threadgate,
},
insertReplies: mutator.insertReplies,
}
const mutator = useMemo(
() =>
createCacheMutator({
params,
queryKey,
queryClient: qc,
}),
[qc, params, queryKey],
)
return useMemo(
() => ({
...query,
data: {
items,
threadgate: query.data?.threadgate,
},
hasHiddenReplies: hasHiddenReplies.current,
showHiddenReplies,
insertReplies: mutator.insertReplies,
}),
[query, items, mutator.insertReplies, showHiddenReplies],
)
}
+11 -36
View File
@@ -5,7 +5,6 @@ import {
} from '@atproto/api'
import {
HiddenReplyKind,
type PostThreadParams,
type ThreadItem,
type TraversalMetadata,
@@ -24,21 +23,22 @@ export function traverse(
threadgateHiddenReplies,
moderationOpts,
hasSession,
showMuted,
showHidden,
view,
hasHiddenReplies,
showHiddenReplies,
loadHiddenReplies,
}: {
threadgateHiddenReplies: Set<string>
moderationOpts: ModerationOpts
hasSession: boolean
showMuted: boolean
showHidden: boolean
view: PostThreadParams['view']
hasHiddenReplies: boolean
showHiddenReplies: boolean
loadHiddenReplies: () => Promise<void>
},
) {
const items: ThreadItem[] = []
const hidden: ThreadItem[] = []
const muted: ThreadItem[] = []
const metadatas = new Map<string, TraversalMetadata>()
traversal: for (let i = 0; i < thread.length; i++) {
@@ -178,16 +178,13 @@ export function traverse(
* Moderated in some way, we're going to walk children
*/
const parent = post
const parentMod = postMod
const parentIsTopLevelReply = parent.depth === 1
const sortArray = parentMod.muted ? muted : hidden
// get sub tree
const branch = getBranch(thread, i, item.depth)
if (parentIsTopLevelReply) {
// push branch anchor into sorted array
sortArray.push(parent)
hidden.push(parent)
// skip branch anchor in branch traversal
const startIndex = branch.start + 1
@@ -238,7 +235,7 @@ export function traverse(
) {
ci = getBranch(thread, ci, child.depth).end
} else {
sortArray.push(childPost)
hidden.push(childPost)
}
} else {
/*
@@ -259,36 +256,14 @@ export function traverse(
}
}
if (hidden.length) {
if (showHidden) {
if (hidden.length || hasHiddenReplies) {
if (showHiddenReplies) {
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,
onLoad: loadHiddenReplies,
})
}
}
+1 -9
View File
@@ -22,14 +22,6 @@ export type PostThreadParams = Pick<
export type UsePostThreadProps = {
enabled?: boolean
params: PostThreadParams
state: {
shownHiddenReplyKinds: Set<HiddenReplyKind>
}
}
export enum HiddenReplyKind {
Hidden = 'hidden',
Muted = 'muted',
}
export type ThreadItem =
@@ -82,7 +74,7 @@ export type ThreadItem =
| {
type: 'showHiddenReplies'
key: string
kind: HiddenReplyKind
onLoad: () => Promise<void>
}
| {
type: 'readMore'