Apply feed preferences (react-query refactor) (#2040)
* Actually implement the feed tuners hook * Move feed-tuner pass into select() to have it apply immediately on changezio/stable
parent
3e1b2346ee
commit
a03f57c8c3
|
@ -2,9 +2,13 @@ import {useMemo} from 'react'
|
||||||
import {FeedTuner} from '#/lib/api/feed-manip'
|
import {FeedTuner} from '#/lib/api/feed-manip'
|
||||||
import {FeedDescriptor} from '../queries/post-feed'
|
import {FeedDescriptor} from '../queries/post-feed'
|
||||||
import {useLanguagePrefs} from './languages'
|
import {useLanguagePrefs} from './languages'
|
||||||
|
import {usePreferencesQuery} from '../queries/preferences'
|
||||||
|
import {useSession} from '../session'
|
||||||
|
|
||||||
export function useFeedTuners(feedDesc: FeedDescriptor) {
|
export function useFeedTuners(feedDesc: FeedDescriptor) {
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (feedDesc.startsWith('feedgen')) {
|
if (feedDesc.startsWith('feedgen')) {
|
||||||
|
@ -19,30 +23,30 @@ export function useFeedTuners(feedDesc: FeedDescriptor) {
|
||||||
if (feedDesc === 'home' || feedDesc === 'following') {
|
if (feedDesc === 'home' || feedDesc === 'following') {
|
||||||
const feedTuners = []
|
const feedTuners = []
|
||||||
|
|
||||||
if (false /*TODOthis.homeFeed.hideReposts*/) {
|
if (preferences?.feedViewPrefs.hideReposts) {
|
||||||
feedTuners.push(FeedTuner.removeReposts)
|
feedTuners.push(FeedTuner.removeReposts)
|
||||||
} else {
|
} else {
|
||||||
feedTuners.push(FeedTuner.dedupReposts)
|
feedTuners.push(FeedTuner.dedupReposts)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true /*TODOthis.homeFeed.hideReplies*/) {
|
if (preferences?.feedViewPrefs.hideReplies) {
|
||||||
feedTuners.push(FeedTuner.removeReplies)
|
feedTuners.push(FeedTuner.removeReplies)
|
||||||
} /* TODO else {
|
} else {
|
||||||
feedTuners.push(
|
feedTuners.push(
|
||||||
FeedTuner.thresholdRepliesOnly({
|
FeedTuner.thresholdRepliesOnly({
|
||||||
userDid: this.rootStore.session.data?.did || '',
|
userDid: currentAccount?.did || '',
|
||||||
minLikes: this.homeFeed.hideRepliesByLikeCount,
|
minLikes: preferences?.feedViewPrefs.hideRepliesByLikeCount || 0,
|
||||||
followedOnly: !!this.homeFeed.hideRepliesByUnfollowed,
|
followedOnly: !!preferences?.feedViewPrefs.hideRepliesByUnfollowed,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}*/
|
}
|
||||||
|
|
||||||
if (false /*TODOthis.homeFeed.hideQuotePosts*/) {
|
if (preferences?.feedViewPrefs.hideQuotePosts) {
|
||||||
feedTuners.push(FeedTuner.removeQuotePosts)
|
feedTuners.push(FeedTuner.removeQuotePosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return feedTuners
|
return feedTuners
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, [feedDesc, langPrefs])
|
}, [feedDesc, currentAccount, preferences, langPrefs])
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,7 @@ export interface FeedParams {
|
||||||
mergeFeedSources?: string[]
|
mergeFeedSources?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type RQPageParam =
|
type RQPageParam = {cursor: string | undefined; api: FeedAPI} | undefined
|
||||||
| {cursor: string | undefined; api: FeedAPI; tuner: FeedTuner | NoopFeedTuner}
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) {
|
export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) {
|
||||||
return ['post-feed', feedDesc, params || {}]
|
return ['post-feed', feedDesc, params || {}]
|
||||||
|
@ -66,6 +64,12 @@ export interface FeedPostSlice {
|
||||||
items: FeedPostSliceItem[]
|
items: FeedPostSliceItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FeedPageUnselected {
|
||||||
|
api: FeedAPI
|
||||||
|
cursor: string | undefined
|
||||||
|
feed: AppBskyFeedDefs.FeedViewPost[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface FeedPage {
|
export interface FeedPage {
|
||||||
api: FeedAPI
|
api: FeedAPI
|
||||||
tuner: FeedTuner | NoopFeedTuner
|
tuner: FeedTuner | NoopFeedTuner
|
||||||
|
@ -83,30 +87,27 @@ export function usePostFeedQuery(
|
||||||
const enabled = opts?.enabled !== false
|
const enabled = opts?.enabled !== false
|
||||||
|
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery<
|
||||||
FeedPage,
|
FeedPageUnselected,
|
||||||
Error,
|
Error,
|
||||||
InfiniteData<FeedPage>,
|
InfiniteData<FeedPage>,
|
||||||
QueryKey,
|
QueryKey,
|
||||||
RQPageParam
|
RQPageParam
|
||||||
>({
|
>({
|
||||||
|
enabled,
|
||||||
staleTime: STALE.INFINITY,
|
staleTime: STALE.INFINITY,
|
||||||
queryKey: RQKEY(feedDesc, params),
|
queryKey: RQKEY(feedDesc, params),
|
||||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||||
logger.debug('usePostFeedQuery', {feedDesc, pageParam})
|
logger.debug('usePostFeedQuery', {feedDesc, pageParam})
|
||||||
|
|
||||||
const {api, tuner, cursor} = pageParam
|
const {api, cursor} = pageParam
|
||||||
? pageParam
|
? pageParam
|
||||||
: {
|
: {
|
||||||
api: createApi(feedDesc, params || {}, feedTuners),
|
api: createApi(feedDesc, params || {}, feedTuners),
|
||||||
tuner: params?.disableTuner
|
|
||||||
? new NoopFeedTuner()
|
|
||||||
: new FeedTuner(feedTuners),
|
|
||||||
cursor: undefined,
|
cursor: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.fetch({cursor, limit: 30})
|
const res = await api.fetch({cursor, limit: 30})
|
||||||
precacheResolvedUris(queryClient, res.feed) // precache the handle->did resolution
|
precacheResolvedUris(queryClient, res.feed) // precache the handle->did resolution
|
||||||
const slices = tuner.tune(res.feed)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If this is a public view, we need to check if posts fail moderation.
|
* If this is a public view, we need to check if posts fail moderation.
|
||||||
|
@ -115,35 +116,31 @@ export function usePostFeedQuery(
|
||||||
* some not.
|
* some not.
|
||||||
*/
|
*/
|
||||||
if (!getAgent().session) {
|
if (!getAgent().session) {
|
||||||
// assume false
|
assertSomePostsPassModeration(res.feed)
|
||||||
let somePostsPassModeration = false
|
|
||||||
|
|
||||||
for (const slice of slices) {
|
|
||||||
for (let i = 0; i < slice.items.length; i++) {
|
|
||||||
const item = slice.items[i]
|
|
||||||
const moderationOpts = getModerationOpts({
|
|
||||||
userDid: '',
|
|
||||||
preferences: DEFAULT_LOGGED_OUT_PREFERENCES,
|
|
||||||
})
|
|
||||||
const moderation = moderatePost(item.post, moderationOpts)
|
|
||||||
|
|
||||||
if (!moderation.content.filter) {
|
|
||||||
// we have a sfw post
|
|
||||||
somePostsPassModeration = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!somePostsPassModeration) {
|
|
||||||
throw new Error(KnownError.FeedNSFPublic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
tuner,
|
|
||||||
cursor: res.cursor,
|
cursor: res.cursor,
|
||||||
slices: slices.map(slice => ({
|
feed: res.feed,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialPageParam: undefined,
|
||||||
|
getNextPageParam: lastPage => ({
|
||||||
|
api: lastPage.api,
|
||||||
|
cursor: lastPage.cursor,
|
||||||
|
}),
|
||||||
|
select(data) {
|
||||||
|
const tuner = params?.disableTuner
|
||||||
|
? new NoopFeedTuner()
|
||||||
|
: new FeedTuner(feedTuners)
|
||||||
|
return {
|
||||||
|
pageParams: data.pageParams,
|
||||||
|
pages: data.pages.map(page => ({
|
||||||
|
api: page.api,
|
||||||
|
tuner,
|
||||||
|
cursor: page.cursor,
|
||||||
|
slices: tuner.tune(page.feed).map(slice => ({
|
||||||
_reactKey: slice._reactKey,
|
_reactKey: slice._reactKey,
|
||||||
rootUri: slice.rootItem.post.uri,
|
rootUri: slice.rootItem.post.uri,
|
||||||
isThread:
|
isThread:
|
||||||
|
@ -162,22 +159,17 @@ export function usePostFeedQuery(
|
||||||
uri: item.post.uri,
|
uri: item.post.uri,
|
||||||
post: item.post,
|
post: item.post,
|
||||||
record: item.post.record,
|
record: item.post.record,
|
||||||
reason: i === 0 && slice.source ? slice.source : item.reason,
|
reason:
|
||||||
|
i === 0 && slice.source ? slice.source : item.reason,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
.filter(Boolean) as FeedPostSliceItem[],
|
.filter(Boolean) as FeedPostSliceItem[],
|
||||||
})),
|
})),
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initialPageParam: undefined,
|
|
||||||
getNextPageParam: lastPage => ({
|
|
||||||
api: lastPage.api,
|
|
||||||
tuner: lastPage.tuner,
|
|
||||||
cursor: lastPage.cursor,
|
|
||||||
}),
|
|
||||||
enabled,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,8 +227,10 @@ function createApi(
|
||||||
export function findPostInQueryData(
|
export function findPostInQueryData(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
uri: string,
|
uri: string,
|
||||||
): FeedPostSliceItem | undefined {
|
): AppBskyFeedDefs.FeedViewPost | undefined {
|
||||||
const queryDatas = queryClient.getQueriesData<InfiniteData<FeedPage>>({
|
const queryDatas = queryClient.getQueriesData<
|
||||||
|
InfiniteData<FeedPageUnselected>
|
||||||
|
>({
|
||||||
queryKey: ['post-feed'],
|
queryKey: ['post-feed'],
|
||||||
})
|
})
|
||||||
for (const [_queryKey, queryData] of queryDatas) {
|
for (const [_queryKey, queryData] of queryDatas) {
|
||||||
|
@ -244,14 +238,34 @@ export function findPostInQueryData(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (const page of queryData?.pages) {
|
for (const page of queryData?.pages) {
|
||||||
for (const slice of page.slices) {
|
for (const item of page.feed) {
|
||||||
for (const item of slice.items) {
|
if (item.post.uri === uri) {
|
||||||
if (item.uri === uri) {
|
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
|
||||||
|
// assume false
|
||||||
|
let somePostsPassModeration = false
|
||||||
|
|
||||||
|
for (const item of feed) {
|
||||||
|
const moderationOpts = getModerationOpts({
|
||||||
|
userDid: '',
|
||||||
|
preferences: DEFAULT_LOGGED_OUT_PREFERENCES,
|
||||||
|
})
|
||||||
|
const moderation = moderatePost(item.post, moderationOpts)
|
||||||
|
|
||||||
|
if (!moderation.content.filter) {
|
||||||
|
// we have a sfw post
|
||||||
|
somePostsPassModeration = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!somePostsPassModeration) {
|
||||||
|
throw new Error(KnownError.FeedNSFPublic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,10 +8,7 @@ import {useQuery, useQueryClient, QueryClient} from '@tanstack/react-query'
|
||||||
import {getAgent} from '#/state/session'
|
import {getAgent} from '#/state/session'
|
||||||
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {
|
import {findPostInQueryData as findPostInFeedQueryData} from './post-feed'
|
||||||
findPostInQueryData as findPostInFeedQueryData,
|
|
||||||
FeedPostSliceItem,
|
|
||||||
} from './post-feed'
|
|
||||||
import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
|
import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
|
||||||
import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri'
|
import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri'
|
||||||
|
|
||||||
|
@ -93,7 +90,7 @@ export function usePostThreadQuery(uri: string | undefined) {
|
||||||
{
|
{
|
||||||
const item = findPostInFeedQueryData(queryClient, uri)
|
const item = findPostInFeedQueryData(queryClient, uri)
|
||||||
if (item) {
|
if (item) {
|
||||||
return feedItemToPlaceholderThread(item)
|
return feedViewPostToPlaceholderThread(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -275,13 +272,15 @@ function threadNodeToPlaceholderThread(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function feedItemToPlaceholderThread(item: FeedPostSliceItem): ThreadNode {
|
function feedViewPostToPlaceholderThread(
|
||||||
|
item: AppBskyFeedDefs.FeedViewPost,
|
||||||
|
): ThreadNode {
|
||||||
return {
|
return {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
_reactKey: item.post.uri,
|
_reactKey: item.post.uri,
|
||||||
uri: item.post.uri,
|
uri: item.post.uri,
|
||||||
post: item.post,
|
post: item.post,
|
||||||
record: item.record,
|
record: item.post.record as AppBskyFeedPost.Record, // validated in post-feed
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
replies: undefined,
|
replies: undefined,
|
||||||
viewer: item.post.viewer,
|
viewer: item.post.viewer,
|
||||||
|
@ -291,7 +290,7 @@ function feedItemToPlaceholderThread(item: FeedPostSliceItem): ThreadNode {
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
showChildReplyLine: false,
|
showChildReplyLine: false,
|
||||||
showParentReplyLine: false,
|
showParentReplyLine: false,
|
||||||
isParentLoading: !!item.record.reply,
|
isParentLoading: !!(item.post.record as AppBskyFeedPost.Record).reply,
|
||||||
isChildLoading: !!item.post.replyCount,
|
isChildLoading: !!item.post.replyCount,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -305,7 +304,7 @@ function postViewToPlaceholderThread(
|
||||||
_reactKey: post.uri,
|
_reactKey: post.uri,
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
post: post,
|
post: post,
|
||||||
record: post.record as AppBskyFeedPost.Record, // validate in notifs
|
record: post.record as AppBskyFeedPost.Record, // validated in notifs
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
replies: undefined,
|
replies: undefined,
|
||||||
viewer: post.viewer,
|
viewer: post.viewer,
|
||||||
|
|
Loading…
Reference in New Issue