From 5eb960ed415595336279d1f8900d516e6bf3d4e0 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 3 Jul 2024 17:35:16 +0800 Subject: [PATCH] feat: feed unread persist to db Signed-off-by: Innei --- src/renderer/src/database/db.ts | 41 ++++--- src/renderer/src/database/db_schema.ts | 6 + .../src/database/models/entry-related.ts | 4 +- src/renderer/src/database/models/entry.ts | 4 +- .../src/database/models/feed-entry.ts | 4 +- src/renderer/src/database/models/feed.ts | 4 +- .../src/database/models/subscription.ts | 4 +- src/renderer/src/database/models/unread.ts | 10 ++ src/renderer/src/database/schemas/base.ts | 7 ++ src/renderer/src/database/schemas/entry.ts | 7 -- src/renderer/src/database/schemas/feed.ts | 7 ++ src/renderer/src/database/schemas/index.ts | 3 +- .../src/hooks/biz/useSubscriptionActions.tsx | 6 +- .../src/modules/discover/feed-form.tsx | 6 +- .../src/modules/feed-column/category.tsx | 6 +- src/renderer/src/modules/feed-column/item.tsx | 4 +- src/renderer/src/modules/feed-column/list.tsx | 6 +- .../src/providers/ui-setting-Initialize.tsx | 4 +- src/renderer/src/queries/subscriptions.ts | 4 +- src/renderer/src/services/feed-unread.ts | 24 ++++ src/renderer/src/services/index.ts | 1 + src/renderer/src/store/entry/store.ts | 6 +- src/renderer/src/store/subscription.ts | 4 +- src/renderer/src/store/unread.ts | 103 ++++++++++-------- src/renderer/src/store/utils/clear.ts | 4 +- src/renderer/src/store/utils/hydrate.ts | 10 +- 26 files changed, 183 insertions(+), 106 deletions(-) create mode 100644 src/renderer/src/database/models/unread.ts create mode 100644 src/renderer/src/database/schemas/base.ts delete mode 100644 src/renderer/src/database/schemas/entry.ts create mode 100644 src/renderer/src/database/schemas/feed.ts create mode 100644 src/renderer/src/services/feed-unread.ts diff --git a/src/renderer/src/database/db.ts b/src/renderer/src/database/db.ts index af02fa5aae..108168a875 100644 --- a/src/renderer/src/database/db.ts +++ b/src/renderer/src/database/db.ts @@ -1,20 +1,19 @@ +import type { Transaction } from "dexie" import Dexie from "dexie" import { LOCAL_DB_NAME } from "./constants" -import { - dbSchemaV1, -} from "./db_schema" -import type { DB_Entry } from "./schemas/entry" +import { dbSchemaV1, dbSchemaV2 } from "./db_schema" +import type { DB_Base } from "./schemas/base" +import type { DB_FeedId } from "./schemas/feed" import type { DBModel } from "./types" -export interface LobeDBSchemaMap { - - entries: DB_Entry - // TODO - Add more schemas here - feeds: DB_Entry - subscriptions: DB_Entry - entryRelated: DB_Entry - feedEntries: DB_Entry +export interface LocalDBSchemaMap { + entries: DB_Base + feeds: DB_Base + subscriptions: DB_Base + entryRelated: DB_Base + feedEntries: DB_FeedId + feedUnreads: DB_FeedId } // Define a local DB @@ -24,16 +23,25 @@ export class BrowserDB extends Dexie { public subscriptions: BrowserDBTable<"subscriptions"> public entryRelated: BrowserDBTable<"entryRelated"> public feedEntries: BrowserDBTable<"feedEntries"> + public feedUnreads: BrowserDBTable<"feedUnreads"> constructor() { super(LOCAL_DB_NAME) this.version(1).stores(dbSchemaV1) + this.version(2).stores(dbSchemaV2) + .upgrade(this.upgradeToV2) this.entries = this.table("entries") this.feeds = this.table("feeds") this.subscriptions = this.table("subscriptions") this.entryRelated = this.table("entryRelated") this.feedEntries = this.table("feedEntries") + this.feedUnreads = this.table("feedUnreads") + } + + async upgradeToV2(trans: Transaction) { + const session = trans.table("feedUnreads") + session.delete("feedId") } } @@ -47,9 +55,10 @@ export const browserDB = new BrowserDB() // types helper export type BrowserDBSchema = { - [t in keyof LobeDBSchemaMap]: { - model: LobeDBSchemaMap[t] - table: Dexie.Table, string> + [t in keyof LocalDBSchemaMap]: { + model: LocalDBSchemaMap[t] + table: Dexie.Table, string> }; } -type BrowserDBTable = BrowserDBSchema[T]["table"] +type BrowserDBTable = + BrowserDBSchema[T]["table"] diff --git a/src/renderer/src/database/db_schema.ts b/src/renderer/src/database/db_schema.ts index dd868a4e86..8918143d91 100644 --- a/src/renderer/src/database/db_schema.ts +++ b/src/renderer/src/database/db_schema.ts @@ -4,4 +4,10 @@ export const dbSchemaV1 = { subscriptions: "&id", entryRelated: "&id", feedEntries: "&feedId", + feedUnreads: "&id", +} + +export const dbSchemaV2 = { + ...dbSchemaV1, + subscriptions: "&id, userId, feedId", } diff --git a/src/renderer/src/database/models/entry-related.ts b/src/renderer/src/database/models/entry-related.ts index dc2d364e0b..3c4531ff8e 100644 --- a/src/renderer/src/database/models/entry-related.ts +++ b/src/renderer/src/database/models/entry-related.ts @@ -1,9 +1,9 @@ import { BaseModel } from "../model" -import { DB_EntrySchema } from "../schemas" +import { DB_BaseSchema } from "../schemas" class ModelStatic extends BaseModel<"entryRelated"> { constructor() { - super("entryRelated", DB_EntrySchema) + super("entryRelated", DB_BaseSchema) } } diff --git a/src/renderer/src/database/models/entry.ts b/src/renderer/src/database/models/entry.ts index c619d9403d..1d8e58aa91 100644 --- a/src/renderer/src/database/models/entry.ts +++ b/src/renderer/src/database/models/entry.ts @@ -1,9 +1,9 @@ import { BaseModel } from "../model" -import { DB_EntrySchema } from "../schemas" +import { DB_BaseSchema } from "../schemas" class EntryModelStatic extends BaseModel<"entries"> { constructor() { - super("entries", DB_EntrySchema) + super("entries", DB_BaseSchema) } } diff --git a/src/renderer/src/database/models/feed-entry.ts b/src/renderer/src/database/models/feed-entry.ts index 98666c3df3..825b606bc1 100644 --- a/src/renderer/src/database/models/feed-entry.ts +++ b/src/renderer/src/database/models/feed-entry.ts @@ -1,9 +1,9 @@ import { BaseModel } from "../model" -import { DB_EntrySchema } from "../schemas" +import { DB_BaseSchema } from "../schemas" class ModelStatic extends BaseModel<"feedEntries"> { constructor() { - super("feedEntries", DB_EntrySchema) + super("feedEntries", DB_BaseSchema) } } diff --git a/src/renderer/src/database/models/feed.ts b/src/renderer/src/database/models/feed.ts index 1c86b63885..9352cc457d 100644 --- a/src/renderer/src/database/models/feed.ts +++ b/src/renderer/src/database/models/feed.ts @@ -1,9 +1,9 @@ import { BaseModel } from "../model" -import { DB_EntrySchema } from "../schemas" +import { DB_BaseSchema } from "../schemas" class ModelStatic extends BaseModel<"feeds"> { constructor() { - super("feeds", DB_EntrySchema) + super("feeds", DB_BaseSchema) } } diff --git a/src/renderer/src/database/models/subscription.ts b/src/renderer/src/database/models/subscription.ts index e12af0368e..c0f83dc35c 100644 --- a/src/renderer/src/database/models/subscription.ts +++ b/src/renderer/src/database/models/subscription.ts @@ -1,9 +1,9 @@ import { BaseModel } from "../model" -import { DB_EntrySchema } from "../schemas" +import { DB_BaseSchema } from "../schemas" class ModelStatic extends BaseModel<"subscriptions"> { constructor() { - super("subscriptions", DB_EntrySchema) + super("subscriptions", DB_BaseSchema) } } diff --git a/src/renderer/src/database/models/unread.ts b/src/renderer/src/database/models/unread.ts new file mode 100644 index 0000000000..9124451f60 --- /dev/null +++ b/src/renderer/src/database/models/unread.ts @@ -0,0 +1,10 @@ +import { BaseModel } from "../model" +import { DB_BaseSchema } from "../schemas" + +class ModelStatic extends BaseModel<"feedUnreads"> { + constructor() { + super("feedUnreads", DB_BaseSchema) + } +} + +export const feedUnreadModel = new ModelStatic() diff --git a/src/renderer/src/database/schemas/base.ts b/src/renderer/src/database/schemas/base.ts new file mode 100644 index 0000000000..659d41fef6 --- /dev/null +++ b/src/renderer/src/database/schemas/base.ts @@ -0,0 +1,7 @@ +import { z } from "zod" + +export const DB_BaseSchema = z.object({ + id: z.string(), +}) + +export type DB_Base = z.infer diff --git a/src/renderer/src/database/schemas/entry.ts b/src/renderer/src/database/schemas/entry.ts deleted file mode 100644 index dc7f714c28..0000000000 --- a/src/renderer/src/database/schemas/entry.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from "zod" - -export const DB_EntrySchema = z.object({ - id: z.string(), -}) - -export type DB_Entry = z.infer diff --git a/src/renderer/src/database/schemas/feed.ts b/src/renderer/src/database/schemas/feed.ts new file mode 100644 index 0000000000..f729d3dbbd --- /dev/null +++ b/src/renderer/src/database/schemas/feed.ts @@ -0,0 +1,7 @@ +import { z } from "zod" + +export const DB_FeedIdSchema = z.object({ + feedId: z.string(), +}) + +export type DB_FeedId = z.infer diff --git a/src/renderer/src/database/schemas/index.ts b/src/renderer/src/database/schemas/index.ts index 6755e4a711..3fd6fdbf23 100644 --- a/src/renderer/src/database/schemas/index.ts +++ b/src/renderer/src/database/schemas/index.ts @@ -1 +1,2 @@ -export * from "./entry" +export * from "./base" +export * from "./feed" diff --git a/src/renderer/src/hooks/biz/useSubscriptionActions.tsx b/src/renderer/src/hooks/biz/useSubscriptionActions.tsx index 3512e1b0da..af18163b25 100644 --- a/src/renderer/src/hooks/biz/useSubscriptionActions.tsx +++ b/src/renderer/src/hooks/biz/useSubscriptionActions.tsx @@ -1,7 +1,7 @@ import { apiClient } from "@renderer/lib/api-fetch" import { Queries } from "@renderer/queries" import type { SubscriptionPlainModel } from "@renderer/store" -import { getFeedById, unreadActions } from "@renderer/store" +import { feedUnreadActions, getFeedById } from "@renderer/store" import { useMutation } from "@tanstack/react-query" import { toast } from "sonner" @@ -19,7 +19,7 @@ export const useDeleteSubscription = ({ onSuccess: (_, variables) => { Queries.subscription.byView(variables.view).invalidate() - unreadActions.updateByFeedId(variables.feedId, 0) + feedUnreadActions.updateByFeedId(variables.feedId, 0) const feed = getFeedById(variables.feedId) @@ -48,7 +48,7 @@ export const useDeleteSubscription = ({ }) Queries.subscription.byView(variables.view).invalidate() - unreadActions.fetchUnreadByView(variables.view) + feedUnreadActions.fetchUnreadByView(variables.view) }, }, }, diff --git a/src/renderer/src/modules/discover/feed-form.tsx b/src/renderer/src/modules/discover/feed-form.tsx index a8c3184262..d3c8a53cd2 100644 --- a/src/renderer/src/modules/discover/feed-form.tsx +++ b/src/renderer/src/modules/discover/feed-form.tsx @@ -30,7 +30,7 @@ import { FeedViewType } from "@renderer/lib/enum" import { cn } from "@renderer/lib/utils" import { Queries } from "@renderer/queries" import { useFeed } from "@renderer/queries/feed" -import { unreadActions } from "@renderer/store" +import { feedUnreadActions } from "@renderer/store" import { useMutation } from "@tanstack/react-query" import { useEffect, useRef } from "react" import { useForm } from "react-hook-form" @@ -106,13 +106,13 @@ export const FeedForm: Component<{ tipcClient?.invalidateQuery( Queries.subscription.byView(feed.data?.subscription?.view).key, ) - unreadActions.fetchUnreadByView(feed.data?.subscription?.view) + feedUnreadActions.fetchUnreadByView(feed.data?.subscription?.view) } Queries.subscription.byView(Number.parseInt(variables.view)).invalidate() tipcClient?.invalidateQuery( Queries.subscription.byView(Number.parseInt(variables.view)).key, ) - unreadActions.fetchUnreadByView(Number.parseInt(variables.view)) + feedUnreadActions.fetchUnreadByView(Number.parseInt(variables.view)) const feedId = feed.data?.feed.id if (feedId) { diff --git a/src/renderer/src/modules/feed-column/category.tsx b/src/renderer/src/modules/feed-column/category.tsx index 7d41d7091d..42efa5b997 100644 --- a/src/renderer/src/modules/feed-column/category.tsx +++ b/src/renderer/src/modules/feed-column/category.tsx @@ -9,7 +9,7 @@ import { stopPropagation } from "@renderer/lib/dom" import { showNativeMenu } from "@renderer/lib/native-menu" import { cn } from "@renderer/lib/utils" import type { FeedListModel } from "@renderer/models" -import { useUnreadStore } from "@renderer/store" +import { useFeedUnreadStore } from "@renderer/store" import { AnimatePresence, m } from "framer-motion" import { memo, useEffect, useState } from "react" @@ -56,11 +56,11 @@ function FeedCategoryImpl({ } } - const unread = useUnreadStore((state) => + const unread = useFeedUnreadStore((state) => data.list.reduce((acc, cur) => (state.data[cur.feedId] || 0) + acc, 0), ) - const sortByUnreadFeedList = useUnreadStore((state) => + const sortByUnreadFeedList = useFeedUnreadStore((state) => data.list.sort( (a, b) => (state.data[b.feedId] || 0) - (state.data[a.feedId] || 0), ), diff --git a/src/renderer/src/modules/feed-column/item.tsx b/src/renderer/src/modules/feed-column/item.tsx index 62befe78ab..79159a7d03 100644 --- a/src/renderer/src/modules/feed-column/item.tsx +++ b/src/renderer/src/modules/feed-column/item.tsx @@ -18,7 +18,7 @@ import { nextFrame } from "@renderer/lib/dom" import { showNativeMenu } from "@renderer/lib/native-menu" import { cn } from "@renderer/lib/utils" import type { SubscriptionPlainModel } from "@renderer/store" -import { getFeedById, useFeedById, useUnreadStore } from "@renderer/store" +import { getFeedById, useFeedById, useFeedUnreadStore } from "@renderer/store" import { WEB_URL } from "@shared/constants" import { memo, useCallback } from "react" @@ -60,7 +60,7 @@ const FeedItemImpl = ({ const deleteSubscription = useDeleteSubscription({}) - const feedUnread = useUnreadStore( + const feedUnread = useFeedUnreadStore( (state) => state.data[subscription.feedId] || 0, ) const { present } = useModalStack() diff --git a/src/renderer/src/modules/feed-column/list.tsx b/src/renderer/src/modules/feed-column/list.tsx index 206a0d36a2..f5f47ccfb1 100644 --- a/src/renderer/src/modules/feed-column/list.tsx +++ b/src/renderer/src/modules/feed-column/list.tsx @@ -11,8 +11,8 @@ import { Queries } from "@renderer/queries" import type { SubscriptionPlainModel } from "@renderer/store" import { getFeedById, + useFeedUnreadStore, useSubscriptionByView, - useUnreadStore, } from "@renderer/store" import { useMemo, useState } from "react" import { Link } from "react-router-dom" @@ -103,7 +103,7 @@ export function FeedList({ useAuthQuery(Queries.subscription.unreadAll()) - const totalUnread = useUnreadStore((state) => { + const totalUnread = useFeedUnreadStore((state) => { let unread = 0 data?.list.forEach((a) => { a.list.forEach((b) => { @@ -113,7 +113,7 @@ export function FeedList({ return unread }) - const sortedByUnread = useUnreadStore((state) => + const sortedByUnread = useFeedUnreadStore((state) => data?.list?.sort( (a, b) => b.list.reduce((acc, cur) => (state.data[cur.feedId] || 0) + acc, 0) - diff --git a/src/renderer/src/providers/ui-setting-Initialize.tsx b/src/renderer/src/providers/ui-setting-Initialize.tsx index 3f9efc8330..3734d63ac0 100644 --- a/src/renderer/src/providers/ui-setting-Initialize.tsx +++ b/src/renderer/src/providers/ui-setting-Initialize.tsx @@ -1,6 +1,6 @@ import { initializeDefaultUISettings, useUISettingValue } from "@renderer/atoms" import { tipcClient } from "@renderer/lib/client" -import { unreadActions } from "@renderer/store" +import { feedUnreadActions } from "@renderer/store" import { useEffect, useInsertionEffect } from "react" initializeDefaultUISettings() @@ -14,7 +14,7 @@ export const UISettingInitialize = () => { useEffect(() => { if (state.showDockBadge) { - return unreadActions.subscribeUnreadCount((count) => tipcClient?.setMacOSBadge(count), true) + return feedUnreadActions.subscribeUnreadCount((count) => tipcClient?.setMacOSBadge(count), true) } else { tipcClient?.setMacOSBadge(0) } diff --git a/src/renderer/src/queries/subscriptions.ts b/src/renderer/src/queries/subscriptions.ts index 3079ef9535..84042ef3c8 100644 --- a/src/renderer/src/queries/subscriptions.ts +++ b/src/renderer/src/queries/subscriptions.ts @@ -1,7 +1,7 @@ import { apiClient } from "@renderer/lib/api-fetch" import { defineQuery } from "@renderer/lib/defineQuery" import type { FeedViewType } from "@renderer/lib/enum" -import { subscriptionActions, unreadActions } from "@renderer/store" +import { feedUnreadActions, subscriptionActions } from "@renderer/store" export const subscription = { byView: (view?: FeedViewType) => @@ -22,5 +22,5 @@ export const subscription = { }), unreadAll: () => - defineQuery(["unread-all"], async () => unreadActions.fetchUnreadAll()), + defineQuery(["unread-all"], async () => feedUnreadActions.fetchUnreadAll()), } diff --git a/src/renderer/src/services/feed-unread.ts b/src/renderer/src/services/feed-unread.ts new file mode 100644 index 0000000000..7b6454df24 --- /dev/null +++ b/src/renderer/src/services/feed-unread.ts @@ -0,0 +1,24 @@ +import { feedUnreadModel } from "@renderer/database/models/unread" + +class ServiceStatic { + updateFeedUnread(list: [string, number][]) { + return feedUnreadModel.table.bulkPut( + list.map(([feedId, count]) => ({ id: feedId, count })), + ) + } + + getAll() { + return feedUnreadModel.table.toArray() as Promise< + { + id: string + count: number + }[] + > + } + + clear() { + return feedUnreadModel.table.clear() + } +} + +export const FeedUnreadService = new ServiceStatic() diff --git a/src/renderer/src/services/index.ts b/src/renderer/src/services/index.ts index 4e626bebfd..7939d25e17 100644 --- a/src/renderer/src/services/index.ts +++ b/src/renderer/src/services/index.ts @@ -2,4 +2,5 @@ export * from "./entry" export * from "./entry-related" export * from "./feed" export * from "./feed-entry" +export * from "./feed-unread" export * from "./subscription" diff --git a/src/renderer/src/store/entry/store.ts b/src/renderer/src/store/entry/store.ts index 25028e8c43..80921d6126 100644 --- a/src/renderer/src/store/entry/store.ts +++ b/src/renderer/src/store/entry/store.ts @@ -6,7 +6,7 @@ import { produce } from "immer" import { merge, omit } from "lodash-es" import { feedActions } from "../feed" -import { unreadActions } from "../unread" +import { feedUnreadActions } from "../unread" import { createZustandStore, getStoreActions } from "../utils/helper" import { isHydrated } from "../utils/hydrate" import type { EntryActions, EntryState } from "./types" @@ -187,7 +187,7 @@ export const useEntryStore = createZustandStore( }, markRead: (feedId: string, entryId: string, read: boolean) => { - unreadActions.incrementByFeedId(feedId, read ? -1 : 1) + feedUnreadActions.incrementByFeedId(feedId, read ? -1 : 1) entryActions.patch(entryId, { read, }) @@ -198,7 +198,7 @@ export const useEntryStore = createZustandStore( entries.forEach((entryId) => { entryActions.markRead(feedId, entryId, true) }) - unreadActions.updateByFeedId(feedId, 0) + feedUnreadActions.updateByFeedId(feedId, 0) }, markStar: (entryId, star) => { diff --git a/src/renderer/src/store/subscription.ts b/src/renderer/src/store/subscription.ts index d3d201f9c1..18e5ed43a5 100644 --- a/src/renderer/src/store/subscription.ts +++ b/src/renderer/src/store/subscription.ts @@ -7,7 +7,7 @@ import { omit } from "lodash-es" import { entryActions } from "./entry/store" import { feedActions } from "./feed" -import { unreadActions } from "./unread" +import { feedUnreadActions } from "./unread" import { createZustandStore, getStoreActions } from "./utils/helper" import { isHydrated } from "./utils/hydrate" @@ -95,7 +95,7 @@ export const useSubscriptionStore = createZustandStore< const state = get() for (const feedId in state.data) { if (state.data[feedId].view === view) { - unreadActions.updateByFeedId(feedId, 0) + feedUnreadActions.updateByFeedId(feedId, 0) entryActions.patchManyByFeedId(feedId, { read: true }) } } diff --git a/src/renderer/src/store/unread.ts b/src/renderer/src/store/unread.ts index 3cf97bfe67..f3703ad15b 100644 --- a/src/renderer/src/store/unread.ts +++ b/src/renderer/src/store/unread.ts @@ -1,42 +1,63 @@ -/** - * Store for `feed` unread count - */ import { apiClient } from "@renderer/lib/api-fetch" import type { FeedViewType } from "@renderer/lib/enum" -import { produce } from "immer" +import { FeedUnreadService } from "@renderer/services" import { createZustandStore, getStoreActions } from "./utils/helper" interface UnreadState { data: Record } -interface UnreadActions { +interface FeedUnreadActions { updateByFeedId: (feedId: string, unread: number) => void fetchUnreadByView: (view?: FeedViewType) => Promise> fetchUnreadAll: () => Promise> incrementByFeedId: (feedId: string, inc: number) => void - subscribeUnreadCount: (fn: (count: number) => void, immediately?: boolean) => () => void + subscribeUnreadCount: ( + fn: (count: number) => void, + immediately?: boolean + ) => () => void internal_reset: () => void + internal_setValue: (data: [string, number][]) => void clear: () => void + + hydrate: ( + data: { + id: string + count: number + }[], + ) => void } -export const useUnreadStore = createZustandStore( - "unread", - { - version: 1, - }, -)((set, get) => ({ +/** + * Store for `feed` unread count + */ +export const useFeedUnreadStore = createZustandStore< + UnreadState & FeedUnreadActions +>("unread", { + version: 1, +})((set, get) => ({ data: {}, internal_reset() { set({ data: {} }) + FeedUnreadService.clear() }, clear() { this.internal_reset() }, + internal_setValue(data) { + set((state) => { + state.data = { ...state.data } + for (const [key, value] of data) { + state.data[key] = value + } + FeedUnreadService.updateFeedUnread(data) + return { ...state } + }) + }, async fetchUnreadByView(view) { const unread = await apiClient.reads.$get({ @@ -45,13 +66,8 @@ export const useUnreadStore = createZustandStore( const { data } = unread - set((state) => - produce(state, (state) => { - for (const [key, value] of Object.entries(data)) { - state.data[key] = value - } - }), - ) + get().internal_setValue(Object.entries(data)) + return data }, async fetchUnreadAll() { @@ -61,39 +77,22 @@ export const useUnreadStore = createZustandStore( const { data } = unread this.internal_reset() - set((state) => - produce(state, (state) => { - for (const [key, value] of Object.entries(data)) { - state.data[key] = value - } - }), - ) + + get().internal_setValue(Object.entries(data)) return data }, incrementByFeedId: (feedId, inc: number) => { - set((state) => - produce(state, (state) => { - const cur = state.data[feedId] - if (cur === undefined) { - state.data[feedId] = Math.max(0, inc) - return state - } - state.data[feedId] = Math.max(0, (cur || 0) + inc) - return state - }), - ) + const state = get() + const cur = state.data[feedId] + + state.internal_setValue([[feedId, Math.max(0, (cur || 0) + inc)]]) }, updateByFeedId: (feedId, unread) => { - set((state) => - produce(state, (state) => { - state.data[feedId] = unread - return state - }), - ) + get().internal_setValue([[feedId, unread]]) }, subscribeUnreadCount(fn, immediately) { - const handler = (state: UnreadState & UnreadActions): void => { + const handler = (state: UnreadState & FeedUnreadActions): void => { let unread = 0 for (const key in state.data) { unread += state.data[key] @@ -104,8 +103,20 @@ export const useUnreadStore = createZustandStore( if (immediately) { handler(get()) } - return useUnreadStore.subscribe(handler) + return useFeedUnreadStore.subscribe(handler) + }, + + hydrate(data) { + set((state) => { + state.data = { ...state.data } + + for (const { id, count } of data) { + state.data[id] = count + } + + return { ...state } + }) }, })) -export const unreadActions = getStoreActions(useUnreadStore) +export const feedUnreadActions = getStoreActions(useFeedUnreadStore) diff --git a/src/renderer/src/store/utils/clear.ts b/src/renderer/src/store/utils/clear.ts index 2f40fde46f..27af502b1e 100644 --- a/src/renderer/src/store/utils/clear.ts +++ b/src/renderer/src/store/utils/clear.ts @@ -4,14 +4,14 @@ import { browserDB } from "@renderer/database" import { entryActions } from "../entry" import { feedActions } from "../feed" import { subscriptionActions } from "../subscription" -import { unreadActions } from "../unread" +import { feedUnreadActions } from "../unread" export const clearLocalPersistStoreData = () => { // All clear and reset method will aggregate here [ entryActions, subscriptionActions, - unreadActions, + feedUnreadActions, feedActions, ].forEach((actions) => { actions.clear() diff --git a/src/renderer/src/store/utils/hydrate.ts b/src/renderer/src/store/utils/hydrate.ts index 28453e04c7..2dd254fbe4 100644 --- a/src/renderer/src/store/utils/hydrate.ts +++ b/src/renderer/src/store/utils/hydrate.ts @@ -4,12 +4,14 @@ import { EntryRelatedService, EntryService, FeedService, + FeedUnreadService, SubscriptionService, } from "@renderer/services" import { entryActions, useEntryStore } from "../entry/store" import { feedActions, useFeedStore } from "../feed" import { subscriptionActions } from "../subscription" +import { feedUnreadActions } from "../unread" // This flag controls write data in indexedDB, if it's false, pass data insert to db // When app not ready, it's false, after hydrate data, it's true @@ -24,7 +26,7 @@ export const isHydrated = () => _isHydrated export const hydrateDatabaseToStore = async () => { const now = Date.now() - const [feeds] = await Promise.all([hydrateFeed(), hydrateSubscription()]) + const [feeds] = await Promise.all([hydrateFeed(), hydrateSubscription(), hydrateFeedUnread()]) await hydrateEntry(feeds) _isHydrated = true @@ -36,6 +38,12 @@ async function hydrateFeed() { feedActions.upsertMany(feeds) return useFeedStore.getState().feeds } + +async function hydrateFeedUnread() { + const unread = await FeedUnreadService.getAll() + + return feedUnreadActions.hydrate(unread) +} async function hydrateEntry(feedMap: Record) { const [entries, entryRelated, feedEntries, collections] = await Promise.all([ EntryService.findAll(),