{
- e.stopPropagation()
- setFeedActive(feed)
- }}
+ onClick={handleNavigate}
onDoubleClick={() => {
window.open(
- `${import.meta.env.VITE_WEB_URL}/feed/${feed.feedId}?view=${view}`,
+ `${import.meta.env.VITE_WEB_URL}/feed/${
+ subscription.feedId
+ }?view=${view}`,
"_blank",
)
}}
@@ -126,14 +141,20 @@ export function FeedItem({
click: () => {
present({
title: "Edit Feed",
- content: ({ dismiss }) =>
,
+ content: ({ dismiss }) => (
+
+ ),
})
},
},
{
type: "text",
label: "Unfollow",
- click: () => deleteMutation.mutate(feed),
+ click: () => deleteMutation.mutate(subscription),
},
{
type: "separator",
@@ -144,7 +165,7 @@ export function FeedItem({
click: () =>
window.open(
`${import.meta.env.VITE_WEB_URL}/feed/${
- feed.feedId
+ subscription.feedId
}?view=${view}`,
"_blank",
),
@@ -152,8 +173,12 @@ export function FeedItem({
{
type: "text",
label: "Open Site in Browser",
- click: () =>
- feed.feeds.siteUrl && window.open(feed.feeds.siteUrl, "_blank"),
+ click: () => {
+ const feed = getFeedById(subscription.feedId)
+ if (feed) {
+ feed.siteUrl && window.open(feed.siteUrl, "_blank")
+ }
+ },
},
],
e,
@@ -163,12 +188,12 @@ export function FeedItem({
-
-
{feed.feeds.title}
- {feed.feeds.errorAt && (
+
+
{feed.title}
+ {feed.errorAt && (
@@ -180,7 +205,7 @@ export function FeedItem({
{" "}
{dayjs
.duration(
- dayjs(feed.feeds.errorAt).diff(dayjs(), "minute"),
+ dayjs(feed.errorAt).diff(dayjs(), "minute"),
"minute",
)
.humanize(true)}
@@ -189,7 +214,7 @@ export function FeedItem({
)}
- {feed.isPrivate && (
+ {subscription.isPrivate && (
@@ -210,3 +235,5 @@ export function FeedItem({
)
}
+
+export const FeedItem = memo(FeedItemImpl)
diff --git a/src/renderer/src/modules/feed-column/list.tsx b/src/renderer/src/modules/feed-column/list.tsx
index 69fce739f..3c8672093 100644
--- a/src/renderer/src/modules/feed-column/list.tsx
+++ b/src/renderer/src/modules/feed-column/list.tsx
@@ -1,13 +1,17 @@
import { useBizQuery } from "@renderer/hooks"
+import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
+import { useRouteFeedId } from "@renderer/hooks/biz/useRouteParams"
import { FEED_COLLECTION_LIST, levels, views } from "@renderer/lib/constants"
import { stopPropagation } from "@renderer/lib/dom"
import type { FeedViewType } from "@renderer/lib/enum"
import { cn } from "@renderer/lib/utils"
-import type { FeedListModel, SubscriptionResponse } from "@renderer/models"
+import type {
+ FeedListModel,
+} from "@renderer/models"
import { Queries } from "@renderer/queries"
+import type { SubscriptionPlainModel } from "@renderer/store"
import {
- feedActions,
- useFeedActiveList,
+ getFeedById,
useSubscriptionByView,
useUnreadStore,
} from "@renderer/store"
@@ -30,7 +34,7 @@ const useData = (view: FeedViewType) => {
list: Record<
string,
{
- list: SubscriptionResponse
+ list: SubscriptionPlainModel[]
}
>
}
@@ -38,8 +42,9 @@ const useData = (view: FeedViewType) => {
const subscriptions = structuredClone(data)
for (const subscription of subscriptions) {
- if (!subscription.category && subscription.feeds.siteUrl) {
- const { domain } = parse(subscription.feeds.siteUrl)
+ const feed = getFeedById(subscription.feedId)
+ if (!subscription.category && feed && feed.siteUrl) {
+ const { domain } = parse(feed.siteUrl)
if (domain) {
if (!domains[domain]) {
domains[domain] = 0
@@ -50,11 +55,13 @@ const useData = (view: FeedViewType) => {
}
for (const subscription of subscriptions) {
+ const feed = getFeedById(subscription.feedId)
+ if (!feed) continue
if (!subscription.category) {
- if (subscription.feeds.siteUrl) {
+ if (feed.siteUrl) {
// FIXME @DIYgod
// The logic here makes it impossible to remove the auto-generated category based on domain
- const { domain } = parse(subscription.feeds.siteUrl)
+ const { domain } = parse(feed.siteUrl)
if (domain && domains[domain] > 1) {
subscription.category =
domain.slice(0, 1).toUpperCase() + domain.slice(1)
@@ -94,7 +101,6 @@ export function FeedList({
}) {
const [expansion, setExpansion] = useState(false)
const data = useData(view)
- const activeList = useFeedActiveList()
useBizQuery(Queries.subscription.unreadAll())
@@ -108,8 +114,6 @@ export function FeedList({
return unread
})
- const { setActiveList } = feedActions
-
const sortedByUnread = useUnreadStore((state) =>
data?.list?.sort(
(a, b) =>
@@ -118,6 +122,9 @@ export function FeedList({
),
)
+ const feedId = useRouteFeedId()
+ const navigate = useNavigateEntry()
+
return (
{!hideTitle && (
@@ -130,10 +137,10 @@ export function FeedList({
onClick={(e) => {
e.stopPropagation()
if (view !== undefined) {
- setActiveList({
+ navigate({
+ entryId: null,
+ feedId: null,
level: levels.view,
- id: view,
- name: views[view].name,
view,
})
}
@@ -160,15 +167,15 @@ export function FeedList({
{
e.stopPropagation()
if (view !== undefined) {
- setActiveList({
- level: levels.folder,
- id: FEED_COLLECTION_LIST,
- name: "Collections",
+ navigate({
+ entryId: null,
+ feedId: FEED_COLLECTION_LIST,
+ level: levels.feed,
view,
})
}
diff --git a/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx b/src/renderer/src/pages/(external)/(with-layout)/feed/[id]/index.tsx
similarity index 100%
rename from src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx
rename to src/renderer/src/pages/(external)/(with-layout)/feed/[id]/index.tsx
diff --git a/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/layout.tsx b/src/renderer/src/pages/(external)/(with-layout)/feed/[id]/layout.tsx
similarity index 100%
rename from src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/layout.tsx
rename to src/renderer/src/pages/(external)/(with-layout)/feed/[id]/layout.tsx
diff --git a/src/renderer/src/pages/(main)/(context)/layout.tsx b/src/renderer/src/pages/(main)/(context)/layout.tsx
deleted file mode 100644
index 0341d292e..000000000
--- a/src/renderer/src/pages/(main)/(context)/layout.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { setMainContainerElement } from "@renderer/atoms"
-import { FeedColumn } from "@renderer/modules/feed-column"
-import { Outlet } from "react-router-dom"
-
-export function Component() {
- return (
-
-
-
-
- {/* NOTE: tabIndex for main element can get by `document.activeElement` */}
-
-
-
-
- )
-}
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/discover/index.tsx b/src/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx
similarity index 100%
rename from src/renderer/src/pages/(main)/(context)/(nonhome)/discover/index.tsx
rename to src/renderer/src/pages/(main)/(layer)/(subview)/discover/index.tsx
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/discover/layout.tsx b/src/renderer/src/pages/(main)/(layer)/(subview)/discover/layout.tsx
similarity index 100%
rename from src/renderer/src/pages/(main)/(context)/(nonhome)/discover/layout.tsx
rename to src/renderer/src/pages/(main)/(layer)/(subview)/discover/layout.tsx
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/layout.tsx b/src/renderer/src/pages/(main)/(layer)/(subview)/layout.tsx
similarity index 100%
rename from src/renderer/src/pages/(main)/(context)/(nonhome)/layout.tsx
rename to src/renderer/src/pages/(main)/(layer)/(subview)/layout.tsx
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx b/src/renderer/src/pages/(main)/(layer)/(subview)/profile/index.tsx
similarity index 100%
rename from src/renderer/src/pages/(main)/(context)/(nonhome)/profile/index.tsx
rename to src/renderer/src/pages/(main)/(layer)/(subview)/profile/index.tsx
diff --git a/src/renderer/src/pages/(main)/(context)/(nonhome)/profile/layout.tsx b/src/renderer/src/pages/(main)/(layer)/(subview)/profile/layout.tsx
similarity index 100%
rename from src/renderer/src/pages/(main)/(context)/(nonhome)/profile/layout.tsx
rename to src/renderer/src/pages/(main)/(layer)/(subview)/profile/layout.tsx
diff --git a/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/[entryId]/index.tsx b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/[entryId]/index.tsx
new file mode 100644
index 000000000..190cba1d8
--- /dev/null
+++ b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/[entryId]/index.tsx
@@ -0,0 +1,20 @@
+import { useRouteView } from "@renderer/hooks/biz/useRouteParams"
+import { ROUTE_FEED_PENDING, views } from "@renderer/lib/constants"
+import { EntryContent } from "@renderer/modules/entry-content"
+import { AnimatePresence } from "framer-motion"
+import { useParams } from "react-router-dom"
+
+export const Component = () => {
+ const { entryId } = useParams()
+ const view = useRouteView()
+ const inWideMode = view ? views[view].wideMode : false
+ return (
+
+ {!inWideMode && (
+
+
+
+ )}
+
+ )
+}
diff --git a/src/renderer/src/pages/(main)/(context)/index.tsx b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx
similarity index 61%
rename from src/renderer/src/pages/(main)/(context)/index.tsx
rename to src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx
index 7ddd04a43..762b5d617 100644
--- a/src/renderer/src/pages/(main)/(context)/index.tsx
+++ b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx
@@ -1,36 +1,21 @@
+import { useRouteView } from "@renderer/hooks/biz/useRouteParams"
import { views } from "@renderer/lib/constants"
import { cn } from "@renderer/lib/utils"
import { EntryColumn } from "@renderer/modules/entry-column"
-import { EntryContent } from "@renderer/modules/entry-content"
-import {
- feedActions,
- uiActions,
- useFeedStore,
- useUIStore,
-} from "@renderer/store"
-import { AnimatePresence } from "framer-motion"
-import { useEffect, useMemo, useRef } from "react"
+import { uiActions, useUIStore } from "@renderer/store"
+import { useMemo, useRef } from "react"
import { HotkeysProvider } from "react-hotkeys-hook"
import { useResizable } from "react-resizable-layout"
-import { useShallow } from "zustand/react/shallow"
+import { Outlet } from "react-router-dom"
export function Component() {
- const { activeEntry, activeList } = useFeedStore(
- useShallow((state) => ({
- activeList: state.activeList,
- activeEntry: state.activeEntryId,
- })),
- )
- const { setActiveEntry } = feedActions
- useEffect(() => {
- setActiveEntry(null)
- }, [activeList?.id])
const containerRef = useRef
(null)
// Memo this initial value to avoid re-render
// eslint-disable-next-line react-compiler/react-compiler
const entryColWidth = useMemo(() => useUIStore.getState().entryColWidth, [])
-
+ const view = useRouteView()
+ const inWideMode = view ? views[view].wideMode : false
const { position, separatorProps } = useResizable({
axis: "x",
min: 300,
@@ -42,7 +27,6 @@ export function Component() {
},
})
- const inWideMode = activeList && views[activeList.view].wideMode
return (
@@ -64,13 +48,7 @@ export function Component() {
className="h-full w-px shrink-0 cursor-ew-resize hover:bg-border"
/>
)}
-
- {!inWideMode && (
-
-
-
- )}
-
+
)
diff --git a/src/renderer/src/pages/(main)/(layer)/layout.tsx b/src/renderer/src/pages/(main)/(layer)/layout.tsx
new file mode 100644
index 000000000..0cb99dba7
--- /dev/null
+++ b/src/renderer/src/pages/(main)/(layer)/layout.tsx
@@ -0,0 +1 @@
+export { Outlet as Component } from "react-router-dom"
diff --git a/src/renderer/src/pages/(main)/index.tsx b/src/renderer/src/pages/(main)/index.tsx
new file mode 100644
index 000000000..c6d607ec7
--- /dev/null
+++ b/src/renderer/src/pages/(main)/index.tsx
@@ -0,0 +1,11 @@
+import { levels, ROUTE_FEED_PENDING } from "@renderer/lib/constants"
+import { FeedViewType } from "@renderer/lib/enum"
+import { redirect } from "react-router-dom"
+
+export function Component() {
+ return null
+}
+
+export const loader = () =>
+ // navigate to the first feed
+ redirect(`/feeds/${ROUTE_FEED_PENDING}?view=${FeedViewType.Articles}&level=${levels.view}`)
diff --git a/src/renderer/src/pages/(main)/layout.tsx b/src/renderer/src/pages/(main)/layout.tsx
index 6702108cd..0341d292e 100644
--- a/src/renderer/src/pages/(main)/layout.tsx
+++ b/src/renderer/src/pages/(main)/layout.tsx
@@ -1,22 +1,17 @@
-import { feedActions, useFeedStore } from "@renderer/store"
-import { useEffect } from "react"
-import { Outlet, useNavigate } from "react-router-dom"
+import { setMainContainerElement } from "@renderer/atoms"
+import { FeedColumn } from "@renderer/modules/feed-column"
+import { Outlet } from "react-router-dom"
-function MainLayout() {
- const navigate = useNavigate()
-
- const changed = useFeedStore(
- (state) => `${state.activeList?.view}-${state.activeList?.id}`,
+export function Component() {
+ return (
+
+
+
+
+ {/* NOTE: tabIndex for main element can get by `document.activeElement` */}
+
+
+
+
)
-
- useEffect(() => {
- feedActions.setActiveEntry(null)
- if (changed) {
- navigate("/")
- }
- }, [changed])
-
- return
}
-
-export { MainLayout as Component }
diff --git a/src/renderer/src/providers/biz-router-provider.tsx b/src/renderer/src/providers/biz-router-provider.tsx
new file mode 100644
index 000000000..68ef8b612
--- /dev/null
+++ b/src/renderer/src/providers/biz-router-provider.tsx
@@ -0,0 +1,17 @@
+import { setNavigate, setRoute } from "@renderer/atoms"
+import { useLayoutEffect } from "react"
+import { useNavigate, useParams, useSearchParams } from "react-router-dom"
+
+export const BizRouterProvider = () => {
+ const [searchParams] = useSearchParams()
+ const params = useParams()
+ const nav = useNavigate()
+ useLayoutEffect(() => {
+ setRoute({
+ params,
+ searchParams,
+ })
+ setNavigate({ fn: nav })
+ }, [searchParams, params, nav])
+ return null
+}
diff --git a/src/renderer/src/providers/root-providers.tsx b/src/renderer/src/providers/root-providers.tsx
index ccd66139e..4a5d362f8 100644
--- a/src/renderer/src/providers/root-providers.tsx
+++ b/src/renderer/src/providers/root-providers.tsx
@@ -9,6 +9,8 @@ import { Provider } from "jotai"
import type { FC, PropsWithChildren } from "react"
import { HelmetProvider } from "react-helmet-async"
+import { BizRouterProvider } from "./biz-router-provider"
+
const loadFeatures = () =>
import("../framer-lazy-feature").then((res) => res.default)
export const RootProviders: FC = ({ children }) => (
@@ -28,6 +30,7 @@ export const RootProviders: FC = ({ children }) => (
{children}
+
diff --git a/src/renderer/src/queries/entries.ts b/src/renderer/src/queries/entries.ts
index 09223734f..af1192c15 100644
--- a/src/renderer/src/queries/entries.ts
+++ b/src/renderer/src/queries/entries.ts
@@ -1,7 +1,7 @@
import { useBizInfiniteQuery, useBizQuery } from "@renderer/hooks"
import { apiClient } from "@renderer/lib/api-fetch"
import { defineQuery } from "@renderer/lib/defineQuery"
-import { entryActions } from "@renderer/store/entry/entry"
+import { entryActions } from "@renderer/store"
export const entries = {
entries: ({
diff --git a/src/renderer/src/router.tsx b/src/renderer/src/router.tsx
index 23e7f7386..ed417bc4d 100644
--- a/src/renderer/src/router.tsx
+++ b/src/renderer/src/router.tsx
@@ -5,7 +5,6 @@ import { buildGlobRoutes } from "./lib/route-builder"
const globTree = import.meta.glob("./pages/**/*.tsx")
const tree = buildGlobRoutes(globTree)
-// console.log(tree)
export const router = createBrowserRouter([
{
diff --git a/src/renderer/src/store/entry/hooks.ts b/src/renderer/src/store/entry/hooks.ts
index ba0c746c9..6c96dc2a0 100644
--- a/src/renderer/src/store/entry/hooks.ts
+++ b/src/renderer/src/store/entry/hooks.ts
@@ -4,13 +4,13 @@ import type { EntryModel } from "@renderer/models"
import { useShallow } from "zustand/react/shallow"
import { useFeedIdByView } from "../subscription"
-import { useEntryStore } from "./entry"
+import { useEntryStore } from "./store"
interface EntryFilter {
unread?: boolean
}
-export const useEntry = (entryId: string | null): EntryModel | null =>
+export const useEntry = (entryId: Nullable): EntryModel | null =>
useEntryStore(useShallow((state) => entryId ? state.flatMapEntries[entryId] : null))
// feedId: single feedId, multiple feedId joint by `,`, and `collections`
export const useEntryIdsByFeedId = (feedId: string, filter?: EntryFilter) =>
diff --git a/src/renderer/src/store/entry/index.ts b/src/renderer/src/store/entry/index.ts
index 812f374e0..a92fe14dc 100644
--- a/src/renderer/src/store/entry/index.ts
+++ b/src/renderer/src/store/entry/index.ts
@@ -1,3 +1,3 @@
-export * from "./entry"
export * from "./hooks"
+export * from "./store"
export * from "./types"
diff --git a/src/renderer/src/store/entry/entry.ts b/src/renderer/src/store/entry/store.ts
similarity index 95%
rename from src/renderer/src/store/entry/entry.ts
rename to src/renderer/src/store/entry/store.ts
index 145c0d973..b30dc5713 100644
--- a/src/renderer/src/store/entry/entry.ts
+++ b/src/renderer/src/store/entry/store.ts
@@ -4,6 +4,7 @@ import type { EntryModel } from "@renderer/models"
import { produce } from "immer"
import { merge, omit } from "lodash-es"
+import { feedActions } from "../feed"
import { unreadActions } from "../unread"
import { createZustandStore, getStoreActions } from "../utils/helper"
import type { EntryActions, EntryState } from "./types"
@@ -132,6 +133,10 @@ export const useEntryStore = createZustandStore(
draft.flatMapEntries[entry.entries.id] || {},
entry,
)
+
+ const feeds = entries.map((entry) => entry.feeds)
+ // Insert to feed store
+ feedActions.upsertMany(feeds)
}
return draft
}),
diff --git a/src/renderer/src/store/feed.ts b/src/renderer/src/store/feed.ts
deleted file mode 100644
index 19ce197ba..000000000
--- a/src/renderer/src/store/feed.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { ActiveEntryId, ActiveList } from "@renderer/models"
-import { create } from "zustand"
-
-import { getStoreActions } from "./utils/helper"
-
-interface FeedStoreActions {
- setActiveList: (value: ActiveList) => void
- setActiveEntry: (value: ActiveEntryId) => void
-}
-interface FeedStoreState {
- activeList: ActiveList
- activeEntryId: ActiveEntryId
-}
-
-type FeedStore = FeedStoreState & Readonly
-export const useFeedStore = create((set) => ({
- activeList: {
- level: "view",
- id: 0,
- name: "Articles",
- view: 0,
- },
- activeEntryId: null,
- activeEntryIndex: null,
-
- // Actions
- setActiveEntry: (value) => set({ activeEntryId: value }),
- setActiveList: (value) => set({ activeList: value }),
-}))
-
-export const feedActions = getStoreActions(useFeedStore)
-
-export const getCurrentFeedId = () => useFeedStore.getState().activeList.id
-export const getCurrentEntryId = () => useFeedStore.getState().activeEntryId
-
-/** Hooks */
-export const useFeedActiveList = () =>
- useFeedStore((state) => state.activeList)
diff --git a/src/renderer/src/store/feed/hooks.ts b/src/renderer/src/store/feed/hooks.ts
new file mode 100644
index 000000000..06d3bb1dd
--- /dev/null
+++ b/src/renderer/src/store/feed/hooks.ts
@@ -0,0 +1,23 @@
+import { useRouteParms } from "@renderer/hooks/biz/useRouteParams"
+import { ROUTE_FEED_PENDING, views } from "@renderer/lib/constants"
+import type { FeedModel } from "@renderer/models"
+import { useShallow } from "zustand/react/shallow"
+
+import { useFeedStore } from "./store"
+
+export const useFeedById = (feedId: Nullable): FeedModel | null =>
+ useFeedStore((state) => (feedId ? state.feeds[feedId] : null))
+
+export const useFeedByIdSelector = (
+ feedId: Nullable,
+ selector: (feed: FeedModel) => T,
+) => useFeedStore(useShallow((state) => (feedId && state.feeds[feedId] ? selector(state.feeds[feedId]) : null)))
+
+export const useFeedHeaderTitle = () => {
+ const { feedId: currentFeedId, category, view } = useRouteParms()
+
+ const feedTitle = useFeedByIdSelector(currentFeedId, (feed) => feed.title)
+ return currentFeedId === ROUTE_FEED_PENDING ?
+ views[view].name :
+ category || feedTitle
+}
diff --git a/src/renderer/src/store/feed/index.ts b/src/renderer/src/store/feed/index.ts
new file mode 100644
index 000000000..a92fe14dc
--- /dev/null
+++ b/src/renderer/src/store/feed/index.ts
@@ -0,0 +1,3 @@
+export * from "./hooks"
+export * from "./store"
+export * from "./types"
diff --git a/src/renderer/src/store/feed/store.ts b/src/renderer/src/store/feed/store.ts
new file mode 100644
index 000000000..2a0f58e95
--- /dev/null
+++ b/src/renderer/src/store/feed/store.ts
@@ -0,0 +1,40 @@
+import type { FeedModel } from "@renderer/models"
+import { produce } from "immer"
+
+import { createZustandStore, getStoreActions } from "../utils/helper"
+import type { FeedActions, FeedState } from "./types"
+
+export const useFeedStore = createZustandStore(
+ "feed",
+ {
+ version: 1,
+ },
+)((set) => ({
+ feeds: {},
+ clear() {
+ set({ feeds: {} })
+ },
+ upsertMany(feeds) {
+ set((state) =>
+ produce(state, (state) => {
+ for (const feed of feeds) {
+ if (feed.id) { state.feeds[feed.id] = feed }
+ }
+ }),
+ )
+ },
+
+ optimisticUpdate(feedId, changed) {
+ set((state) =>
+ produce(state, (state) => {
+ const feed = state.feeds[feedId]
+ if (!feed) return
+
+ Object.assign(feed, changed)
+ }),
+ )
+ },
+}))
+export const feedActions = getStoreActions(useFeedStore)
+
+export const getFeedById = (feedId: string): Nullable => useFeedStore.getState().feeds[feedId]
diff --git a/src/renderer/src/store/feed/types.ts b/src/renderer/src/store/feed/types.ts
new file mode 100644
index 000000000..927666226
--- /dev/null
+++ b/src/renderer/src/store/feed/types.ts
@@ -0,0 +1,13 @@
+import type { FeedModel } from "@renderer/models"
+
+type FeedId = string
+
+export interface FeedState {
+ feeds: Record
+}
+
+export interface FeedActions {
+ upsertMany: (feeds: FeedModel[]) => void
+ optimisticUpdate: (feedId: FeedId, changed: Partial) => void
+ clear: () => void
+}
diff --git a/src/renderer/src/store/subscription.ts b/src/renderer/src/store/subscription.ts
index 4e1a47fe6..ed42ec77d 100644
--- a/src/renderer/src/store/subscription.ts
+++ b/src/renderer/src/store/subscription.ts
@@ -2,20 +2,23 @@ import { apiClient } from "@renderer/lib/api-fetch"
import { FeedViewType } from "@renderer/lib/enum"
import type { SubscriptionModel } from "@renderer/models"
import { produce } from "immer"
+import { omit } from "lodash-es"
-import { entryActions } from "./entry/entry"
+import { entryActions } from "./entry/store"
+import { feedActions } from "./feed"
import { unreadActions } from "./unread"
import { createZustandStore, getStoreActions } from "./utils/helper"
type FeedId = string
+export type SubscriptionPlainModel = Omit
interface SubscriptionState {
- data: Record
+ data: Record
dataIdByView: Record
}
interface SubscriptionActions {
- upsert: (feedId: FeedId, subscription: SubscriptionModel) => void
- fetchByView: (view?: FeedViewType) => Promise
+ upsertMany: (subscription: SubscriptionPlainModel[]) => void
+ fetchByView: (view?: FeedViewType) => Promise
markReadByView: (view?: FeedViewType) => void
internal_reset: () => void
clear: () => void
@@ -66,23 +69,20 @@ export const useSubscriptionStore = createZustandStore<
}))
}
- set((state) =>
- produce(state, (state) => {
- res.data.forEach((subscription) => {
- state.data[subscription.feeds.id] = subscription
- state.dataIdByView[subscription.view].push(subscription.feeds.id)
- return state
- })
- }),
- )
+ get().upsertMany(res.data)
+ feedActions.upsertMany(res.data.map((s) => s.feeds))
return res.data
},
- upsert: (feedId, subscription) => {
+ upsertMany: (subscriptions) => {
set((state) =>
produce(state, (state) => {
- state.data[feedId] = subscription
- return state
+ subscriptions.forEach((subscription) => {
+ state.data[subscription.feedId] = omit(subscription, "feeds")
+ state.dataIdByView[subscription.view].push(subscription.feedId)
+
+ return state
+ })
}),
)
},
diff --git a/src/renderer/src/store/utils/local.ts b/src/renderer/src/store/utils/local.ts
index 112815d5e..eb304c4ae 100644
--- a/src/renderer/src/store/utils/local.ts
+++ b/src/renderer/src/store/utils/local.ts
@@ -1,11 +1,12 @@
-import { entryActions } from "../entry/entry"
+import { entryActions } from "../entry/store"
+import { feedActions } from "../feed"
import { subscriptionActions } from "../subscription"
import { uiActions } from "../ui"
import { unreadActions } from "../unread"
export const clearLocalPersistStoreData = () => {
// All clear and reset method will aggregate here
- [entryActions, subscriptionActions, unreadActions, uiActions].forEach(
+ [entryActions, subscriptionActions, unreadActions, uiActions, feedActions].forEach(
(actions) => {
actions.clear()
},