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