fix: mark feed unread dirty, refetch unread next time, fixed #1830
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
3cb6364462
commit
58d0e9c190
|
|
@ -0,0 +1,69 @@
|
|||
import { jotaiStore } from "@follow/utils/jotai"
|
||||
import { isBizId } from "@follow/utils/utils"
|
||||
import { atom, useAtomValue } from "jotai"
|
||||
import { selectAtom } from "jotai/utils"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import {
|
||||
FEED_COLLECTION_LIST,
|
||||
INBOX_PREFIX_ID,
|
||||
ROUTE_FEED_IN_LIST,
|
||||
ROUTE_FEED_PENDING,
|
||||
} from "~/constants"
|
||||
|
||||
const feedUnreadDirtySetAtom = atom(new Set<string>())
|
||||
|
||||
// 1. feedId may be feedId, or `inbox-id` or `feedId, feedId,` or `list-id`, or `all`, or `collections`
|
||||
export const useFeedUnreadIsDirty = (feedId: string) => {
|
||||
return useAtomValue(
|
||||
useMemo(
|
||||
() =>
|
||||
selectAtom(feedUnreadDirtySetAtom, (set) => {
|
||||
const isRealFeedId = isBizId(feedId)
|
||||
if (isRealFeedId) return set.has(feedId)
|
||||
|
||||
if (feedId.startsWith(ROUTE_FEED_IN_LIST) || feedId.startsWith(INBOX_PREFIX_ID)) {
|
||||
// List/Inbox is not supported unread
|
||||
return false
|
||||
}
|
||||
|
||||
if (feedId === ROUTE_FEED_PENDING) {
|
||||
return set.size > 0
|
||||
}
|
||||
|
||||
if (feedId === FEED_COLLECTION_LIST) {
|
||||
// Entry in collections has not unread status
|
||||
return false
|
||||
}
|
||||
|
||||
const splitted = feedId.split(",")
|
||||
let isDirty = false
|
||||
for (const feedId of splitted) {
|
||||
if (isBizId(feedId)) {
|
||||
isDirty = isDirty || set.has(feedId)
|
||||
|
||||
if (isDirty) break
|
||||
}
|
||||
}
|
||||
return isDirty
|
||||
}),
|
||||
[feedId],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const setFeedUnreadDirty = (feedId: string) => {
|
||||
jotaiStore.set(feedUnreadDirtySetAtom, (prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.add(feedId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
||||
export const clearFeedUnreadDirty = (feedId: string) => {
|
||||
jotaiStore.set(feedUnreadDirtySetAtom, (prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(feedId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
|
@ -40,5 +40,5 @@ export const useMarkAllByRoute = (filter?: MarkAllFilter) => {
|
|||
filter,
|
||||
})
|
||||
}
|
||||
}, [routerParams, folderIds, view, filter])
|
||||
}, [routerParams, inboxId, folderIds, view, filter])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useFeedUnreadIsDirty } from "~/atoms/feed"
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useAuthInfiniteQuery, useAuthQuery } from "~/hooks/common"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
|
|
@ -144,6 +145,9 @@ export const useEntries = ({
|
|||
isArchived?: boolean
|
||||
}) => {
|
||||
const reduceRefetch = useGeneralSettingKey("reduceRefetch")
|
||||
const fetchUnread = read === false
|
||||
const feedUnreadDirty = useFeedUnreadIsDirty((feedId as string) || "")
|
||||
|
||||
return useAuthInfiniteQuery(
|
||||
entries.entries({ feedId, inboxId, listId, view, read, isArchived }),
|
||||
{
|
||||
|
|
@ -155,8 +159,16 @@ export const useEntries = ({
|
|||
initialPageParam: undefined,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: fetchUnread && feedUnreadDirty ? "always" : false,
|
||||
|
||||
staleTime: reduceRefetch ? maxStaleTime : defaultStaleTime,
|
||||
staleTime:
|
||||
// Force refetch unread entries when feed is dirty
|
||||
fetchUnread && feedUnreadDirty
|
||||
? 0
|
||||
: // Keep reduce data fetch logic
|
||||
reduceRefetch
|
||||
? maxStaleTime
|
||||
: defaultStaleTime,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { omitObjectUndefinedValue } from "@follow/utils/utils"
|
|||
import { isNil, merge, omit } from "es-toolkit/compat"
|
||||
import { produce } from "immer"
|
||||
|
||||
import { clearFeedUnreadDirty, setFeedUnreadDirty } from "~/atoms/feed"
|
||||
import { runTransactionInScope } from "~/database"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { getEntriesParams } from "~/lib/utils"
|
||||
|
|
@ -123,43 +124,61 @@ class EntryActions {
|
|||
pageParam?: string
|
||||
isArchived?: boolean
|
||||
}) {
|
||||
const data = inboxId
|
||||
? await apiClient.entries.inbox
|
||||
.$post({
|
||||
json: {
|
||||
publishedAfter: pageParam,
|
||||
limit,
|
||||
inboxId: `${inboxId}`,
|
||||
read,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
return {
|
||||
...res,
|
||||
data: res.data?.map(({ feeds, ...d }) => {
|
||||
return {
|
||||
...d,
|
||||
inboxes: feeds,
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
: await apiClient.entries.$post({
|
||||
if (inboxId) {
|
||||
const data = await apiClient.entries.inbox
|
||||
.$post({
|
||||
json: {
|
||||
publishedAfter: pageParam,
|
||||
read,
|
||||
limit,
|
||||
isArchived,
|
||||
// withContent: true,
|
||||
...getEntriesParams({
|
||||
feedId,
|
||||
inboxId,
|
||||
listId,
|
||||
view,
|
||||
}),
|
||||
inboxId: `${inboxId}`,
|
||||
read,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
return {
|
||||
...res,
|
||||
data: res.data?.map(({ feeds, ...d }) => {
|
||||
return {
|
||||
...d,
|
||||
inboxes: feeds,
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
if (data.data) {
|
||||
this.upsertMany(data.data, { isArchived })
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const params = getEntriesParams({
|
||||
feedId,
|
||||
inboxId,
|
||||
listId,
|
||||
view,
|
||||
})
|
||||
const data = await apiClient.entries.$post({
|
||||
json: {
|
||||
publishedAfter: pageParam,
|
||||
read,
|
||||
limit,
|
||||
isArchived,
|
||||
...params,
|
||||
},
|
||||
})
|
||||
|
||||
// Mark feed unread dirty, so re-fetch the unread data when view feed unread entires in the next time
|
||||
if (read === false) {
|
||||
if (params.feedId) {
|
||||
clearFeedUnreadDirty(params.feedId as string)
|
||||
}
|
||||
if (params.feedIdList) {
|
||||
params.feedIdList.forEach((feedId) => {
|
||||
clearFeedUnreadDirty(feedId)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (data.data) {
|
||||
this.upsertMany(data.data, { isArchived })
|
||||
}
|
||||
|
|
@ -431,7 +450,7 @@ class EntryActions {
|
|||
|
||||
const tx = createTransaction<unknown, { prevUnread: number }>({})
|
||||
|
||||
tx.optimistic(async (_, ctx) => {
|
||||
tx.optimistic((_, ctx) => {
|
||||
const prevUnread = feedUnreadActions.incrementByFeedId(feedId, read ? -1 : 1)
|
||||
ctx.prevUnread = prevUnread
|
||||
|
||||
|
|
@ -456,13 +475,13 @@ class EntryActions {
|
|||
}
|
||||
})
|
||||
|
||||
tx.persist(async () => {
|
||||
tx.persist(() => {
|
||||
EntryService.bulkStoreReadStatus({
|
||||
[entryId]: read,
|
||||
})
|
||||
})
|
||||
|
||||
tx.rollback(async (_, ctx) => {
|
||||
tx.rollback((_, ctx) => {
|
||||
feedUnreadActions.updateByFeedId(feedId, ctx.prevUnread)
|
||||
this.patch(entryId, {
|
||||
read: !read,
|
||||
|
|
@ -470,12 +489,14 @@ class EntryActions {
|
|||
})
|
||||
|
||||
await tx.run()
|
||||
|
||||
setFeedUnreadDirty(feedId)
|
||||
}
|
||||
|
||||
async markStar(entryId: string, star: boolean, view?: FeedViewType) {
|
||||
const tx = createTransaction<unknown, { prevIsStar: boolean }>({})
|
||||
|
||||
tx.optimistic(async (_, ctx) => {
|
||||
tx.optimistic((_, ctx) => {
|
||||
ctx.prevIsStar = !!get().flatMapEntries[entryId]?.collections?.createdAt
|
||||
this.patch(entryId, {
|
||||
collections: star
|
||||
|
|
@ -508,7 +529,7 @@ class EntryActions {
|
|||
}
|
||||
})
|
||||
|
||||
tx.rollback(async (_, ctx) => {
|
||||
tx.rollback((_, ctx) => {
|
||||
set((state) =>
|
||||
produce(state, (state) => {
|
||||
ctx.prevIsStar ? state.starIds.add(entryId) : state.starIds.delete(entryId)
|
||||
|
|
@ -555,7 +576,7 @@ class EntryActions {
|
|||
deletedIndex: -1,
|
||||
})
|
||||
|
||||
tx.optimistic(async (entry, ctx) => {
|
||||
tx.optimistic((entry, ctx) => {
|
||||
const { inboxId } = entry
|
||||
const fullInboxId = `inbox-${inboxId}`
|
||||
|
||||
|
|
@ -594,7 +615,7 @@ class EntryActions {
|
|||
await EntryService.deleteEntries([entryId])
|
||||
})
|
||||
|
||||
tx.rollback(async (entry, ctx) => {
|
||||
tx.rollback((entry, ctx) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
entries: {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { omit } from "es-toolkit/compat"
|
|||
import { produce } from "immer"
|
||||
import { parse } from "tldts"
|
||||
|
||||
import { setFeedUnreadDirty } from "~/atoms/feed"
|
||||
import { whoami } from "~/atoms/user"
|
||||
import { runTransactionInScope } from "~/database"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
|
|
@ -270,6 +271,11 @@ class SubscriptionActions {
|
|||
await feedUnreadActions.fetchUnreadByView(view)
|
||||
})
|
||||
await tx.run()
|
||||
|
||||
const feedIdsInView = get().feedIdByView[view]
|
||||
for (const feedId of feedIdsInView) {
|
||||
setFeedUnreadDirty(feedId)
|
||||
}
|
||||
}
|
||||
|
||||
async markReadByFeedIds({
|
||||
|
|
@ -307,7 +313,7 @@ class SubscriptionActions {
|
|||
},
|
||||
})
|
||||
})
|
||||
tx.optimistic(async () => {
|
||||
tx.optimistic(() => {
|
||||
if (listId) {
|
||||
feedUnreadActions.updateByFeedId(listId, 0)
|
||||
} else if (inboxId) {
|
||||
|
|
@ -331,6 +337,10 @@ class SubscriptionActions {
|
|||
})
|
||||
|
||||
await tx.run()
|
||||
|
||||
for (const feedId of stableFeedIds) {
|
||||
setFeedUnreadDirty(feedId)
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { FeedViewType } from "@follow/constants"
|
||||
|
||||
import { setFeedUnreadDirty } from "~/atoms/feed"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { FeedUnreadService } from "~/services"
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ class FeedUnreadActions {
|
|||
const nextValue = Math.max(0, (cur || 0) + inc)
|
||||
|
||||
this.internal_setValue([[feedId, nextValue]])
|
||||
setFeedUnreadDirty(feedId)
|
||||
return cur
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue