diff --git a/src/renderer/src/components/entry-column/item-wrapper.tsx b/src/renderer/src/components/entry-column/item-wrapper.tsx
index a5da7e107..3c04b0a92 100644
--- a/src/renderer/src/components/entry-column/item-wrapper.tsx
+++ b/src/renderer/src/components/entry-column/item-wrapper.tsx
@@ -1,12 +1,10 @@
import { useAsRead } from "@renderer/hooks/useAsRead"
-import { useEntryActions } from "@renderer/hooks/useEntryActions"
-import { apiClient } from "@renderer/lib/api-fetch"
+import { useEntryActions, useRead } from "@renderer/hooks/useEntryActions"
import { views } from "@renderer/lib/constants"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { cn } from "@renderer/lib/utils"
import { feedActions, useFeedStore } from "@renderer/store"
-import { entryActions, useEntry } from "@renderer/store/entry"
-import { useMutation } from "@tanstack/react-query"
+import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
@@ -29,20 +27,7 @@ export function EntryItemWrapper({
const asRead = useAsRead(entry)
- const read = useMutation({
- mutationFn: async () =>
- apiClient.reads.$post({
- json: {
- entryIds: [entry.entries.id],
- },
- }),
- onMutate: () => {
- entryActions.optimisticUpdate(entry.entries.id, {
- read: true,
- })
- },
- // TODO fallback
- })
+ const markReadMutation = useRead(entry)
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry) return
@@ -52,15 +37,17 @@ export function EntryItemWrapper({
{
e.stopPropagation()
feedActions.setActiveEntry(entry.entries.id)
- if (!entry.read) {
- read.mutate()
+ if (!asRead) {
+ markReadMutation.mutate()
}
}}
onDoubleClick={() =>
diff --git a/src/renderer/src/components/feed-column/list.tsx b/src/renderer/src/components/feed-column/list.tsx
index f7fefd25b..19028b17d 100644
--- a/src/renderer/src/components/feed-column/list.tsx
+++ b/src/renderer/src/components/feed-column/list.tsx
@@ -5,7 +5,11 @@ import type { FeedViewType } from "@renderer/lib/enum"
import { cn } from "@renderer/lib/utils"
import type { FeedListModel, SubscriptionResponse } from "@renderer/models"
import { Queries } from "@renderer/queries"
-import { feedActions, useFeedActiveList, useUnreadStore } from "@renderer/store"
+import {
+ feedActions,
+ useFeedActiveList,
+ useUnreadStore,
+} from "@renderer/store"
import { useMemo, useState } from "react"
import { Link } from "react-router-dom"
import { parse } from "tldts"
@@ -13,11 +17,11 @@ import { parse } from "tldts"
import { FeedCategory } from "./category"
const useData = (view?: FeedViewType) => {
- const subscriptions = useBizQuery(Queries.subscription.byView(view))
+ const query = useBizQuery(Queries.subscription.byView(view))
// TODO Refactor this into category store
return useMemo(() => {
- if (!subscriptions.data) return null
+ if (!query.data) return null
const categories = {
list: {},
} as {
@@ -29,10 +33,10 @@ const useData = (view?: FeedViewType) => {
>
}
const domains: Record
= {}
- const data = structuredClone(subscriptions.data)
+ const subscriptions = structuredClone(query.data)
- if (subscriptions) {
- for (const subscription of data.subscriptions) {
+ if (query) {
+ for (const subscription of subscriptions) {
if (!subscription.category && subscription.feeds.siteUrl) {
const { domain } = parse(subscription.feeds.siteUrl)
if (domain) {
@@ -44,8 +48,8 @@ const useData = (view?: FeedViewType) => {
}
}
}
- if (subscriptions) {
- for (const subscription of data.subscriptions) {
+ if (query) {
+ for (const subscription of subscriptions) {
if (!subscription.category) {
if (subscription.feeds.siteUrl) {
const { domain } = parse(subscription.feeds.siteUrl)
@@ -64,8 +68,6 @@ const useData = (view?: FeedViewType) => {
}
}
- const unread = data.unreads[subscription.feedId] || 0
- subscription.unread = unread
categories.list[subscription.category].list.push(subscription)
}
}
@@ -77,7 +79,7 @@ const useData = (view?: FeedViewType) => {
return {
list,
} as FeedListModel
- }, [subscriptions])
+ }, [query])
}
export function FeedList({
className,
@@ -92,6 +94,8 @@ export function FeedList({
const data = useData(view)
const activeList = useFeedActiveList()
+ useBizQuery(Queries.subscription.unreadAll())
+
const totalUnread = useUnreadStore((state) => {
let unread = 0
data?.list.forEach((a) => {
@@ -154,8 +158,7 @@ export function FeedList({
{
e.stopPropagation()
@@ -172,7 +175,7 @@ export function FeedList({
Starred
- {data?.list?.length ? (
+ {data?.list?.length ?
sortedByUnread?.map((category) => (
- ))
- ) : (
- !hideTitle && (
-
-
-
- Add some feeds
-
-
- )
- )}
+ )) :
+ !hideTitle && (
+
+
+
+ Add some feeds
+
+
+ )}
)
}
diff --git a/src/renderer/src/hono.ts b/src/renderer/src/hono.ts
index 74e69102c..ec35c5850 100644
--- a/src/renderer/src/hono.ts
+++ b/src/renderer/src/hono.ts
@@ -31,7 +31,7 @@ declare const routes: hono_hono_base.HonoBase
+ useMutation({
+ mutationFn: async () =>
+ entry &&
+ apiClient.collections.$post({
+ json: {
+ entryId: entry?.entries.id,
+ },
+ }),
+
+ onMutate() {
+ if (!entry) return
+ entryActions.optimisticUpdate(entry.entries.id, {
+ collections: {
+ createdAt: new Date().toISOString(),
+ },
+ })
+ },
+ onSuccess: () => {
+ toast("Collected.", {
+ duration: 1000,
+ })
+ },
+ })
+
+export const useUnCollect = (entry: EntryModel | undefined) =>
+ useMutation({
+ mutationFn: async () =>
+ entry &&
+ apiClient.collections.$delete({
+ json: {
+ entryId: entry?.entries.id,
+ },
+ }),
+
+ onMutate() {
+ if (!entry) return
+ entryActions.optimisticUpdate(entry.entries.id, {
+ collections: undefined,
+ })
+ },
+ onSuccess: () => {
+ toast("Uncollected.", {
+ duration: 1000,
+ })
+ },
+ })
+
+export const useRead = (entry: EntryModel | undefined) =>
+ useMutation({
+ mutationFn: async () =>
+ entry &&
+ apiClient.reads.$post({
+ json: {
+ entryIds: [entry.entries.id],
+ },
+ }),
+
+ onMutate: () => {
+ if (!entry) return
+
+ entryActions.markRead(entry.feeds.id, entry.entries.id, true)
+ },
+ })
+export const useUnread = (entry: EntryModel | undefined) =>
+ useMutation({
+ mutationFn: async () =>
+ entry &&
+ apiClient.reads.$delete({
+ json: {
+ entryId: entry.entries.id,
+ },
+ }),
+
+ onMutate: () => {
+ if (!entry) return
+
+ entryActions.markRead(entry.feeds.id, entry.entries.id, false)
+ },
+ })
+
export const useEntryActions = ({
view,
entry,
@@ -27,76 +108,10 @@ export const useEntryActions = ({
},
})
- const collect = useMutation({
- mutationFn: async () =>
- entry && apiClient.collections.$post({
- json: {
- entryId: entry?.entries.id,
- },
- }),
-
- onMutate() {
- if (!entry) return
- entryActions.optimisticUpdate(entry.entries.id, {
- collections: {
- createdAt: new Date().toISOString(),
- },
- })
- },
- onSuccess: () => {
- toast("Collected.", {
- duration: 1000,
- })
- },
- })
- const uncollect = useMutation({
- mutationFn: async () =>
- entry && apiClient.collections.$delete({
- json: {
- entryId: entry?.entries.id,
- },
- }),
-
- onMutate() {
- if (!entry) return
- entryActions.optimisticUpdate(entry.entries.id, {
- collections: undefined,
- })
- },
- onSuccess: () => {
- toast("Uncollected.", {
- duration: 1000,
- })
- },
- })
- const read = useMutation({
- mutationFn: async () =>
- entry &&
- apiClient.reads.$post({
- json: {
- entryIds: [entry.entries.id],
- },
- }),
-
- onMutate: () => {
- if (!entry) return
-
- entryActions.markRead(entry.feeds.id, entry.entries.id, true)
- },
- })
- const unread = useMutation({
- mutationFn: async () => entry && apiClient.reads.$delete({
- json: {
- entryId: entry.entries.id,
- },
- }),
-
- onMutate: () => {
- if (!entry) return
-
- entryActions.markRead(entry.feeds.id, entry.entries.id, false)
- },
- })
+ const collect = useCollect(entry)
+ const uncollect = useUnCollect(entry)
+ const read = useRead(entry)
+ const unread = useUnread(entry)
if (!entry?.entries.url || view === undefined) return { items: [] }
diff --git a/src/renderer/src/queries/subscriptions.ts b/src/renderer/src/queries/subscriptions.ts
index 4181162dc..3079ef953 100644
--- a/src/renderer/src/queries/subscriptions.ts
+++ b/src/renderer/src/queries/subscriptions.ts
@@ -7,16 +7,7 @@ export const subscription = {
byView: (view?: FeedViewType) =>
defineQuery(
["subscriptions", view],
- async () => {
- const [subscriptions, unreads] = await Promise.all([
- subscriptionActions.fetchByView(view),
- unreadActions.fetchUnreadByView(view),
- ])
- return {
- subscriptions,
- unreads,
- }
- },
+ async () => subscriptionActions.fetchByView(view),
{
rootKey: ["subscriptions"],
},
@@ -29,4 +20,7 @@ export const subscription = {
return res.data
}),
+
+ unreadAll: () =>
+ defineQuery(["unread-all"], async () => unreadActions.fetchUnreadAll()),
}
diff --git a/src/renderer/src/store/entry.ts b/src/renderer/src/store/entry.ts
index 01abacfbb..588c83391 100644
--- a/src/renderer/src/store/entry.ts
+++ b/src/renderer/src/store/entry.ts
@@ -127,6 +127,7 @@ export const useEntryStore = createZustandStore(
},
markRead: (feedId: string, entryId: string, read: boolean) => {
+ console.log(feedId, "feedId")
unreadActions.incrementByFeedId(feedId, read ? -1 : 1)
entryActions.optimisticUpdate(entryId, {
read,
diff --git a/src/renderer/src/store/unread.ts b/src/renderer/src/store/unread.ts
index 0c16d147d..e3f227ebb 100644
--- a/src/renderer/src/store/unread.ts
+++ b/src/renderer/src/store/unread.ts
@@ -10,6 +10,7 @@ interface UnreadState {
interface UnreadActions {
updateByFeedId: (feedId: string, unread: number) => void
fetchUnreadByView: (view?: FeedViewType) => Promise>
+ fetchUnreadAll: () => Promise>
incrementByFeedId: (feedId: string, inc: number) => void
internal_reset: () => void
@@ -19,7 +20,7 @@ export const useUnreadStore = createZustandStore(
{
version: 0,
},
-)((set, get) => ({
+)((set) => ({
data: {},
internal_reset() {
@@ -32,14 +33,28 @@ export const useUnreadStore = createZustandStore(
})
const { data } = unread
+
set((state) =>
produce(state, (state) => {
- get().internal_reset()
- for (const feedId in data) {
- state.data[feedId] = data[feedId]
- return state
+ for (const [key, value] of Object.entries(data)) {
+ state.data[key] = value
+ }
+ }),
+ )
+ return data
+ },
+ async fetchUnreadAll() {
+ const unread = await apiClient.reads.$get({
+ query: {},
+ })
+
+ const { data } = unread
+ this.internal_reset()
+ set((state) =>
+ produce(state, (state) => {
+ for (const [key, value] of Object.entries(data)) {
+ state.data[key] = value
}
- return state
}),
)
return data