Clean up
This commit is contained in:
@@ -227,7 +227,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: thread.state.hiddenRepliesVisible && item.depth > 0,
|
moderation: thread.state.hiddenItemsVisible && item.depth > 0,
|
||||||
}}
|
}}
|
||||||
onPostSuccess={optimisticOnPostReply}
|
onPostSuccess={optimisticOnPostReply}
|
||||||
/>
|
/>
|
||||||
@@ -238,7 +238,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: thread.state.hiddenRepliesVisible && item.depth > 0,
|
moderation: thread.state.hiddenItemsVisible && item.depth > 0,
|
||||||
}}
|
}}
|
||||||
onPostSuccess={optimisticOnPostReply}
|
onPostSuccess={optimisticOnPostReply}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
createPostThreadHiddenQueryKey,
|
createPostThreadHiddenQueryKey,
|
||||||
createPostThreadQueryKey,
|
createPostThreadQueryKey,
|
||||||
type ThreadItem,
|
type ThreadItem,
|
||||||
|
type UsePostThreadQueryResult,
|
||||||
} from '#/state/queries/usePostThread/types'
|
} from '#/state/queries/usePostThread/types'
|
||||||
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
|
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
|
||||||
import * as views from '#/state/queries/usePostThread/views'
|
import * as views from '#/state/queries/usePostThread/views'
|
||||||
@@ -35,6 +36,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
setView: baseSetView,
|
setView: baseSetView,
|
||||||
prioritizeFollowedUsers,
|
prioritizeFollowedUsers,
|
||||||
} = useThreadPreferences()
|
} = useThreadPreferences()
|
||||||
|
|
||||||
const postThreadQueryKey = createPostThreadQueryKey({
|
const postThreadQueryKey = createPostThreadQueryKey({
|
||||||
anchor,
|
anchor,
|
||||||
sort,
|
sort,
|
||||||
@@ -42,7 +44,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
prioritizeFollowedUsers,
|
prioritizeFollowedUsers,
|
||||||
})
|
})
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery<UsePostThreadQueryResult>({
|
||||||
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
||||||
queryKey: postThreadQueryKey,
|
queryKey: postThreadQueryKey,
|
||||||
// gcTime: 0, // TODO faster if we let it cache
|
// gcTime: 0, // TODO faster if we let it cache
|
||||||
@@ -73,10 +75,18 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
ctx.meta.hasHiddenReplies = true
|
ctx.meta.hasHiddenReplies = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
...data,
|
thread: data.thread || [],
|
||||||
|
threadgate: data.threadgate,
|
||||||
hasHiddenReplies: !!ctx.meta.hasHiddenReplies,
|
hasHiddenReplies: !!ctx.meta.hasHiddenReplies,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const record = getThreadgateRecord(result.threadgate)
|
||||||
|
if (result.threadgate && record) {
|
||||||
|
result.threadgate.record = record
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as UsePostThreadQueryResult
|
||||||
},
|
},
|
||||||
placeholderData() {
|
placeholderData() {
|
||||||
if (!anchor) return
|
if (!anchor) return
|
||||||
@@ -90,39 +100,86 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
return {thread, threadgate: undefined, hasHiddenReplies: false}
|
return {thread, threadgate: undefined, hasHiddenReplies: false}
|
||||||
},
|
},
|
||||||
select(data) {
|
select(data) {
|
||||||
const threadgate = getThreadgateRecord(data.threadgate)
|
const record = getThreadgateRecord(data.threadgate)
|
||||||
return {
|
if (data.threadgate && record) {
|
||||||
...data,
|
data.threadgate.record = record
|
||||||
threadgate: {
|
|
||||||
...data.threadgate,
|
|
||||||
record: threadgate,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasServerHiddenReplies = !!query.data?.hasHiddenReplies
|
const thread = useMemo(() => query.data?.thread || [], [query.data?.thread])
|
||||||
const [hiddenRepliesVisible, setHiddenRepliesVisible] = useState(false)
|
const threadgate = useMemo(
|
||||||
|
() => query.data?.threadgate,
|
||||||
|
[query.data?.threadgate],
|
||||||
|
)
|
||||||
|
const hasServerHiddenItems = useMemo(
|
||||||
|
() => !!query.data?.hasHiddenReplies,
|
||||||
|
[query.data?.hasHiddenReplies],
|
||||||
|
)
|
||||||
|
|
||||||
|
const [hiddenItemsVisible, setHiddenItemsVisible] = useState(false)
|
||||||
const [additionalHiddenItems, setAdditionalHiddenItems] = useState<
|
const [additionalHiddenItems, setAdditionalHiddenItems] = useState<
|
||||||
ThreadItem[]
|
ThreadItem[]
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sort order for the thread and resets the hidden items
|
||||||
|
*/
|
||||||
|
const setSort: typeof baseSetSort = useCallback(
|
||||||
|
nextSort => {
|
||||||
|
setHiddenItemsVisible(false)
|
||||||
|
setAdditionalHiddenItems([])
|
||||||
|
baseSetSort(nextSort)
|
||||||
|
},
|
||||||
|
[baseSetSort, setAdditionalHiddenItems, setHiddenItemsVisible],
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the view variant for the thread and resets the hidden items
|
||||||
|
*/
|
||||||
|
const setView: typeof baseSetView = useCallback(
|
||||||
|
nextView => {
|
||||||
|
setHiddenItemsVisible(false)
|
||||||
|
setAdditionalHiddenItems([])
|
||||||
|
baseSetView(nextView)
|
||||||
|
},
|
||||||
|
[baseSetView, setAdditionalHiddenItems, setHiddenItemsVisible],
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mutator for the post thread cache. This is used to insert
|
||||||
|
* replies into the thread cache after posting.
|
||||||
|
*/
|
||||||
|
const mutator = useMemo(
|
||||||
|
() =>
|
||||||
|
createCacheMutator({
|
||||||
|
params: {
|
||||||
|
sort,
|
||||||
|
view,
|
||||||
|
},
|
||||||
|
queryKey: postThreadQueryKey,
|
||||||
|
queryClient: qc,
|
||||||
|
}),
|
||||||
|
[qc, sort, view, postThreadQueryKey],
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads hidden replies for this thread. Any replies that are moderated from
|
* Loads hidden replies for this thread. Any replies that are moderated from
|
||||||
* the initial visible response(s) are shown immediately. Remote data is
|
* the initial visible response(s) are shown immediately. Remote data is
|
||||||
* fetched and inserted when it's available.
|
* fetched and inserted when it's available.
|
||||||
*/
|
*/
|
||||||
const loadHiddenReplies = useCallback(async () => {
|
const loadServerHiddenItems = useCallback(async () => {
|
||||||
/*
|
/*
|
||||||
* Show any moderated replies already in memory that were handled here on
|
* Show any moderated replies already in memory that were handled here on
|
||||||
* the client. If there are server-hidden replies, we'll fetch those next.
|
* the client. If there are server-hidden replies, we'll fetch those next.
|
||||||
*/
|
*/
|
||||||
setHiddenRepliesVisible(true)
|
setHiddenItemsVisible(true)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there are no server hidden replies, just stop here.
|
* If there are no server hidden replies, just stop here.
|
||||||
*/
|
*/
|
||||||
if (!hasServerHiddenReplies) return
|
if (!hasServerHiddenItems) return
|
||||||
|
|
||||||
setAdditionalHiddenItems(
|
setAdditionalHiddenItems(
|
||||||
Array.from({length: 2}).map((_, i) =>
|
Array.from({length: 2}).map((_, i) =>
|
||||||
@@ -153,9 +210,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
const {items} = traverse(data || [], {
|
const {items} = traverse(data || [], {
|
||||||
view,
|
view,
|
||||||
skipHiddenReplyHandling: true,
|
skipHiddenReplyHandling: true,
|
||||||
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
|
threadgateHiddenReplies: mergeThreadgateHiddenReplies(threadgate?.record),
|
||||||
query.data?.threadgate?.record,
|
|
||||||
),
|
|
||||||
moderationOpts: moderationOpts!,
|
moderationOpts: moderationOpts!,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -169,49 +224,52 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
prioritizeFollowedUsers,
|
prioritizeFollowedUsers,
|
||||||
mergeThreadgateHiddenReplies,
|
mergeThreadgateHiddenReplies,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
query.data?.threadgate?.record,
|
threadgate?.record,
|
||||||
hasServerHiddenReplies,
|
hasServerHiddenItems,
|
||||||
setHiddenRepliesVisible,
|
setHiddenItemsVisible,
|
||||||
])
|
])
|
||||||
|
|
||||||
const combined = useMemo(() => {
|
/**
|
||||||
const traversal = traverse(query.data?.thread || [], {
|
* Builds the full set of thread items, minus any server-hidden replies.
|
||||||
|
*/
|
||||||
|
const threadItems = useMemo(() => {
|
||||||
|
const traversal = traverse(thread, {
|
||||||
view: view,
|
view: view,
|
||||||
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
|
threadgateHiddenReplies: mergeThreadgateHiddenReplies(threadgate?.record),
|
||||||
query.data?.threadgate?.record,
|
|
||||||
),
|
|
||||||
moderationOpts: moderationOpts!,
|
moderationOpts: moderationOpts!,
|
||||||
})
|
})
|
||||||
return combine(traversal, {
|
return combine(traversal, {
|
||||||
hasSession,
|
hasSession,
|
||||||
hasServerHiddenReplies,
|
hasServerHiddenItems,
|
||||||
hiddenRepliesVisible,
|
hiddenItemsVisible,
|
||||||
loadHiddenReplies,
|
loadServerHiddenItems,
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
query.data,
|
thread,
|
||||||
|
threadgate?.record,
|
||||||
mergeThreadgateHiddenReplies,
|
mergeThreadgateHiddenReplies,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
hasSession,
|
hasSession,
|
||||||
view,
|
view,
|
||||||
hasServerHiddenReplies,
|
hasServerHiddenItems,
|
||||||
hiddenRepliesVisible,
|
hiddenItemsVisible,
|
||||||
loadHiddenReplies,
|
loadServerHiddenItems,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the final thread items based on load state and the availability
|
||||||
|
* of server-hidden replies.
|
||||||
|
*/
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
return combined.concat(additionalHiddenItems)
|
|
||||||
}, [combined, additionalHiddenItems])
|
|
||||||
|
|
||||||
if (query.isPlaceholderData) {
|
if (query.isPlaceholderData) {
|
||||||
const anchorPost = items.at(0)
|
const anchorPost = threadItems.at(0)
|
||||||
const skeletonReplies =
|
const skeletonReplies =
|
||||||
anchorPost && anchorPost.type === 'threadPost'
|
anchorPost && anchorPost.type === 'threadPost'
|
||||||
? anchorPost?.value.post.replyCount ?? 4
|
? anchorPost?.value.post.replyCount ?? 4
|
||||||
: 4
|
: 4
|
||||||
|
|
||||||
if (!items.length) {
|
if (!threadItems.length) {
|
||||||
items.push(
|
threadItems.push(
|
||||||
views.skeleton({
|
views.skeleton({
|
||||||
key: anchor!,
|
key: anchor!,
|
||||||
item: 'anchor',
|
item: 'anchor',
|
||||||
@@ -219,7 +277,7 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (hasSession) {
|
if (hasSession) {
|
||||||
items.push(
|
threadItems.push(
|
||||||
views.skeleton({
|
views.skeleton({
|
||||||
key: 'replyComposer',
|
key: 'replyComposer',
|
||||||
item: 'replyComposer',
|
item: 'replyComposer',
|
||||||
@@ -229,63 +287,55 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < skeletonReplies; i++) {
|
for (let i = 0; i < skeletonReplies; i++) {
|
||||||
items.push(
|
threadItems.push(
|
||||||
views.skeleton({
|
views.skeleton({
|
||||||
key: `${anchor!}-reply-${i}`,
|
key: `${anchor!}-reply-${i}`,
|
||||||
item: 'reply',
|
item: 'reply',
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return threadItems
|
||||||
|
} else {
|
||||||
|
return threadItems.concat(additionalHiddenItems)
|
||||||
}
|
}
|
||||||
|
}, [
|
||||||
const mutator = useMemo(
|
query.isPlaceholderData,
|
||||||
() =>
|
threadItems,
|
||||||
createCacheMutator({
|
additionalHiddenItems,
|
||||||
params: {
|
anchor,
|
||||||
sort,
|
hasSession,
|
||||||
view,
|
])
|
||||||
},
|
|
||||||
queryKey: postThreadQueryKey,
|
|
||||||
queryClient: qc,
|
|
||||||
}),
|
|
||||||
[qc, sort, view, postThreadQueryKey],
|
|
||||||
)
|
|
||||||
|
|
||||||
const setSort: typeof baseSetSort = useCallback(
|
|
||||||
nextSort => {
|
|
||||||
setHiddenRepliesVisible(false)
|
|
||||||
setAdditionalHiddenItems([])
|
|
||||||
baseSetSort(nextSort)
|
|
||||||
},
|
|
||||||
[baseSetSort, setAdditionalHiddenItems, setHiddenRepliesVisible],
|
|
||||||
)
|
|
||||||
|
|
||||||
const setView: typeof baseSetView = useCallback(
|
|
||||||
nextView => {
|
|
||||||
setHiddenRepliesVisible(false)
|
|
||||||
setAdditionalHiddenItems([])
|
|
||||||
baseSetView(nextView)
|
|
||||||
},
|
|
||||||
[baseSetView, setAdditionalHiddenItems, setHiddenRepliesVisible],
|
|
||||||
)
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
state: {
|
state: {
|
||||||
|
/*
|
||||||
|
* Copy in any query state that is useful
|
||||||
|
*/
|
||||||
isFetching: query.isFetching,
|
isFetching: query.isFetching,
|
||||||
isPlaceholderData: query.isPlaceholderData,
|
isPlaceholderData: query.isPlaceholderData,
|
||||||
error: query.error,
|
error: query.error,
|
||||||
hiddenRepliesVisible,
|
/*
|
||||||
|
* Other state
|
||||||
|
*/
|
||||||
sort,
|
sort,
|
||||||
view,
|
view,
|
||||||
|
hiddenItemsVisible,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
items: items || [],
|
items,
|
||||||
threadgate: query.data?.threadgate,
|
threadgate,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
/*
|
||||||
|
* Copy in any query actions that are useful
|
||||||
|
*/
|
||||||
insertReplies: mutator.insertReplies,
|
insertReplies: mutator.insertReplies,
|
||||||
refetch: query.refetch,
|
refetch: query.refetch,
|
||||||
|
/*
|
||||||
|
* Other actions
|
||||||
|
*/
|
||||||
setSort,
|
setSort,
|
||||||
setView,
|
setView,
|
||||||
},
|
},
|
||||||
@@ -294,11 +344,12 @@ export function usePostThread({anchor}: {anchor?: string}) {
|
|||||||
query,
|
query,
|
||||||
items,
|
items,
|
||||||
mutator.insertReplies,
|
mutator.insertReplies,
|
||||||
hiddenRepliesVisible,
|
hiddenItemsVisible,
|
||||||
sort,
|
sort,
|
||||||
view,
|
view,
|
||||||
setSort,
|
setSort,
|
||||||
setView,
|
setView,
|
||||||
|
threadgate,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api'
|
||||||
AppBskyUnspeccedDefs,
|
|
||||||
type ModerationDecision,
|
|
||||||
type ModerationOpts,
|
|
||||||
} from '@atproto/api'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ApiThreadItem,
|
type ApiThreadItem,
|
||||||
@@ -376,14 +372,14 @@ export function combine(
|
|||||||
{items, hidden}: {items: ThreadItem[]; hidden: ThreadItem[]},
|
{items, hidden}: {items: ThreadItem[]; hidden: ThreadItem[]},
|
||||||
{
|
{
|
||||||
hasSession,
|
hasSession,
|
||||||
hiddenRepliesVisible,
|
hiddenItemsVisible,
|
||||||
hasServerHiddenReplies,
|
hasServerHiddenItems,
|
||||||
loadHiddenReplies,
|
loadServerHiddenItems,
|
||||||
}: {
|
}: {
|
||||||
hasSession: boolean
|
hasSession: boolean
|
||||||
hiddenRepliesVisible: boolean
|
hiddenItemsVisible: boolean
|
||||||
hasServerHiddenReplies: boolean
|
hasServerHiddenItems: boolean
|
||||||
loadHiddenReplies: () => Promise<void>
|
loadServerHiddenItems: () => Promise<void>
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
@@ -402,14 +398,14 @@ export function combine(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hidden.length || hasServerHiddenReplies) {
|
if (hidden.length || hasServerHiddenItems) {
|
||||||
if (hiddenRepliesVisible) {
|
if (hiddenItemsVisible) {
|
||||||
return items.concat(hidden)
|
return items.concat(hidden)
|
||||||
} else {
|
} else {
|
||||||
return items.concat({
|
return items.concat({
|
||||||
type: 'showHiddenReplies',
|
type: 'showHiddenReplies',
|
||||||
key: 'showHiddenReplies',
|
key: 'showHiddenReplies',
|
||||||
onLoad: loadHiddenReplies,
|
onLoad: loadServerHiddenItems,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,14 +452,3 @@ export function getBranch(
|
|||||||
length: end - branchStartIndex,
|
length: end - branchStartIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModerationState(moderation: ModerationDecision) {
|
|
||||||
const modui = moderation.ui('contentList')
|
|
||||||
const blurred = modui.blur || modui.filter
|
|
||||||
const muted = (modui.blurs[0] || modui.filters[0])?.type === 'muted'
|
|
||||||
return {
|
|
||||||
blurred,
|
|
||||||
muted,
|
|
||||||
modui,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
|
type AppBskyFeedThreadgate,
|
||||||
type AppBskyUnspeccedDefs,
|
type AppBskyUnspeccedDefs,
|
||||||
type AppBskyUnspeccedGetPostThreadHiddenV2,
|
type AppBskyUnspeccedGetPostThreadHiddenV2,
|
||||||
type AppBskyUnspeccedGetPostThreadV2,
|
type AppBskyUnspeccedGetPostThreadV2,
|
||||||
@@ -29,6 +30,14 @@ export type PostThreadParams = Pick<
|
|||||||
view: 'tree' | 'linear'
|
view: 'tree' | 'linear'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UsePostThreadQueryResult = {
|
||||||
|
hasHiddenReplies: boolean
|
||||||
|
thread: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
|
||||||
|
threadgate?: Omit<AppBskyFeedDefs.ThreadgateView, 'record'> & {
|
||||||
|
record: AppBskyFeedThreadgate.Record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type ThreadItem =
|
export type ThreadItem =
|
||||||
| {
|
| {
|
||||||
type: 'threadPost'
|
type: 'threadPost'
|
||||||
|
|||||||
Reference in New Issue
Block a user