Respect labels on feeds and lists (#4818)
* Prep * Pass in optional moderation to FeedCard * Compute moderation decision, filter contentList contexts, pass into card * Let's go a different route * Filter from within search queries * Use same search query for starter packs * Filter lists from profile tabs * Cleanup * Filter from profile feeds * Moderate post embeds * Memoize * Use ScreenHider on lists * Hide both list types * Fix crash on iOS in screen hider, fix lineheight * Memoize renderItem * Reuse objects to prevent re-renders
This commit is contained in:
parent
293ac6fab2
commit
c3d8beee6d
9 changed files with 261 additions and 145 deletions
|
@ -5,6 +5,7 @@ import {
|
|||
AppBskyGraphDefs,
|
||||
AppBskyUnspeccedGetPopularFeedGenerators,
|
||||
AtUri,
|
||||
moderateFeedGenerator,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
|
@ -26,6 +27,7 @@ import {RQKEY as listQueryKey} from '#/state/queries/list'
|
|||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {router} from '#/routes'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
import {FeedDescriptor} from './post-feed'
|
||||
import {precacheResolvedUri} from './resolve-uri'
|
||||
|
||||
|
@ -207,14 +209,16 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
const limit = options?.limit || 10
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
// Make sure this doesn't invalidate unless really needed.
|
||||
const selectArgs = useMemo(
|
||||
() => ({
|
||||
hasSession,
|
||||
savedFeeds: preferences?.savedFeeds || [],
|
||||
moderationOpts,
|
||||
}),
|
||||
[hasSession, preferences?.savedFeeds],
|
||||
[hasSession, preferences?.savedFeeds, moderationOpts],
|
||||
)
|
||||
const lastPageCountRef = useRef(0)
|
||||
|
||||
|
@ -225,6 +229,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
QueryKey,
|
||||
string | undefined
|
||||
>({
|
||||
enabled: Boolean(moderationOpts),
|
||||
queryKey: createGetPopularFeedsQueryKey(options),
|
||||
queryFn: async ({pageParam}) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
|
@ -246,7 +251,11 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
(
|
||||
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
|
||||
) => {
|
||||
const {savedFeeds, hasSession: hasSessionInner} = selectArgs
|
||||
const {
|
||||
savedFeeds,
|
||||
hasSession: hasSessionInner,
|
||||
moderationOpts,
|
||||
} = selectArgs
|
||||
return {
|
||||
...data,
|
||||
pages: data.pages.map(page => {
|
||||
|
@ -264,7 +273,8 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
return f.value === feed.uri
|
||||
}),
|
||||
)
|
||||
return !alreadySaved
|
||||
const decision = moderateFeedGenerator(feed, moderationOpts!)
|
||||
return !alreadySaved && !decision.ui('contentList').filter
|
||||
}),
|
||||
}
|
||||
}),
|
||||
|
@ -304,6 +314,8 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
|||
|
||||
export function useSearchPopularFeedsMutation() {
|
||||
const agent = useAgent()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (query: string) => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
|
@ -311,24 +323,15 @@ export function useSearchPopularFeedsMutation() {
|
|||
query: query,
|
||||
})
|
||||
|
||||
return res.data.feeds
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useSearchPopularFeedsQuery({q}: {q: string}) {
|
||||
const agent = useAgent()
|
||||
return useQuery({
|
||||
queryKey: ['searchPopularFeeds', q],
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 15,
|
||||
query: q,
|
||||
})
|
||||
if (moderationOpts) {
|
||||
return res.data.feeds.filter(feed => {
|
||||
const decision = moderateFeedGenerator(feed, moderationOpts)
|
||||
return !decision.ui('contentList').filter
|
||||
})
|
||||
}
|
||||
|
||||
return res.data.feeds
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -346,17 +349,27 @@ export function usePopularFeedsSearch({
|
|||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabledInner = enabled ?? Boolean(moderationOpts)
|
||||
|
||||
return useQuery({
|
||||
enabled,
|
||||
enabled: enabledInner,
|
||||
queryKey: createPopularFeedsSearchQueryKey(query),
|
||||
queryFn: async () => {
|
||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||
limit: 10,
|
||||
limit: 15,
|
||||
query: query,
|
||||
})
|
||||
|
||||
return res.data.feeds
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
select(data) {
|
||||
return data.filter(feed => {
|
||||
const decision = moderateFeedGenerator(feed, moderationOpts!)
|
||||
return !decision.ui('contentList').filter
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {AppBskyFeedGetActorFeeds} from '@atproto/api'
|
||||
import {AppBskyFeedGetActorFeeds, moderateFeedGenerator} from '@atproto/api'
|
||||
import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
type RQPageParam = string | undefined
|
||||
|
@ -14,7 +15,8 @@ export function useProfileFeedgensQuery(
|
|||
did: string,
|
||||
opts?: {enabled?: boolean},
|
||||
) {
|
||||
const enabled = opts?.enabled !== false
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
||||
const agent = useAgent()
|
||||
return useInfiniteQuery<
|
||||
AppBskyFeedGetActorFeeds.OutputSchema,
|
||||
|
@ -38,5 +40,21 @@ export function useProfileFeedgensQuery(
|
|||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
enabled,
|
||||
select(data) {
|
||||
return {
|
||||
...data,
|
||||
pages: data.pages.map(page => {
|
||||
return {
|
||||
...page,
|
||||
feeds: page.feeds
|
||||
// filter by labels
|
||||
.filter(list => {
|
||||
const decision = moderateFeedGenerator(list, moderationOpts!)
|
||||
return !decision.ui('contentList').filter
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {AppBskyGraphGetLists} from '@atproto/api'
|
||||
import {AppBskyGraphGetLists, moderateUserList} from '@atproto/api'
|
||||
import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
type RQPageParam = string | undefined
|
||||
|
@ -10,7 +11,8 @@ const RQKEY_ROOT = 'profile-lists'
|
|||
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||
|
||||
export function useProfileListsQuery(did: string, opts?: {enabled?: boolean}) {
|
||||
const enabled = opts?.enabled !== false
|
||||
const moderationOpts = useModerationOpts()
|
||||
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
||||
const agent = useAgent()
|
||||
return useInfiniteQuery<
|
||||
AppBskyGraphGetLists.OutputSchema,
|
||||
|
@ -27,17 +29,32 @@ export function useProfileListsQuery(did: string, opts?: {enabled?: boolean}) {
|
|||
cursor: pageParam,
|
||||
})
|
||||
|
||||
// Starter packs use a reference list, which we do not want to show on profiles. At some point we could probably
|
||||
// just filter this out on the backend instead of in the client.
|
||||
return {
|
||||
...res.data,
|
||||
lists: res.data.lists.filter(
|
||||
l => l.purpose !== 'app.bsky.graph.defs#referencelist',
|
||||
),
|
||||
}
|
||||
return res.data
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
enabled,
|
||||
select(data) {
|
||||
return {
|
||||
...data,
|
||||
pages: data.pages.map(page => {
|
||||
return {
|
||||
...page,
|
||||
lists: page.lists
|
||||
/*
|
||||
* Starter packs use a reference list, which we do not want to
|
||||
* show on profiles. At some point we could probably just filter
|
||||
* this out on the backend instead of in the client.
|
||||
*/
|
||||
.filter(l => l.purpose !== 'app.bsky.graph.defs#referencelist')
|
||||
// filter by labels
|
||||
.filter(list => {
|
||||
const decision = moderateUserList(list, moderationOpts!)
|
||||
return !decision.ui('contentList').filter
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue