This commit is contained in:
Eric Bailey
2025-06-05 21:09:03 -05:00
parent c71b4ecb73
commit 7205bb08de
5 changed files with 85 additions and 89 deletions
+3 -5
View File
@@ -251,8 +251,7 @@ export function Inner({uri}: {uri: string | undefined}) {
item={item} item={item}
threadgateRecord={thread.data.threadgate?.record ?? undefined} threadgateRecord={thread.data.threadgate?.record ?? undefined}
overrides={{ overrides={{
moderation: moderation: thread.state.otherItemsVisible && item.depth > 0,
thread.state.hiddenThreadItemsVisible && item.depth > 0,
}} }}
onPostSuccess={optimisticOnPostReply} onPostSuccess={optimisticOnPostReply}
/> />
@@ -263,8 +262,7 @@ export function Inner({uri}: {uri: string | undefined}) {
item={item} item={item}
threadgateRecord={thread.data.threadgate?.record ?? undefined} threadgateRecord={thread.data.threadgate?.record ?? undefined}
overrides={{ overrides={{
moderation: moderation: thread.state.otherItemsVisible && item.depth > 0,
thread.state.hiddenThreadItemsVisible && item.depth > 0,
}} }}
onPostSuccess={optimisticOnPostReply} onPostSuccess={optimisticOnPostReply}
/> />
@@ -292,7 +290,7 @@ export function Inner({uri}: {uri: string | undefined}) {
)} )}
</View> </View>
) )
} else if (item.type === 'showHiddenReplies') { } else if (item.type === 'showOtherReplies') {
return ( return (
<PostThreadShowHiddenReplies type="hidden" onPress={item.onPress} /> <PostThreadShowHiddenReplies type="hidden" onPress={item.onPress} />
) )
+45 -47
View File
@@ -14,7 +14,7 @@ import {
sortAndAnnotateThreadItems, sortAndAnnotateThreadItems,
} from '#/state/queries/usePostThread/traversal' } from '#/state/queries/usePostThread/traversal'
import { import {
createPostThreadHiddenQueryKey, createPostThreadOtherQueryKey,
createPostThreadQueryKey, createPostThreadQueryKey,
type ThreadItem, type ThreadItem,
type UsePostThreadQueryResult, type UsePostThreadQueryResult,
@@ -47,7 +47,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
view, view,
prioritizeFollowedUsers, prioritizeFollowedUsers,
}) })
const postThreadHiddenQueryKey = createPostThreadHiddenQueryKey({ const postThreadOtherQueryKey = createPostThreadOtherQueryKey({
anchor, anchor,
prioritizeFollowedUsers, prioritizeFollowedUsers,
}) })
@@ -68,15 +68,15 @@ export function usePostThread({anchor}: {anchor?: string}) {
) )
/* /*
* Initialize `ctx.meta` to track if there are hidden replies in any of * Initialize `ctx.meta` to track if we know we have additional replies
* the fetched pages of results. * we could fetch once we hit the end.
*/ */
ctx.meta = ctx.meta || { ctx.meta = ctx.meta || {
hasHiddenReplies: false, hasHiddenReplies: false,
} }
/* /*
* If we ever see hidden replies, we'll set this to true. * If we know we have additional replies, we'll set this to true.
*/ */
if (data.hasHiddenReplies) { if (data.hasHiddenReplies) {
ctx.meta.hasHiddenReplies = true ctx.meta.hasHiddenReplies = true
@@ -120,12 +120,11 @@ export function usePostThread({anchor}: {anchor?: string}) {
() => query.data?.threadgate, () => query.data?.threadgate,
[query.data?.threadgate], [query.data?.threadgate],
) )
const hasServerHiddenThreadItems = useMemo( const hasOtherThreadItems = useMemo(
() => !!query.data?.hasHiddenReplies, () => !!query.data?.hasHiddenReplies,
[query.data?.hasHiddenReplies], [query.data?.hasHiddenReplies],
) )
const [hiddenThreadItemsVisible, setHiddenThreadItemsVisible] = const [otherItemsVisible, setOtherItemsVisible] = useState(false)
useState(false)
/** /**
* Creates a mutator for the post thread cache. This is used to insert * Creates a mutator for the post thread cache. This is used to insert
@@ -136,21 +135,20 @@ export function usePostThread({anchor}: {anchor?: string}) {
createCacheMutator({ createCacheMutator({
params: {view}, params: {view},
postThreadQueryKey, postThreadQueryKey,
postThreadHiddenQueryKey, postThreadOtherQueryKey,
queryClient: qc, queryClient: qc,
}), }),
[qc, view, postThreadQueryKey, postThreadHiddenQueryKey], [qc, view, postThreadQueryKey, postThreadOtherQueryKey],
) )
/** /**
* If we have server-hidden items and the user has chosen to view them, * If we have additional items available from the server and the user has
* start loading data * chosen to view them, start loading data
*/ */
const hiddenQueryEnabled = const additionalQueryEnabled = hasOtherThreadItems && otherItemsVisible
hasServerHiddenThreadItems && hiddenThreadItemsVisible const additionalItemsQuery = useQuery({
const hiddenQuery = useQuery({ enabled: additionalQueryEnabled,
enabled: hiddenQueryEnabled, queryKey: postThreadOtherQueryKey,
queryKey: postThreadHiddenQueryKey,
async queryFn() { async queryFn() {
const {data} = await wait( const {data} = await wait(
400, 400,
@@ -162,24 +160,24 @@ export function usePostThread({anchor}: {anchor?: string}) {
return data return data
}, },
}) })
const serverHiddenThreadItems: ThreadItem[] = useMemo(() => { const serverOtherThreadItems: ThreadItem[] = useMemo(() => {
if (!hiddenQueryEnabled) return [] if (!additionalQueryEnabled) return []
if (hiddenQuery.isLoading) { if (additionalItemsQuery.isLoading) {
return Array.from({length: 2}).map((_, i) => return Array.from({length: 2}).map((_, i) =>
views.skeleton({ views.skeleton({
key: `hidden-reply-${i}`, key: `other-reply-${i}`,
item: 'reply', item: 'reply',
}), }),
) )
} else if (hiddenQuery.isError) { } else if (additionalItemsQuery.isError) {
// TODO could insert error component // TODO could insert error component
return [] return []
} else if (hiddenQuery.data?.thread) { } else if (additionalItemsQuery.data?.thread) {
const {threadItems} = sortAndAnnotateThreadItems( const {threadItems} = sortAndAnnotateThreadItems(
hiddenQuery.data.thread, additionalItemsQuery.data.thread,
{ {
view, view,
skipHiddenReplyHandling: true, skipModerationHandling: true,
threadgateHiddenReplies: mergeThreadgateHiddenReplies( threadgateHiddenReplies: mergeThreadgateHiddenReplies(
threadgate?.record, threadgate?.record,
), ),
@@ -192,40 +190,40 @@ export function usePostThread({anchor}: {anchor?: string}) {
} }
}, [ }, [
view, view,
hiddenQueryEnabled, additionalQueryEnabled,
hiddenQuery, additionalItemsQuery,
mergeThreadgateHiddenReplies, mergeThreadgateHiddenReplies,
moderationOpts, moderationOpts,
threadgate?.record, threadgate?.record,
]) ])
/** /**
* Sets the sort order for the thread and resets the hidden items * Sets the sort order for the thread and resets the additional thread items
*/ */
const setSort: typeof baseSetSort = useCallback( const setSort: typeof baseSetSort = useCallback(
nextSort => { nextSort => {
setHiddenThreadItemsVisible(false) setOtherItemsVisible(false)
baseSetSort(nextSort) baseSetSort(nextSort)
}, },
[baseSetSort, setHiddenThreadItemsVisible], [baseSetSort, setOtherItemsVisible],
) )
/** /**
* Sets the view variant for the thread and resets the hidden items * Sets the view variant for the thread and resets the additional thread items
*/ */
const setView: typeof baseSetView = useCallback( const setView: typeof baseSetView = useCallback(
nextView => { nextView => {
setHiddenThreadItemsVisible(false) setOtherItemsVisible(false)
baseSetView(nextView) baseSetView(nextView)
}, },
[baseSetView, setHiddenThreadItemsVisible], [baseSetView, setOtherItemsVisible],
) )
/* /*
* This is the main thread response, sorted into separate buckets based on * This is the main thread response, sorted into separate buckets based on
* moderation, and annotated with all UI state needed for rendering. * moderation, and annotated with all UI state needed for rendering.
*/ */
const {threadItems, hiddenThreadItems} = useMemo(() => { const {threadItems, otherThreadItems} = useMemo(() => {
return sortAndAnnotateThreadItems(thread, { return sortAndAnnotateThreadItems(thread, {
view: view, view: view,
threadgateHiddenReplies: mergeThreadgateHiddenReplies(threadgate?.record), threadgateHiddenReplies: mergeThreadgateHiddenReplies(threadgate?.record),
@@ -241,29 +239,29 @@ export function usePostThread({anchor}: {anchor?: string}) {
/* /*
* Take all three sets of thread items and combine them into a single thread, * Take all three sets of thread items and combine them into a single thread,
* along with any other thread items required for rendering e.g. "Show hidden * along with any other thread items required for rendering e.g. "Show more
* replies" or the reply composer. * replies" or the reply composer.
*/ */
const items = useMemo(() => { const items = useMemo(() => {
return buildThread({ return buildThread({
threadItems, threadItems,
hiddenThreadItems, otherThreadItems,
serverHiddenThreadItems, serverOtherThreadItems,
isLoading: query.isPlaceholderData, isLoading: query.isPlaceholderData,
hasSession, hasSession,
hasServerHiddenThreadItems, hasOtherThreadItems,
hiddenThreadItemsVisible, otherItemsVisible,
showHiddenThreadItems: () => setHiddenThreadItemsVisible(true), showOtherItems: () => setOtherItemsVisible(true),
}) })
}, [ }, [
threadItems, threadItems,
hiddenThreadItems, otherThreadItems,
serverHiddenThreadItems, serverOtherThreadItems,
query.isPlaceholderData, query.isPlaceholderData,
hasSession, hasSession,
hasServerHiddenThreadItems, hasOtherThreadItems,
hiddenThreadItemsVisible, otherItemsVisible,
setHiddenThreadItemsVisible, setOtherItemsVisible,
]) ])
return useMemo( return useMemo(
@@ -280,7 +278,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
*/ */
sort, sort,
view, view,
hiddenThreadItemsVisible, otherItemsVisible,
}, },
data: { data: {
items, items,
@@ -302,7 +300,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
[ [
query, query,
mutator.insertReplies, mutator.insertReplies,
hiddenThreadItemsVisible, otherItemsVisible,
sort, sort,
view, view,
setSort, setSort,
@@ -17,7 +17,7 @@ import {BELOW} from '#/state/queries/usePostThread/const'
import {getBranch} from '#/state/queries/usePostThread/traversal' import {getBranch} from '#/state/queries/usePostThread/traversal'
import { import {
type ApiThreadItem, type ApiThreadItem,
type createPostThreadHiddenQueryKey, type createPostThreadOtherQueryKey,
type createPostThreadQueryKey, type createPostThreadQueryKey,
type PostThreadParams, type PostThreadParams,
postThreadQueryKeyRoot, postThreadQueryKeyRoot,
@@ -30,12 +30,12 @@ import {embedViewRecordToPostView} from '#/state/queries/util'
export function createCacheMutator({ export function createCacheMutator({
queryClient, queryClient,
postThreadQueryKey, postThreadQueryKey,
postThreadHiddenQueryKey, postThreadOtherQueryKey,
params, params,
}: { }: {
queryClient: QueryClient queryClient: QueryClient
postThreadQueryKey: ReturnType<typeof createPostThreadQueryKey> postThreadQueryKey: ReturnType<typeof createPostThreadQueryKey>
postThreadHiddenQueryKey: ReturnType<typeof createPostThreadHiddenQueryKey> postThreadOtherQueryKey: ReturnType<typeof createPostThreadOtherQueryKey>
params: Pick<PostThreadParams, 'view'> params: Pick<PostThreadParams, 'view'>
}) { }) {
return { return {
@@ -60,10 +60,10 @@ export function createCacheMutator({
) )
/* /*
* Hidden threads query mutator. * Additional replies query mutator.
*/ */
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadHiddenV2.OutputSchema>( queryClient.setQueryData<AppBskyUnspeccedGetPostThreadHiddenV2.OutputSchema>(
postThreadHiddenQueryKey, postThreadOtherQueryKey,
data => { data => {
if (!data) return if (!data) return
console.log(data) console.log(data)
+29 -29
View File
@@ -20,23 +20,23 @@ export function sortAndAnnotateThreadItems(
threadgateHiddenReplies, threadgateHiddenReplies,
moderationOpts, moderationOpts,
view, view,
skipHiddenReplyHandling, skipModerationHandling,
}: { }: {
threadgateHiddenReplies: Set<string> threadgateHiddenReplies: Set<string>
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
view: PostThreadParams['view'] view: PostThreadParams['view']
/** /**
* Set to `true` in cases where we already know the moderation state of the * Set to `true` in cases where we already know the moderation state of the
* post e.g. when fetching server-hidden replies. This will prevent * post e.g. when fetching additional replies from the server. This will
* additional sorting or nested-branch truncation, and all replies, * prevent additional sorting or nested-branch truncation, and all replies,
* regardless of moderation state, will be included in the resulting * regardless of moderation state, will be included in the resulting
* `threadItems` array. * `threadItems` array.
*/ */
skipHiddenReplyHandling?: boolean skipModerationHandling?: boolean
}, },
) { ) {
const threadItems: ThreadItem[] = [] const threadItems: ThreadItem[] = []
const hiddenThreadItems: ThreadItem[] = [] const otherThreadItems: 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++) {
@@ -140,7 +140,7 @@ export function sortAndAnnotateThreadItems(
threadgateHiddenReplies, threadgateHiddenReplies,
}) })
if (!post.isBlurred || skipHiddenReplyHandling) { if (!post.isBlurred || skipModerationHandling) {
/* /*
* Not moderated, need to insert it * Not moderated, need to insert it
*/ */
@@ -163,7 +163,7 @@ export function sortAndAnnotateThreadItems(
if (parentIsTopLevelReply) { if (parentIsTopLevelReply) {
// push branch anchor into sorted array // push branch anchor into sorted array
hiddenThreadItems.push(parent) otherThreadItems.push(parent)
// skip branch anchor in branch traversal // skip branch anchor in branch traversal
const startIndex = branch.start + 1 const startIndex = branch.start + 1
@@ -199,14 +199,14 @@ export function sortAndAnnotateThreadItems(
}) })
/* /*
* If a child is hidden in any way, drop it an its sub-branch * If a child is moderated in any way, drop it an its sub-branch
* entirely. To reveal these, the user must navigate to the * entirely. To reveal these, the user must navigate to the
* parent post directly. * parent post directly.
*/ */
if (childPost.isBlurred) { if (childPost.isBlurred) {
ci = getBranch(thread, ci, child.depth).end ci = getBranch(thread, ci, child.depth).end
} else { } else {
hiddenThreadItems.push(childPost) otherThreadItems.push(childPost)
} }
} else { } else {
/* /*
@@ -228,10 +228,10 @@ export function sortAndAnnotateThreadItems(
} }
/* /*
* Both `threadItems` and `hiddenThreadItems` now need to be traversed again to fully compute * Both `threadItems` and `otherThreadItems` now need to be traversed again to fully compute
* UI state based on collected metadata. These arrays will be muted in situ. * UI state based on collected metadata. These arrays will be muted in situ.
*/ */
for (const subset of [threadItems, hiddenThreadItems]) { for (const subset of [threadItems, otherThreadItems]) {
for (let i = 0; i < subset.length; i++) { for (let i = 0; i < subset.length; i++) {
const item = subset[i] const item = subset[i]
const prevItem = subset.at(i - 1) const prevItem = subset.at(i - 1)
@@ -376,28 +376,28 @@ export function sortAndAnnotateThreadItems(
return { return {
threadItems, threadItems,
hiddenThreadItems, otherThreadItems,
} }
} }
export function buildThread({ export function buildThread({
threadItems, threadItems,
hiddenThreadItems, otherThreadItems,
serverHiddenThreadItems, serverOtherThreadItems,
isLoading, isLoading,
hasSession, hasSession,
hiddenThreadItemsVisible, otherItemsVisible,
hasServerHiddenThreadItems, hasOtherThreadItems,
showHiddenThreadItems, showOtherItems,
}: { }: {
threadItems: ThreadItem[] threadItems: ThreadItem[]
hiddenThreadItems: ThreadItem[] otherThreadItems: ThreadItem[]
serverHiddenThreadItems: ThreadItem[] serverOtherThreadItems: ThreadItem[]
isLoading: boolean isLoading: boolean
hasSession: boolean hasSession: boolean
hiddenThreadItemsVisible: boolean otherItemsVisible: boolean
hasServerHiddenThreadItems: boolean hasOtherThreadItems: boolean
showHiddenThreadItems: () => void showOtherItems: () => void
}) { }) {
/** /**
* `threadItems` is memoized here, so don't mutate it directly. * `threadItems` is memoized here, so don't mutate it directly.
@@ -466,15 +466,15 @@ export function buildThread({
} }
} }
if (hiddenThreadItems.length || hasServerHiddenThreadItems) { if (otherThreadItems.length || hasOtherThreadItems) {
if (hiddenThreadItemsVisible) { if (otherItemsVisible) {
items.push(...hiddenThreadItems) items.push(...otherThreadItems)
items.push(...serverHiddenThreadItems) items.push(...serverOtherThreadItems)
} else { } else {
items.push({ items.push({
type: 'showHiddenReplies', type: 'showOtherReplies',
key: 'showHiddenReplies', key: 'showOtherReplies',
onPress: showHiddenThreadItems, onPress: showOtherItems,
}) })
} }
} }
+3 -3
View File
@@ -17,11 +17,11 @@ export const postThreadQueryKeyRoot = 'post-thread-v2' as const
export const createPostThreadQueryKey = (props: PostThreadParams) => export const createPostThreadQueryKey = (props: PostThreadParams) =>
[postThreadQueryKeyRoot, props] as const [postThreadQueryKeyRoot, props] as const
export const createPostThreadHiddenQueryKey = ( export const createPostThreadOtherQueryKey = (
props: Omit<AppBskyUnspeccedGetPostThreadHiddenV2.QueryParams, 'anchor'> & { props: Omit<AppBskyUnspeccedGetPostThreadHiddenV2.QueryParams, 'anchor'> & {
anchor?: string anchor?: string
}, },
) => [postThreadQueryKeyRoot, 'hidden', props] as const ) => [postThreadQueryKeyRoot, 'other', props] as const
export type PostThreadParams = Pick< export type PostThreadParams = Pick<
AppBskyUnspeccedGetPostThreadV2.QueryParams, AppBskyUnspeccedGetPostThreadV2.QueryParams,
@@ -88,7 +88,7 @@ export type ThreadItem =
key: string key: string
} }
| { | {
type: 'showHiddenReplies' type: 'showOtherReplies'
key: string key: string
onPress: () => void onPress: () => void
} }