Filter mute words from notifications
This commit is contained in:
@@ -17,7 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||||
import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
|
import {
|
||||||
|
AppBskyActorDefs,
|
||||||
|
AppBskyFeedDefs,
|
||||||
|
AppBskyFeedPost,
|
||||||
|
AtUri,
|
||||||
|
} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
InfiniteData,
|
InfiniteData,
|
||||||
QueryClient,
|
QueryClient,
|
||||||
@@ -26,6 +31,7 @@ import {
|
|||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies'
|
import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies'
|
||||||
import {useModerationOpts} from '../../preferences/moderation-opts'
|
import {useModerationOpts} from '../../preferences/moderation-opts'
|
||||||
@@ -67,9 +73,15 @@ export function useNotificationFeedQuery(opts?: {
|
|||||||
|
|
||||||
const selectArgs = useMemo(() => {
|
const selectArgs = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
moderationOpts,
|
||||||
hiddenReplyUris,
|
hiddenReplyUris,
|
||||||
}
|
}
|
||||||
}, [hiddenReplyUris])
|
}, [moderationOpts, hiddenReplyUris])
|
||||||
|
const lastRun = useRef<{
|
||||||
|
data: InfiniteData<FeedPage>
|
||||||
|
args: typeof selectArgs
|
||||||
|
result: InfiniteData<FeedPage>
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
const query = useInfiniteQuery<
|
const query = useInfiniteQuery<
|
||||||
FeedPage,
|
FeedPage,
|
||||||
@@ -111,7 +123,38 @@ export function useNotificationFeedQuery(opts?: {
|
|||||||
enabled,
|
enabled,
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: InfiniteData<FeedPage>) => {
|
(data: InfiniteData<FeedPage>) => {
|
||||||
const {hiddenReplyUris} = selectArgs
|
const {moderationOpts, hiddenReplyUris} = selectArgs
|
||||||
|
|
||||||
|
// Keep track of the last run and whether we can reuse
|
||||||
|
// some already selected pages from there.
|
||||||
|
let reusedPages = []
|
||||||
|
if (lastRun.current) {
|
||||||
|
const {
|
||||||
|
data: lastData,
|
||||||
|
args: lastArgs,
|
||||||
|
result: lastResult,
|
||||||
|
} = lastRun.current
|
||||||
|
let canReuse = true
|
||||||
|
for (let key in selectArgs) {
|
||||||
|
if (selectArgs.hasOwnProperty(key)) {
|
||||||
|
if ((selectArgs as any)[key] !== (lastArgs as any)[key]) {
|
||||||
|
// Can't do reuse anything if any input has changed.
|
||||||
|
canReuse = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canReuse) {
|
||||||
|
for (let i = 0; i < data.pages.length; i++) {
|
||||||
|
if (data.pages[i] && lastData.pages[i] === data.pages[i]) {
|
||||||
|
reusedPages.push(lastResult.pages[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Stop as soon as pages stop matching up.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// override 'isRead' using the first page's returned seenAt
|
// override 'isRead' using the first page's returned seenAt
|
||||||
// we do this because the `markAllRead()` call above will
|
// we do this because the `markAllRead()` call above will
|
||||||
@@ -124,23 +167,49 @@ export function useNotificationFeedQuery(opts?: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = {
|
const result = {
|
||||||
...data,
|
...data,
|
||||||
pages: data.pages.map(page => {
|
pages: [
|
||||||
return {
|
...reusedPages,
|
||||||
...page,
|
...data.pages.slice(reusedPages.length).map(page => {
|
||||||
items: page.items.filter(item => {
|
return {
|
||||||
const isHiddenReply =
|
...page,
|
||||||
item.type === 'reply' &&
|
items: page.items
|
||||||
item.subjectUri &&
|
.filter(item => {
|
||||||
hiddenReplyUris.has(item.subjectUri)
|
const isHiddenReply =
|
||||||
return !isHiddenReply
|
item.type === 'reply' &&
|
||||||
}),
|
item.subjectUri &&
|
||||||
}
|
hiddenReplyUris.has(item.subjectUri)
|
||||||
}),
|
return !isHiddenReply
|
||||||
|
})
|
||||||
|
.filter(item => {
|
||||||
|
if (
|
||||||
|
item.type === 'reply' ||
|
||||||
|
item.type === 'mention' ||
|
||||||
|
item.type === 'quote'
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* The `isPostView` check will fail here bc we don't have
|
||||||
|
* a `$type` field on the `subject`. But if the nested
|
||||||
|
* `record` is a post, we know it's a post view.
|
||||||
|
*/
|
||||||
|
if (AppBskyFeedPost.isRecord(item.subject?.record)) {
|
||||||
|
const mod = moderatePost(item.subject, moderationOpts!)
|
||||||
|
if (mod.ui('contentList').filter) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
lastRun.current = {data, result, args: selectArgs}
|
||||||
|
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
[selectArgs],
|
[selectArgs],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user