Skip to content

Commit 55eb6c5

Browse files
authored
add shadow filter to post feeds (#9406)
1 parent 051dbd2 commit 55eb6c5

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

src/state/cache/profile-shadow.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from
1313
import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '#/state/queries/my-blocked-accounts'
1414
import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '#/state/queries/my-muted-accounts'
1515
import {findAllProfilesInQueryData as findAllProfilesInNotifsQueryData} from '#/state/queries/notifications/feed'
16-
import {findAllProfilesInQueryData as findAllProfilesInFeedsQueryData} from '#/state/queries/post-feed'
16+
import {
17+
type FeedPage,
18+
findAllProfilesInQueryData as findAllProfilesInFeedsQueryData,
19+
} from '#/state/queries/post-feed'
1720
import {findAllProfilesInQueryData as findAllProfilesInPostLikedByQueryData} from '#/state/queries/post-liked-by'
1821
import {findAllProfilesInQueryData as findAllProfilesInPostQuotesQueryData} from '#/state/queries/post-quotes'
1922
import {findAllProfilesInQueryData as findAllProfilesInPostRepostedByQueryData} from '#/state/queries/post-reposted-by'
@@ -110,6 +113,81 @@ export function useMaybeProfileShadow<
110113
}, [profile, shadow])
111114
}
112115

116+
/**
117+
* Takes a list of posts, and returns a list of DIDs that should be filtered out
118+
*
119+
* Note: it doesn't retroactively scan the cache, but only listens to new updates.
120+
* The use case here is intended for removing a post from a feed after you mute the author
121+
*/
122+
export function usePostAuthorShadowFilter(data?: FeedPage[]) {
123+
const [trackedDids, setTrackedDids] = useState<string[]>(
124+
() =>
125+
data?.flatMap(page =>
126+
page.slices.flatMap(slice =>
127+
slice.items.map(item => item.post.author.did),
128+
),
129+
) ?? [],
130+
)
131+
const [authors, setAuthors] = useState(
132+
new Map<string, {muted: boolean; blocked: boolean}>(),
133+
)
134+
135+
const [prevData, setPrevData] = useState(data)
136+
if (data !== prevData) {
137+
const newAuthors = new Set(trackedDids)
138+
let hasNew = false
139+
for (const slice of data?.flatMap(page => page.slices) ?? []) {
140+
for (const item of slice.items) {
141+
const author = item.post.author
142+
if (!newAuthors.has(author.did)) {
143+
hasNew = true
144+
newAuthors.add(author.did)
145+
}
146+
}
147+
}
148+
if (hasNew) setTrackedDids([...newAuthors])
149+
setPrevData(data)
150+
}
151+
152+
useEffect(() => {
153+
const unsubs: Array<() => void> = []
154+
155+
for (const did of trackedDids) {
156+
function onUpdate(value: Partial<ProfileShadow>) {
157+
setAuthors(prev => {
158+
const prevValue = prev.get(did)
159+
const next = new Map(prev)
160+
next.set(did, {
161+
blocked: Boolean(value.blockingUri ?? prevValue?.blocked ?? false),
162+
muted: Boolean(value.muted ?? prevValue?.muted ?? false),
163+
})
164+
return next
165+
})
166+
}
167+
emitter.addListener(did, onUpdate)
168+
unsubs.push(() => {
169+
emitter.removeListener(did, onUpdate)
170+
})
171+
}
172+
173+
return () => {
174+
unsubs.map(fn => fn())
175+
}
176+
}, [trackedDids])
177+
178+
return useMemo(() => {
179+
const dids: Array<string> = []
180+
181+
for (const [did, value] of authors.entries()) {
182+
if (value.blocked || value.muted) {
183+
dids.push(did)
184+
}
185+
}
186+
187+
return dids
188+
}, [authors])
189+
}
190+
113191
export function updateProfileShadow(
114192
queryClient: QueryClient,
115193
did: string,

src/view/com/posts/PostFeed.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {logEvent} from '#/lib/statsig/statsig'
3535
import {isNetworkError} from '#/lib/strings/errors'
3636
import {logger} from '#/logger'
3737
import {isIOS, isNative, isWeb} from '#/platform/detection'
38+
import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow'
3839
import {listenPostCreated} from '#/state/events'
3940
import {useFeedFeedbackContext} from '#/state/feed-feedback'
4041
import {useTrendingSettings} from '#/state/preferences/trending'
@@ -363,6 +364,11 @@ let PostFeed = ({
363364
*/
364365
const [isCurrentFeedAtStartupSelected] = useState(selectedFeed === feed)
365366

367+
const blockedOrMutedAuthors = usePostAuthorShadowFilter(
368+
// author feeds have their own handling
369+
feed.startsWith('author|') ? undefined : data?.pages,
370+
)
371+
366372
const feedItems: FeedRow[] = useMemo(() => {
367373
// wraps a slice item, and replaces it with a showLessFollowup item
368374
// if the user has pressed show less on it
@@ -423,7 +429,11 @@ let PostFeed = ({
423429
// eslint-disable-next-line @typescript-eslint/no-shadow
424430
item => item.uri === slice.feedPostUri,
425431
)
426-
if (item && AppBskyEmbedVideo.isView(item.post.embed)) {
432+
if (
433+
item &&
434+
AppBskyEmbedVideo.isView(item.post.embed) &&
435+
!blockedOrMutedAuthors.includes(item.post.author.did)
436+
) {
427437
videos.push({
428438
item,
429439
feedContext: slice.feedContext,
@@ -541,6 +551,12 @@ let PostFeed = ({
541551
key:
542552
'sliceFallbackMarker-' + sliceIndex + '-' + lastFetchedAt,
543553
})
554+
} else if (
555+
slice.items.some(item =>
556+
blockedOrMutedAuthors.includes(item.post.author.did),
557+
)
558+
) {
559+
// skip
544560
} else if (slice.isIncompleteThread && slice.items.length >= 3) {
545561
const beforeLast = slice.items.length - 2
546562
const last = slice.items.length - 1
@@ -636,6 +652,7 @@ let PostFeed = ({
636652
hasPressedShowLessUris,
637653
ageAssuranceBannerState,
638654
isCurrentFeedAtStartupSelected,
655+
blockedOrMutedAuthors,
639656
])
640657

641658
// events

0 commit comments

Comments
 (0)