+79
-1
src/state/cache/profile-shadow.ts
+79
-1
src/state/cache/profile-shadow.ts
···
13
import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '#/state/queries/my-blocked-accounts'
14
import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '#/state/queries/my-muted-accounts'
15
import {findAllProfilesInQueryData as findAllProfilesInNotifsQueryData} from '#/state/queries/notifications/feed'
16
-
import {findAllProfilesInQueryData as findAllProfilesInFeedsQueryData} from '#/state/queries/post-feed'
17
import {findAllProfilesInQueryData as findAllProfilesInPostLikedByQueryData} from '#/state/queries/post-liked-by'
18
import {findAllProfilesInQueryData as findAllProfilesInPostQuotesQueryData} from '#/state/queries/post-quotes'
19
import {findAllProfilesInQueryData as findAllProfilesInPostRepostedByQueryData} from '#/state/queries/post-reposted-by'
···
108
return castAsShadow(profile)
109
}
110
}, [profile, shadow])
111
}
112
113
export function updateProfileShadow(
···
13
import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '#/state/queries/my-blocked-accounts'
14
import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '#/state/queries/my-muted-accounts'
15
import {findAllProfilesInQueryData as findAllProfilesInNotifsQueryData} from '#/state/queries/notifications/feed'
16
+
import {
17
+
type FeedPage,
18
+
findAllProfilesInQueryData as findAllProfilesInFeedsQueryData,
19
+
} from '#/state/queries/post-feed'
20
import {findAllProfilesInQueryData as findAllProfilesInPostLikedByQueryData} from '#/state/queries/post-liked-by'
21
import {findAllProfilesInQueryData as findAllProfilesInPostQuotesQueryData} from '#/state/queries/post-quotes'
22
import {findAllProfilesInQueryData as findAllProfilesInPostRepostedByQueryData} from '#/state/queries/post-reposted-by'
···
111
return castAsShadow(profile)
112
}
113
}, [profile, shadow])
114
+
}
115
+
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
191
export function updateProfileShadow(
+18
-1
src/view/com/posts/PostFeed.tsx
+18
-1
src/view/com/posts/PostFeed.tsx
···
35
import {isNetworkError} from '#/lib/strings/errors'
36
import {logger} from '#/logger'
37
import {isIOS, isNative, isWeb} from '#/platform/detection'
38
import {listenPostCreated} from '#/state/events'
39
import {useFeedFeedbackContext} from '#/state/feed-feedback'
40
import {useTrendingSettings} from '#/state/preferences/trending'
···
363
*/
364
const [isCurrentFeedAtStartupSelected] = useState(selectedFeed === feed)
365
366
const feedItems: FeedRow[] = useMemo(() => {
367
// wraps a slice item, and replaces it with a showLessFollowup item
368
// if the user has pressed show less on it
···
423
// eslint-disable-next-line @typescript-eslint/no-shadow
424
item => item.uri === slice.feedPostUri,
425
)
426
-
if (item && AppBskyEmbedVideo.isView(item.post.embed)) {
427
videos.push({
428
item,
429
feedContext: slice.feedContext,
···
541
key:
542
'sliceFallbackMarker-' + sliceIndex + '-' + lastFetchedAt,
543
})
544
} else if (slice.isIncompleteThread && slice.items.length >= 3) {
545
const beforeLast = slice.items.length - 2
546
const last = slice.items.length - 1
···
636
hasPressedShowLessUris,
637
ageAssuranceBannerState,
638
isCurrentFeedAtStartupSelected,
639
])
640
641
// events
···
35
import {isNetworkError} from '#/lib/strings/errors'
36
import {logger} from '#/logger'
37
import {isIOS, isNative, isWeb} from '#/platform/detection'
38
+
import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow'
39
import {listenPostCreated} from '#/state/events'
40
import {useFeedFeedbackContext} from '#/state/feed-feedback'
41
import {useTrendingSettings} from '#/state/preferences/trending'
···
364
*/
365
const [isCurrentFeedAtStartupSelected] = useState(selectedFeed === feed)
366
367
+
const blockedOrMutedAuthors = usePostAuthorShadowFilter(
368
+
// author feeds have their own handling
369
+
feed.startsWith('author|') ? undefined : data?.pages,
370
+
)
371
+
372
const feedItems: FeedRow[] = useMemo(() => {
373
// wraps a slice item, and replaces it with a showLessFollowup item
374
// if the user has pressed show less on it
···
429
// eslint-disable-next-line @typescript-eslint/no-shadow
430
item => item.uri === slice.feedPostUri,
431
)
432
+
if (
433
+
item &&
434
+
AppBskyEmbedVideo.isView(item.post.embed) &&
435
+
!blockedOrMutedAuthors.includes(item.post.author.did)
436
+
) {
437
videos.push({
438
item,
439
feedContext: slice.feedContext,
···
551
key:
552
'sliceFallbackMarker-' + sliceIndex + '-' + lastFetchedAt,
553
})
554
+
} else if (
555
+
slice.items.some(item =>
556
+
blockedOrMutedAuthors.includes(item.post.author.did),
557
+
)
558
+
) {
559
+
// skip
560
} else if (slice.isIncompleteThread && slice.items.length >= 3) {
561
const beforeLast = slice.items.length - 2
562
const last = slice.items.length - 1
···
652
hasPressedShowLessUris,
653
ageAssuranceBannerState,
654
isCurrentFeedAtStartupSelected,
655
+
blockedOrMutedAuthors,
656
])
657
658
// events