mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 change

authored by

Paul Frazee and committed by
GitHub
a03f57c8 3e1b2346

+102 -85
+13 -9
src/state/preferences/feed-tuners.tsx
··· 2 2 import {FeedTuner} from '#/lib/api/feed-manip' 3 3 import {FeedDescriptor} from '../queries/post-feed' 4 4 import {useLanguagePrefs} from './languages' 5 + import {usePreferencesQuery} from '../queries/preferences' 6 + import {useSession} from '../session' 5 7 6 8 export function useFeedTuners(feedDesc: FeedDescriptor) { 7 9 const langPrefs = useLanguagePrefs() 10 + const {data: preferences} = usePreferencesQuery() 11 + const {currentAccount} = useSession() 8 12 9 13 return useMemo(() => { 10 14 if (feedDesc.startsWith('feedgen')) { ··· 19 23 if (feedDesc === 'home' || feedDesc === 'following') { 20 24 const feedTuners = [] 21 25 22 - if (false /*TODOthis.homeFeed.hideReposts*/) { 26 + if (preferences?.feedViewPrefs.hideReposts) { 23 27 feedTuners.push(FeedTuner.removeReposts) 24 28 } else { 25 29 feedTuners.push(FeedTuner.dedupReposts) 26 30 } 27 31 28 - if (true /*TODOthis.homeFeed.hideReplies*/) { 32 + if (preferences?.feedViewPrefs.hideReplies) { 29 33 feedTuners.push(FeedTuner.removeReplies) 30 - } /* TODO else { 34 + } else { 31 35 feedTuners.push( 32 36 FeedTuner.thresholdRepliesOnly({ 33 - userDid: this.rootStore.session.data?.did || '', 34 - minLikes: this.homeFeed.hideRepliesByLikeCount, 35 - followedOnly: !!this.homeFeed.hideRepliesByUnfollowed, 37 + userDid: currentAccount?.did || '', 38 + minLikes: preferences?.feedViewPrefs.hideRepliesByLikeCount || 0, 39 + followedOnly: !!preferences?.feedViewPrefs.hideRepliesByUnfollowed, 36 40 }), 37 41 ) 38 - }*/ 42 + } 39 43 40 - if (false /*TODOthis.homeFeed.hideQuotePosts*/) { 44 + if (preferences?.feedViewPrefs.hideQuotePosts) { 41 45 feedTuners.push(FeedTuner.removeQuotePosts) 42 46 } 43 47 44 48 return feedTuners 45 49 } 46 50 return [] 47 - }, [feedDesc, langPrefs]) 51 + }, [feedDesc, currentAccount, preferences, langPrefs]) 48 52 }
+81 -67
src/state/queries/post-feed.ts
··· 43 43 mergeFeedSources?: string[] 44 44 } 45 45 46 - type RQPageParam = 47 - | {cursor: string | undefined; api: FeedAPI; tuner: FeedTuner | NoopFeedTuner} 48 - | undefined 46 + type RQPageParam = {cursor: string | undefined; api: FeedAPI} | undefined 49 47 50 48 export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) { 51 49 return ['post-feed', feedDesc, params || {}] ··· 64 62 rootUri: string 65 63 isThread: boolean 66 64 items: FeedPostSliceItem[] 65 + } 66 + 67 + export interface FeedPageUnselected { 68 + api: FeedAPI 69 + cursor: string | undefined 70 + feed: AppBskyFeedDefs.FeedViewPost[] 67 71 } 68 72 69 73 export interface FeedPage { ··· 83 87 const enabled = opts?.enabled !== false 84 88 85 89 return useInfiniteQuery< 86 - FeedPage, 90 + FeedPageUnselected, 87 91 Error, 88 92 InfiniteData<FeedPage>, 89 93 QueryKey, 90 94 RQPageParam 91 95 >({ 96 + enabled, 92 97 staleTime: STALE.INFINITY, 93 98 queryKey: RQKEY(feedDesc, params), 94 99 async queryFn({pageParam}: {pageParam: RQPageParam}) { 95 100 logger.debug('usePostFeedQuery', {feedDesc, pageParam}) 96 101 97 - const {api, tuner, cursor} = pageParam 102 + const {api, cursor} = pageParam 98 103 ? pageParam 99 104 : { 100 105 api: createApi(feedDesc, params || {}, feedTuners), 101 - tuner: params?.disableTuner 102 - ? new NoopFeedTuner() 103 - : new FeedTuner(feedTuners), 104 106 cursor: undefined, 105 107 } 106 108 107 109 const res = await api.fetch({cursor, limit: 30}) 108 110 precacheResolvedUris(queryClient, res.feed) // precache the handle->did resolution 109 - const slices = tuner.tune(res.feed) 110 111 111 112 /* 112 113 * If this is a public view, we need to check if posts fail moderation. ··· 115 116 * some not. 116 117 */ 117 118 if (!getAgent().session) { 118 - // assume false 119 - let somePostsPassModeration = false 120 - 121 - for (const slice of slices) { 122 - for (let i = 0; i < slice.items.length; i++) { 123 - const item = slice.items[i] 124 - const moderationOpts = getModerationOpts({ 125 - userDid: '', 126 - preferences: DEFAULT_LOGGED_OUT_PREFERENCES, 127 - }) 128 - const moderation = moderatePost(item.post, moderationOpts) 129 - 130 - if (!moderation.content.filter) { 131 - // we have a sfw post 132 - somePostsPassModeration = true 133 - } 134 - } 135 - } 136 - 137 - if (!somePostsPassModeration) { 138 - throw new Error(KnownError.FeedNSFPublic) 139 - } 119 + assertSomePostsPassModeration(res.feed) 140 120 } 141 121 142 122 return { 143 123 api, 144 - tuner, 145 124 cursor: res.cursor, 146 - slices: slices.map(slice => ({ 147 - _reactKey: slice._reactKey, 148 - rootUri: slice.rootItem.post.uri, 149 - isThread: 150 - slice.items.length > 1 && 151 - slice.items.every( 152 - item => item.post.author.did === slice.items[0].post.author.did, 153 - ), 154 - items: slice.items 155 - .map((item, i) => { 156 - if ( 157 - AppBskyFeedPost.isRecord(item.post.record) && 158 - AppBskyFeedPost.validateRecord(item.post.record).success 159 - ) { 160 - return { 161 - _reactKey: `${slice._reactKey}-${i}`, 162 - uri: item.post.uri, 163 - post: item.post, 164 - record: item.post.record, 165 - reason: i === 0 && slice.source ? slice.source : item.reason, 166 - } 167 - } 168 - return undefined 169 - }) 170 - .filter(Boolean) as FeedPostSliceItem[], 171 - })), 125 + feed: res.feed, 172 126 } 173 127 }, 174 128 initialPageParam: undefined, 175 129 getNextPageParam: lastPage => ({ 176 130 api: lastPage.api, 177 - tuner: lastPage.tuner, 178 131 cursor: lastPage.cursor, 179 132 }), 180 - enabled, 133 + select(data) { 134 + const tuner = params?.disableTuner 135 + ? new NoopFeedTuner() 136 + : new FeedTuner(feedTuners) 137 + return { 138 + pageParams: data.pageParams, 139 + pages: data.pages.map(page => ({ 140 + api: page.api, 141 + tuner, 142 + cursor: page.cursor, 143 + slices: tuner.tune(page.feed).map(slice => ({ 144 + _reactKey: slice._reactKey, 145 + rootUri: slice.rootItem.post.uri, 146 + isThread: 147 + slice.items.length > 1 && 148 + slice.items.every( 149 + item => item.post.author.did === slice.items[0].post.author.did, 150 + ), 151 + items: slice.items 152 + .map((item, i) => { 153 + if ( 154 + AppBskyFeedPost.isRecord(item.post.record) && 155 + AppBskyFeedPost.validateRecord(item.post.record).success 156 + ) { 157 + return { 158 + _reactKey: `${slice._reactKey}-${i}`, 159 + uri: item.post.uri, 160 + post: item.post, 161 + record: item.post.record, 162 + reason: 163 + i === 0 && slice.source ? slice.source : item.reason, 164 + } 165 + } 166 + return undefined 167 + }) 168 + .filter(Boolean) as FeedPostSliceItem[], 169 + })), 170 + })), 171 + } 172 + }, 181 173 }) 182 174 } 183 175 ··· 235 227 export function findPostInQueryData( 236 228 queryClient: QueryClient, 237 229 uri: string, 238 - ): FeedPostSliceItem | undefined { 239 - const queryDatas = queryClient.getQueriesData<InfiniteData<FeedPage>>({ 230 + ): AppBskyFeedDefs.FeedViewPost | undefined { 231 + const queryDatas = queryClient.getQueriesData< 232 + InfiniteData<FeedPageUnselected> 233 + >({ 240 234 queryKey: ['post-feed'], 241 235 }) 242 236 for (const [_queryKey, queryData] of queryDatas) { ··· 244 238 continue 245 239 } 246 240 for (const page of queryData?.pages) { 247 - for (const slice of page.slices) { 248 - for (const item of slice.items) { 249 - if (item.uri === uri) { 250 - return item 251 - } 241 + for (const item of page.feed) { 242 + if (item.post.uri === uri) { 243 + return item 252 244 } 253 245 } 254 246 } 255 247 } 256 248 return undefined 257 249 } 250 + 251 + function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) { 252 + // assume false 253 + let somePostsPassModeration = false 254 + 255 + for (const item of feed) { 256 + const moderationOpts = getModerationOpts({ 257 + userDid: '', 258 + preferences: DEFAULT_LOGGED_OUT_PREFERENCES, 259 + }) 260 + const moderation = moderatePost(item.post, moderationOpts) 261 + 262 + if (!moderation.content.filter) { 263 + // we have a sfw post 264 + somePostsPassModeration = true 265 + } 266 + } 267 + 268 + if (!somePostsPassModeration) { 269 + throw new Error(KnownError.FeedNSFPublic) 270 + } 271 + }
+8 -9
src/state/queries/post-thread.ts
··· 8 8 import {getAgent} from '#/state/session' 9 9 import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 10 10 import {STALE} from '#/state/queries' 11 - import { 12 - findPostInQueryData as findPostInFeedQueryData, 13 - FeedPostSliceItem, 14 - } from './post-feed' 11 + import {findPostInQueryData as findPostInFeedQueryData} from './post-feed' 15 12 import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed' 16 13 import {precacheThreadPosts as precacheResolvedUris} from './resolve-uri' 17 14 ··· 93 90 { 94 91 const item = findPostInFeedQueryData(queryClient, uri) 95 92 if (item) { 96 - return feedItemToPlaceholderThread(item) 93 + return feedViewPostToPlaceholderThread(item) 97 94 } 98 95 } 99 96 { ··· 275 272 } 276 273 } 277 274 278 - function feedItemToPlaceholderThread(item: FeedPostSliceItem): ThreadNode { 275 + function feedViewPostToPlaceholderThread( 276 + item: AppBskyFeedDefs.FeedViewPost, 277 + ): ThreadNode { 279 278 return { 280 279 type: 'post', 281 280 _reactKey: item.post.uri, 282 281 uri: item.post.uri, 283 282 post: item.post, 284 - record: item.record, 283 + record: item.post.record as AppBskyFeedPost.Record, // validated in post-feed 285 284 parent: undefined, 286 285 replies: undefined, 287 286 viewer: item.post.viewer, ··· 291 290 hasMore: false, 292 291 showChildReplyLine: false, 293 292 showParentReplyLine: false, 294 - isParentLoading: !!item.record.reply, 293 + isParentLoading: !!(item.post.record as AppBskyFeedPost.Record).reply, 295 294 isChildLoading: !!item.post.replyCount, 296 295 }, 297 296 } ··· 305 304 _reactKey: post.uri, 306 305 uri: post.uri, 307 306 post: post, 308 - record: post.record as AppBskyFeedPost.Record, // validate in notifs 307 + record: post.record as AppBskyFeedPost.Record, // validated in notifs 309 308 parent: undefined, 310 309 replies: undefined, 311 310 viewer: post.viewer,