Rough handling of hidden/muted

This commit is contained in:
Eric Bailey
2025-05-06 18:52:46 -05:00
parent e42cd77fc0
commit df607870f0
2 changed files with 115 additions and 66 deletions
+96 -59
View File
@@ -43,7 +43,7 @@ import {
findAllProfilesInQueryData as findAllProfilesInFeedQueryData,
} from './post-feed'
export type PostThreadV2Options = {
export type PostThreadV2Params = {
view: 'tree' | 'linear'
sort: 'hotness' | 'oldest' | 'newest' | 'most-likes' | 'random' | string
prioritizeFollows: BskyThreadViewPreference['prioritizeFollowedUsers']
@@ -51,13 +51,16 @@ export type PostThreadV2Options = {
export const getPostThreadV2QueryKeyRoot = 'getPostThreadV2' as const
export const createGetPostThreadV2QueryKey = (
props: Pick<GetPostThreadV2Params, 'uri' | 'options'>,
props: Pick<GetPostThreadV2QueryProps, 'uri' | 'params'>,
) => [getPostThreadV2QueryKeyRoot, props] as const
export type GetPostThreadV2Params = {
export type GetPostThreadV2QueryProps = {
uri?: string
enabled?: boolean
options: PostThreadV2Options
params: PostThreadV2Params
state: {
shownHiddenReplyKinds: Set<HiddenReplyKind>
}
}
export type GetPostThreadV2QueryData = {
@@ -65,7 +68,7 @@ export type GetPostThreadV2QueryData = {
threadgate?: AppBskyFeedDefs.ThreadgateView
}
export function mapSortOptionsToSortID(sort: PostThreadV2Options['sort']) {
export function mapSortOptionsToSortID(sort: PostThreadV2Params['sort']) {
switch (sort) {
case 'hotness':
return APP_BSKY_FEED.GetPostThreadV2Hotness
@@ -83,8 +86,9 @@ export function mapSortOptionsToSortID(sort: PostThreadV2Options['sort']) {
export function useGetPostThreadV2({
uri,
enabled: isEnabled,
options,
}: GetPostThreadV2Params) {
params,
state,
}: GetPostThreadV2QueryProps) {
const qc = useQueryClient()
const agent = useAgent()
const {hasSession} = useSession()
@@ -97,13 +101,13 @@ export function useGetPostThreadV2({
enabled,
queryKey: createGetPostThreadV2QueryKey({
uri,
options,
params,
}),
async queryFn() {
const {data} = await agent.app.bsky.feed.getPostThreadV2({
uri: uri!,
below: 10,
sorting: mapSortOptionsToSortID(options.sort),
sorting: mapSortOptionsToSortID(params.sort),
})
return data
},
@@ -136,12 +140,12 @@ export function useGetPostThreadV2({
const filtered = filterAndSort(query.data?.thread || [], {
hasSession,
options,
params,
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
query.data?.threadgate?.record,
),
moderationOpts: moderationOpts!,
showHiddenReplies: false,
shownHiddenReplyKinds: state.shownHiddenReplyKinds,
})
return {
@@ -251,20 +255,24 @@ export function filterAndSort(
thread: AppBskyFeedGetPostThreadV2.OutputSchema['thread'],
{
hasSession,
options,
params,
threadgateHiddenReplies,
moderationOpts,
showHiddenReplies = false,
shownHiddenReplyKinds,
}: {
hasSession: boolean
options: PostThreadV2Options
params: PostThreadV2Params
threadgateHiddenReplies: Set<string>
moderationOpts: ModerationOpts
showHiddenReplies: boolean
shownHiddenReplyKinds: Set<HiddenReplyKind>
},
) {
const sorted: Slice[] = []
const slices: Slice[] = []
const hidden: Slice[] = []
const muted: Slice[] = []
const showMuted = shownHiddenReplyKinds.has(HiddenReplyKind.Muted)
const showHidden = shownHiddenReplyKinds.has(HiddenReplyKind.Hidden)
traversal: for (let i = 0; i < thread.length; i++) {
const item = thread[i]
@@ -282,13 +290,13 @@ export function filterAndSort(
*/
} else if (item.depth === 0) {
if (AppBskyFeedDefs.isThreadItemNoUnauthenticated(item)) {
sorted.push(views.noUnauthenticated({item}))
slices.push(views.noUnauthenticated({item}))
} else if (AppBskyFeedDefs.isThreadItemNotFound(item)) {
sorted.push(views.notFound({item}))
slices.push(views.notFound({item}))
} else if (AppBskyFeedDefs.isThreadItemBlocked(item)) {
sorted.push(views.blocked({item}))
slices.push(views.blocked({item}))
} else if (AppBskyFeedDefs.isThreadItemPost(item)) {
sorted.push(
slices.push(
views.post({
item,
oneUp: oneUp,
@@ -298,7 +306,7 @@ export function filterAndSort(
)
if (hasSession) {
sorted.push({
slices.push({
type: 'replyComposer',
key: 'replyComposer',
})
@@ -310,16 +318,16 @@ export function filterAndSort(
const parentOneUp = thread[pi - 1]
if (AppBskyFeedDefs.isThreadItemNoUnauthenticated(parent)) {
sorted.unshift(views.noUnauthenticated({item: parent}))
slices.unshift(views.noUnauthenticated({item: parent}))
break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemNotFound(parent)) {
sorted.unshift(views.notFound({item: parent}))
slices.unshift(views.notFound({item: parent}))
break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemBlocked(parent)) {
sorted.unshift(views.blocked({item: parent}))
slices.unshift(views.blocked({item: parent}))
break parentTraversal
} else if (AppBskyFeedDefs.isThreadItemPost(parent)) {
sorted.unshift(
slices.unshift(
views.post({
item,
oneUp: parentOneUp,
@@ -347,9 +355,18 @@ export function filterAndSort(
i = branch.end
continue traversal
} else if (AppBskyFeedDefs.isThreadItemPost(item)) {
const post = views.post({
item,
oneUp,
oneDown,
moderationOpts,
})
const modui = post.moderation.ui('contentList')
const isBlurred = modui.blur || modui.filter
const isMuted = (modui.blurs[0] || modui.filters[0])?.type === 'muted'
const isHidden = threadgateHiddenReplies.has(item.uri)
if (isHidden) {
if (isHidden || isBlurred || isMuted) {
const branch = getSubBranch(thread, i, item.depth)
/*
@@ -360,30 +377,29 @@ export function filterAndSort(
const next = thread[ci]
if (AppBskyFeedDefs.isThreadItemPost(next)) {
hidden.push(
views.post({
item: next,
oneUp: oneUp,
oneDown: oneDown,
moderationOpts,
}),
)
const post = views.post({
item: next,
oneUp: oneUp,
oneDown: oneDown,
moderationOpts,
})
if (isMuted) {
muted.push(post)
} else {
hidden.push(post)
}
} else {
break
}
}
} else if (showHiddenReplies) {
} else {
/*
* Nested hidden replies either filter entirely or show in situ
*/
sorted.push(
views.post({
item,
oneUp,
oneDown,
moderationOpts,
}),
)
if ((isMuted && showMuted) || showHidden) {
slices.push(post)
}
}
/*
@@ -395,24 +411,49 @@ export function filterAndSort(
/*
* Not hidden, so show it
*/
sorted.push(
views.post({
item,
oneUp,
oneDown,
moderationOpts,
}),
)
slices.push(post)
}
}
}
}
if (showHiddenReplies) {
return sorted.concat(hidden)
const [hiddenKind1, hiddenKind2] = Array.from(shownHiddenReplyKinds)
if (hiddenKind1 === HiddenReplyKind.Hidden) {
slices.push(...hidden)
}
if (hiddenKind1 === HiddenReplyKind.Muted) {
slices.push(...muted)
}
if (hiddenKind2 === HiddenReplyKind.Hidden) {
slices.push(...hidden)
}
if (hiddenKind2 === HiddenReplyKind.Muted) {
slices.push(...muted)
}
return sorted
if (muted.length && !showMuted) {
slices.push({
type: 'showHiddenReplies',
key: 'showMutedReplies',
kind: HiddenReplyKind.Muted,
})
}
if (hidden.length && !showHidden) {
slices.push({
type: 'showHiddenReplies',
key: 'showHiddenReplies',
kind: HiddenReplyKind.Hidden,
})
}
return slices
}
export enum HiddenReplyKind {
Hidden = 'hidden',
Muted = 'muted',
}
export type Slice =
@@ -453,11 +494,7 @@ export type Slice =
| {
type: 'showHiddenReplies'
key: string
}
| {
// TODO needed?
type: 'showMutedReplies'
key: string
kind: HiddenReplyKind
}
function getThreadgate(
+19 -7
View File
@@ -16,7 +16,7 @@ import * as Layout from '#/components/Layout'
import {useSession} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useGetPostThreadV2, Slice} from '#/state/queries/useGetPostThreadV2'
import {useGetPostThreadV2, Slice, HiddenReplyKind} from '#/state/queries/useGetPostThreadV2'
import * as Menu from '#/components/Menu'
import {Button, ButtonIcon} from '#/components/Button'
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
@@ -28,6 +28,7 @@ import {PostThreadItem} from '#/view/com/post-thread/PostThreadItem'
import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposePrompt'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useComposerControls} from '#/state/shell'
import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShowHiddenReplies'
const MAINTAIN_VISIBLE_CONTENT_POSITION = {
// We don't insert any elements before the root row while loading.
@@ -126,14 +127,20 @@ export function Inner({uri}: {uri: string | undefined}) {
setTreeViewEnabled,
} = useThreadPreferences()
const [showHiddenReplies, setShowHiddenReplies] = useState<HiddenReplyKind | null>(null)
const [shownHiddenReplyKinds, setShownHiddenReplyKinds] = useState<Set<HiddenReplyKind>>(new Set())
const {isFetching, isPlaceholderData, error, data, refetch} = useGetPostThreadV2({
uri,
enabled: isThreadPreferencesLoaded,
options: {
params: {
sort: sortReplies,
view: treeViewEnabled ? 'tree' : 'linear',
prioritizeFollows: prioritizeFollowedUsers,
},
state: {
shownHiddenReplyKinds,
}
})
const ref = useRef<ListMethods>(null)
@@ -199,11 +206,9 @@ export function Inner({uri}: {uri: string | undefined}) {
hasPrecedingItem={
item.ui.showParentReplyLine || !!item.slice.hasUnhydratedParents
} // !!hasUnrevealedParents // TODO
// overrideBlur={
// hiddenRepliesState ===
// HiddenRepliesState.ShowAndOverridePostHider &&
// item.ctx.depth > 0
// }
overrideBlur={
shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && item.slice.depth > 0
}
onPostReply={onPostReply}
hideTopBorder={index === 0} // && !item.slice.isParentLoading} // TODO
/>
@@ -215,6 +220,13 @@ export function Inner({uri}: {uri: string | undefined}) {
{gtPhone && <PostThreadComposePrompt onPressCompose={onReplyToAnchor} />}
</View>
)
} else if (item.type === 'showHiddenReplies') {
return (
<PostThreadShowHiddenReplies
type={item.kind === 'muted' ? 'muted' : 'hidden'}
onPress={() => setShownHiddenReplyKinds(kinds => new Set([...kinds, item.kind]))}
/>
)
}
return null
}