fix: sync unread when mismatched, close #4028

This commit is contained in:
Stephen Zhou 2025-07-21 16:52:37 +08:00
parent 71408ea923
commit 35d7f9e784
No known key found for this signature in database
5 changed files with 58 additions and 21 deletions

View File

@ -99,18 +99,12 @@ const useRemoteEntries = (): UseEntriesReturn => {
const refetch = useCallback(async () => void query.refetch(), [query])
const fetchNextPage = useCallback(async () => void query.fetchNextPage(), [query])
const entriesIds = useMemo(() => {
if (!query.data || query.isLoading || query.isError) {
return []
}
return query.data?.pages?.map((page) => page.data?.map((entry) => entry.entries.id)).flat()
}, [query.data, query.isLoading, query.isError])
if (!query.data || query.isLoading) {
return fallbackReturn
}
return {
entriesIds,
entriesIds: query.entriesIds,
hasNext: query.hasNextPage,
hasUpdate,
refetch,

View File

@ -178,24 +178,13 @@ function useRemoteEntries(props?: UseEntriesProps): UseEntriesReturn {
const refetch = useCallback(async () => void query.refetch(), [query])
const fetchNextPage = useCallback(async () => void query.fetchNextPage(), [query])
const entriesIds = useMemo(() => {
if (!query.data || query.isLoading || query.isError) {
return []
}
return (
query.data?.pages
?.map((page) => page.data?.map((entry) => entry.entries.id))
.flat()
.filter((id) => typeof id === "string") || []
)
}, [query.data, query.isLoading, query.isError])
if (!query.data || query.isLoading) {
return fallbackReturn
}
return {
entriesIds,
entriesIds: query.entriesIds,
hasNext: query.hasNextPage,
hasUpdate: false,
refetch,

View File

@ -1,11 +1,12 @@
import type { FeedViewType } from "@follow/constants"
import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
import { useCallback } from "react"
import { useCallback, useMemo } from "react"
import { useFeedUnreadIsDirty } from "../atoms/feed"
import { FEED_COLLECTION_LIST } from "../constants/app"
import { queryClient } from "../context"
import { getSubscriptionByEntryId } from "../subscription/getter"
import { useSyncUnreadWhenUnMatch } from "../unread/hooks"
import { getEntry } from "./getter"
import { entrySyncServices, useEntryStore } from "./store"
import type { EntryModel, FetchEntriesProps, FetchEntriesPropsSettings } from "./types"
@ -62,7 +63,7 @@ export const useEntriesQuery = (
const isPop =
"history" in globalThis && "isPop" in globalThis.history && !!globalThis.history.isPop
return useInfiniteQuery({
const query = useInfiniteQuery({
queryKey: [
"entries",
feedId,
@ -96,6 +97,27 @@ export const useEntriesQuery = (
isPop ? Infinity : fetchUnread && feedUnreadDirty ? 0 : defaultStaleTime,
enabled: !!props,
})
const entriesIds = useMemo(() => {
if (!query.data || query.isLoading || query.isError) {
return []
}
return (
query.data?.pages
?.map((page) => page.data?.map((entry) => entry.entries.id))
.flat()
.filter((id) => typeof id === "string") || []
)
}, [query.data, query.isLoading, query.isError])
useSyncUnreadWhenUnMatch(entriesIds)
return useMemo(() => {
return {
...query,
entriesIds,
}
}, [entriesIds, query])
}
export const usePrefetchEntryDetail = (entryId: string) => {

View File

@ -2,6 +2,7 @@ import type { FeedViewType } from "@follow/constants"
import { useMutation, useQuery } from "@tanstack/react-query"
import { useCallback, useEffect } from "react"
import { getEntry } from "../entry/getter"
import { useListFeedIds } from "../list/hooks"
import { useSubscriptionIdsByView } from "../subscription/hooks"
import { unreadCountAllSelector, unreadCountIdSelector, unreadCountIdsSelector } from "./selectors"
@ -15,6 +16,32 @@ export const usePrefetchUnread = () => {
})
}
export const useSyncUnreadWhenUnMatch = (entryIds: string[]) => {
useEffect(() => {
const entries = entryIds.map((id) => getEntry(id))
const unreadCountMap = entries.reduce(
(acc, entry) => {
if (entry && entry.feedId && !entry?.read) {
acc[entry.feedId] = (acc[entry.feedId] || 0) + 1
}
return acc
},
{} as Record<string, number>,
)
const unread = useUnreadStore.getState().data
const hasUnreadMismatch = Object.keys(unreadCountMap).some(
(feedId) =>
!unread[feedId] || (unreadCountMap[feedId] && unreadCountMap[feedId] > unread[feedId]),
)
if (hasUnreadMismatch) {
unreadSyncService.resetFromRemote()
}
}, [entryIds.toString()])
}
export const useAutoMarkAsRead = (entryId: string) => {
const { mutate } = useMutation({
mutationFn: (entryId: string) => unreadSyncService.markEntryAsRead(entryId),

View File

@ -2,6 +2,7 @@ import type { FeedViewType } from "@follow/constants"
import type { UnreadSchema } from "@follow/database/schemas/types"
import { EntryService } from "@follow/database/services/entry"
import { UnreadService } from "@follow/database/services/unread"
import { isEqual } from "es-toolkit"
import { setFeedUnreadDirty } from "../atoms/feed"
import { apiClient } from "../context"
@ -34,6 +35,10 @@ class UnreadSyncService {
query: {},
})
if (isEqual(res.data, get().data)) {
return res.data
}
await unreadActions.upsertMany(res.data, { reset: true })
return res.data
}