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-renderszio/stable
parent
293ac6fab2
commit
c3d8beee6d
|
@ -14,7 +14,7 @@ import {useModerationCauseDescription} from '#/lib/moderation/useModerationCause
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {
|
import {
|
||||||
ModerationDetailsDialog,
|
ModerationDetailsDialog,
|
||||||
|
@ -105,6 +105,7 @@ export function ScreenHider({
|
||||||
a.mb_md,
|
a.mb_md,
|
||||||
a.px_lg,
|
a.px_lg,
|
||||||
a.text_center,
|
a.text_center,
|
||||||
|
a.leading_snug,
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
]}>
|
]}>
|
||||||
{isNoPwi ? (
|
{isNoPwi ? (
|
||||||
|
@ -113,8 +114,15 @@ export function ScreenHider({
|
||||||
</Trans>
|
</Trans>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Trans>This {screenDescription} has been flagged:</Trans>
|
<Trans>This {screenDescription} has been flagged:</Trans>{' '}
|
||||||
<Text style={[a.text_lg, a.font_semibold, t.atoms.text, a.ml_xs]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_lg,
|
||||||
|
a.font_semibold,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text,
|
||||||
|
a.ml_xs,
|
||||||
|
]}>
|
||||||
{desc.name}.{' '}
|
{desc.name}.{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
|
@ -127,16 +135,17 @@ export function ScreenHider({
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_lg,
|
a.text_lg,
|
||||||
|
a.leading_snug,
|
||||||
{
|
{
|
||||||
color: t.palette.primary_500,
|
color: t.palette.primary_500,
|
||||||
// @ts-ignore web only -prf
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
},
|
||||||
|
web({
|
||||||
|
cursor: 'pointer',
|
||||||
|
}),
|
||||||
]}>
|
]}>
|
||||||
<Trans>Learn More</Trans>
|
<Trans>Learn More</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
|
||||||
<ModerationDetailsDialog control={control} modcause={blur} />
|
<ModerationDetailsDialog control={control} modcause={blur} />
|
||||||
</>
|
</>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {useA11y} from '#/state/a11y'
|
||||||
import {DISCOVER_FEED_URI} from 'lib/constants'
|
import {DISCOVER_FEED_URI} from 'lib/constants'
|
||||||
import {
|
import {
|
||||||
useGetPopularFeedsQuery,
|
useGetPopularFeedsQuery,
|
||||||
|
usePopularFeedsSearch,
|
||||||
useSavedFeeds,
|
useSavedFeeds,
|
||||||
useSearchPopularFeedsQuery,
|
|
||||||
} from 'state/queries/feed'
|
} from 'state/queries/feed'
|
||||||
import {SearchInput} from 'view/com/util/forms/SearchInput'
|
import {SearchInput} from 'view/com/util/forms/SearchInput'
|
||||||
import {List} from 'view/com/util/List'
|
import {List} from 'view/com/util/List'
|
||||||
|
@ -59,7 +59,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const {data: searchedFeeds, isFetching: isFetchingSearchedFeeds} =
|
const {data: searchedFeeds, isFetching: isFetchingSearchedFeeds} =
|
||||||
useSearchPopularFeedsQuery({q: throttledQuery})
|
usePopularFeedsSearch({query: throttledQuery})
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
!isFetchedSavedFeeds || isLoadingPopularFeeds || isFetchingSearchedFeeds
|
!isFetchedSavedFeeds || isLoadingPopularFeeds || isFetchingSearchedFeeds
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
AppBskyGraphDefs,
|
AppBskyGraphDefs,
|
||||||
AppBskyUnspeccedGetPopularFeedGenerators,
|
AppBskyUnspeccedGetPopularFeedGenerators,
|
||||||
AtUri,
|
AtUri,
|
||||||
|
moderateFeedGenerator,
|
||||||
RichText,
|
RichText,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
|
@ -26,6 +27,7 @@ import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {router} from '#/routes'
|
import {router} from '#/routes'
|
||||||
|
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||||
import {FeedDescriptor} from './post-feed'
|
import {FeedDescriptor} from './post-feed'
|
||||||
import {precacheResolvedUri} from './resolve-uri'
|
import {precacheResolvedUri} from './resolve-uri'
|
||||||
|
|
||||||
|
@ -207,14 +209,16 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||||
const limit = options?.limit || 10
|
const limit = options?.limit || 10
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
// Make sure this doesn't invalidate unless really needed.
|
// Make sure this doesn't invalidate unless really needed.
|
||||||
const selectArgs = useMemo(
|
const selectArgs = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
hasSession,
|
hasSession,
|
||||||
savedFeeds: preferences?.savedFeeds || [],
|
savedFeeds: preferences?.savedFeeds || [],
|
||||||
|
moderationOpts,
|
||||||
}),
|
}),
|
||||||
[hasSession, preferences?.savedFeeds],
|
[hasSession, preferences?.savedFeeds, moderationOpts],
|
||||||
)
|
)
|
||||||
const lastPageCountRef = useRef(0)
|
const lastPageCountRef = useRef(0)
|
||||||
|
|
||||||
|
@ -225,6 +229,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||||
QueryKey,
|
QueryKey,
|
||||||
string | undefined
|
string | undefined
|
||||||
>({
|
>({
|
||||||
|
enabled: Boolean(moderationOpts),
|
||||||
queryKey: createGetPopularFeedsQueryKey(options),
|
queryKey: createGetPopularFeedsQueryKey(options),
|
||||||
queryFn: async ({pageParam}) => {
|
queryFn: async ({pageParam}) => {
|
||||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||||
|
@ -246,7 +251,11 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||||
(
|
(
|
||||||
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
|
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
|
||||||
) => {
|
) => {
|
||||||
const {savedFeeds, hasSession: hasSessionInner} = selectArgs
|
const {
|
||||||
|
savedFeeds,
|
||||||
|
hasSession: hasSessionInner,
|
||||||
|
moderationOpts,
|
||||||
|
} = selectArgs
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
pages: data.pages.map(page => {
|
pages: data.pages.map(page => {
|
||||||
|
@ -264,7 +273,8 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
|
||||||
return f.value === feed.uri
|
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() {
|
export function useSearchPopularFeedsMutation() {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (query: string) => {
|
mutationFn: async (query: string) => {
|
||||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||||
|
@ -311,24 +323,15 @@ export function useSearchPopularFeedsMutation() {
|
||||||
query: query,
|
query: query,
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.data.feeds
|
if (moderationOpts) {
|
||||||
},
|
return res.data.feeds.filter(feed => {
|
||||||
})
|
const decision = moderateFeedGenerator(feed, moderationOpts)
|
||||||
}
|
return !decision.ui('contentList').filter
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return res.data.feeds
|
return res.data.feeds
|
||||||
},
|
},
|
||||||
placeholderData: keepPreviousData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,17 +349,27 @@ export function usePopularFeedsSearch({
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
const enabledInner = enabled ?? Boolean(moderationOpts)
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled,
|
enabled: enabledInner,
|
||||||
queryKey: createPopularFeedsSearchQueryKey(query),
|
queryKey: createPopularFeedsSearchQueryKey(query),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||||
limit: 10,
|
limit: 15,
|
||||||
query: query,
|
query: query,
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.data.feeds
|
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 {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
type RQPageParam = string | undefined
|
type RQPageParam = string | undefined
|
||||||
|
@ -14,7 +15,8 @@ export function useProfileFeedgensQuery(
|
||||||
did: string,
|
did: string,
|
||||||
opts?: {enabled?: boolean},
|
opts?: {enabled?: boolean},
|
||||||
) {
|
) {
|
||||||
const enabled = opts?.enabled !== false
|
const moderationOpts = useModerationOpts()
|
||||||
|
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery<
|
||||||
AppBskyFeedGetActorFeeds.OutputSchema,
|
AppBskyFeedGetActorFeeds.OutputSchema,
|
||||||
|
@ -38,5 +40,21 @@ export function useProfileFeedgensQuery(
|
||||||
initialPageParam: undefined,
|
initialPageParam: undefined,
|
||||||
getNextPageParam: lastPage => lastPage.cursor,
|
getNextPageParam: lastPage => lastPage.cursor,
|
||||||
enabled,
|
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 {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||||
|
|
||||||
const PAGE_SIZE = 30
|
const PAGE_SIZE = 30
|
||||||
type RQPageParam = string | undefined
|
type RQPageParam = string | undefined
|
||||||
|
@ -10,7 +11,8 @@ const RQKEY_ROOT = 'profile-lists'
|
||||||
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||||
|
|
||||||
export function useProfileListsQuery(did: string, opts?: {enabled?: boolean}) {
|
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()
|
const agent = useAgent()
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery<
|
||||||
AppBskyGraphGetLists.OutputSchema,
|
AppBskyGraphGetLists.OutputSchema,
|
||||||
|
@ -27,17 +29,32 @@ export function useProfileListsQuery(did: string, opts?: {enabled?: boolean}) {
|
||||||
cursor: pageParam,
|
cursor: pageParam,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Starter packs use a reference list, which we do not want to show on profiles. At some point we could probably
|
return res.data
|
||||||
// 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',
|
|
||||||
),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
initialPageParam: undefined,
|
initialPageParam: undefined,
|
||||||
getNextPageParam: lastPage => lastPage.cursor,
|
getNextPageParam: lastPage => lastPage.cursor,
|
||||||
enabled,
|
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
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,8 @@ export const ProfileFeedgens = React.forwardRef<
|
||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
const renderItem = ({item, index}: ListRenderItemInfo<any>) => {
|
const renderItem = React.useCallback(
|
||||||
|
({item, index}: ListRenderItemInfo<any>) => {
|
||||||
if (item === EMPTY) {
|
if (item === EMPTY) {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
@ -168,7 +169,9 @@ export const ProfileFeedgens = React.forwardRef<
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
},
|
||||||
|
[_, t, error, refetch, onPressRetryLoadMore, preferences],
|
||||||
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (enabled && scrollElRef.current) {
|
if (enabled && scrollElRef.current) {
|
||||||
|
|
|
@ -75,12 +75,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
||||||
items = items.concat([EMPTY])
|
items = items.concat([EMPTY])
|
||||||
} else if (data?.pages) {
|
} else if (data?.pages) {
|
||||||
for (const page of data?.pages) {
|
for (const page of data?.pages) {
|
||||||
items = items.concat(
|
items = items.concat(page.lists)
|
||||||
page.lists.map(l => ({
|
|
||||||
...l,
|
|
||||||
_reactKey: l.uri,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isError && !isEmpty) {
|
if (isError && !isEmpty) {
|
||||||
|
@ -192,7 +187,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
keyExtractor={(item: any) => item._reactKey}
|
keyExtractor={(item: any) => item._reactKey || item.uri}
|
||||||
renderItem={renderItemInner}
|
renderItem={renderItemInner}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
|
|
|
@ -15,11 +15,14 @@ import {
|
||||||
AppBskyEmbedRecordWithMedia,
|
AppBskyEmbedRecordWithMedia,
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyGraphDefs,
|
AppBskyGraphDefs,
|
||||||
|
moderateFeedGenerator,
|
||||||
|
moderateUserList,
|
||||||
ModerationDecision,
|
ModerationDecision,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
|
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
@ -51,7 +54,6 @@ export function PostEmbeds({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
allowNestedQuotes?: boolean
|
allowNestedQuotes?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
|
||||||
const {openLightbox} = useLightboxControls()
|
const {openLightbox} = useLightboxControls()
|
||||||
const largeAltBadge = useLargeAltBadgeEnabled()
|
const largeAltBadge = useLargeAltBadgeEnabled()
|
||||||
|
|
||||||
|
@ -72,22 +74,13 @@ export function PostEmbeds({
|
||||||
|
|
||||||
if (AppBskyEmbedRecord.isView(embed)) {
|
if (AppBskyEmbedRecord.isView(embed)) {
|
||||||
// custom feed embed (i.e. generator view)
|
// custom feed embed (i.e. generator view)
|
||||||
// =
|
|
||||||
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
|
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
|
||||||
// TODO moderation
|
return <MaybeFeedCard view={embed.record} />
|
||||||
return (
|
|
||||||
<FeedSourceCard
|
|
||||||
feedUri={embed.record.uri}
|
|
||||||
style={[pal.view, pal.border, styles.customFeedOuter]}
|
|
||||||
showLikes
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// list embed
|
// list embed
|
||||||
if (AppBskyGraphDefs.isListView(embed.record)) {
|
if (AppBskyGraphDefs.isListView(embed.record)) {
|
||||||
// TODO moderation
|
return <MaybeListCard view={embed.record} />
|
||||||
return <ListEmbed item={embed.record} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) {
|
if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) {
|
||||||
|
@ -185,6 +178,39 @@ export function PostEmbeds({
|
||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MaybeFeedCard({view}: {view: AppBskyFeedDefs.GeneratorView}) {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
const moderation = React.useMemo(() => {
|
||||||
|
return moderationOpts
|
||||||
|
? moderateFeedGenerator(view, moderationOpts)
|
||||||
|
: undefined
|
||||||
|
}, [view, moderationOpts])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentHider modui={moderation?.ui('contentList')}>
|
||||||
|
<FeedSourceCard
|
||||||
|
feedUri={view.uri}
|
||||||
|
style={[pal.view, pal.border, styles.customFeedOuter]}
|
||||||
|
showLikes
|
||||||
|
/>
|
||||||
|
</ContentHider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MaybeListCard({view}: {view: AppBskyGraphDefs.ListView}) {
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
const moderation = React.useMemo(() => {
|
||||||
|
return moderationOpts ? moderateUserList(view, moderationOpts) : undefined
|
||||||
|
}, [view, moderationOpts])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentHider modui={moderation?.ui('contentList')}>
|
||||||
|
<ListEmbed item={view} />
|
||||||
|
</ContentHider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import React, {useCallback, useMemo} from 'react'
|
import React, {useCallback, useMemo} from 'react'
|
||||||
import {Pressable, StyleSheet, View} from 'react-native'
|
import {Pressable, StyleSheet, View} from 'react-native'
|
||||||
import {AppBskyGraphDefs, AtUri, RichText as RichTextAPI} from '@atproto/api'
|
import {
|
||||||
|
AppBskyGraphDefs,
|
||||||
|
AtUri,
|
||||||
|
moderateUserList,
|
||||||
|
ModerationOpts,
|
||||||
|
RichText as RichTextAPI,
|
||||||
|
} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
@ -14,6 +20,7 @@ import {logger} from '#/logger'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {listenSoftReset} from '#/state/events'
|
import {listenSoftReset} from '#/state/events'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {
|
import {
|
||||||
useListBlockMutation,
|
useListBlockMutation,
|
||||||
useListDeleteMutation,
|
useListDeleteMutation,
|
||||||
|
@ -62,6 +69,7 @@ import * as Toast from 'view/com/util/Toast'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
|
@ -81,6 +89,7 @@ export function ProfileListScreen(props: Props) {
|
||||||
AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(),
|
AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(),
|
||||||
)
|
)
|
||||||
const {data: list, error: listError} = useListQuery(resolvedUri?.uri)
|
const {data: list, error: listError} = useListQuery(resolvedUri?.uri)
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
if (resolveError) {
|
if (resolveError) {
|
||||||
return (
|
return (
|
||||||
|
@ -101,8 +110,13 @@ export function ProfileListScreen(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedUri && list ? (
|
return resolvedUri && list && moderationOpts ? (
|
||||||
<ProfileListScreenLoaded {...props} uri={resolvedUri.uri} list={list} />
|
<ProfileListScreenLoaded
|
||||||
|
{...props}
|
||||||
|
uri={resolvedUri.uri}
|
||||||
|
list={list}
|
||||||
|
moderationOpts={moderationOpts}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LoadingScreen />
|
<LoadingScreen />
|
||||||
)
|
)
|
||||||
|
@ -112,7 +126,12 @@ function ProfileListScreenLoaded({
|
||||||
route,
|
route,
|
||||||
uri,
|
uri,
|
||||||
list,
|
list,
|
||||||
}: Props & {uri: string; list: AppBskyGraphDefs.ListView}) {
|
moderationOpts,
|
||||||
|
}: Props & {
|
||||||
|
uri: string
|
||||||
|
list: AppBskyGraphDefs.ListView
|
||||||
|
moderationOpts: ModerationOpts
|
||||||
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
|
@ -124,6 +143,10 @@ function ProfileListScreenLoaded({
|
||||||
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
|
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
|
||||||
const isScreenFocused = useIsFocused()
|
const isScreenFocused = useIsFocused()
|
||||||
|
|
||||||
|
const moderation = React.useMemo(() => {
|
||||||
|
return moderateUserList(list, moderationOpts)
|
||||||
|
}, [list, moderationOpts])
|
||||||
|
|
||||||
useSetTitle(list.name)
|
useSetTitle(list.name)
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
@ -161,6 +184,9 @@ function ProfileListScreenLoaded({
|
||||||
|
|
||||||
if (isCurateList) {
|
if (isCurateList) {
|
||||||
return (
|
return (
|
||||||
|
<ScreenHider
|
||||||
|
screenDescription={'list'}
|
||||||
|
modui={moderation.ui('contentView')}>
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
<PagerWithHeader
|
<PagerWithHeader
|
||||||
items={SECTION_TITLES_CURATE}
|
items={SECTION_TITLES_CURATE}
|
||||||
|
@ -201,9 +227,13 @@ function ProfileListScreenLoaded({
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</ScreenHider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
<ScreenHider
|
||||||
|
screenDescription={_(msg`list`)}
|
||||||
|
modui={moderation.ui('contentView')}>
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
<PagerWithHeader
|
<PagerWithHeader
|
||||||
items={SECTION_TITLES_MOD}
|
items={SECTION_TITLES_MOD}
|
||||||
|
@ -222,13 +252,18 @@ function ProfileListScreenLoaded({
|
||||||
testID="composeFAB"
|
testID="composeFAB"
|
||||||
onPress={() => openComposer({})}
|
onPress={() => openComposer({})}
|
||||||
icon={
|
icon={
|
||||||
<ComposeIcon2 strokeWidth={1.5} size={29} style={{color: 'white'}} />
|
<ComposeIcon2
|
||||||
|
strokeWidth={1.5}
|
||||||
|
size={29}
|
||||||
|
style={{color: 'white'}}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={_(msg`New post`)}
|
accessibilityLabel={_(msg`New post`)}
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</ScreenHider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue