diff --git a/package.json b/package.json index 91b037252..14a97b19d 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "@electron-forge/plugin-fuses": "7.4.0", "@electron-forge/publisher-github": "7.4.0", "@electron-toolkit/tsconfig": "^1.0.1", + "@hono/node-server": "1.12.0", "@iconify-json/mingcute": "1.1.18", "@pengx17/electron-forge-maker-appimage": "1.2.1", "@tailwindcss/container-queries": "0.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5e65344f..4f6a15c5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -329,6 +329,9 @@ importers: '@electron-toolkit/tsconfig': specifier: ^1.0.1 version: 1.0.1(@types/node@20.14.12) + '@hono/node-server': + specifier: 1.12.0 + version: 1.12.0 '@iconify-json/mingcute': specifier: 1.1.18 version: 1.1.18 @@ -1016,6 +1019,10 @@ packages: hono: '>=3.*' react: '>=18' + '@hono/node-server@1.12.0': + resolution: {integrity: sha512-e6oHjNiErRxsZRZBmc2KucuvY3btlO/XPncIpP2X75bRdTilF9GLjm3NHvKKunpJbbJJj31/FoPTksTf8djAVw==} + engines: {node: '>=18.14.1'} + '@hookform/resolvers@3.9.0': resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==} peerDependencies: @@ -7739,6 +7746,8 @@ snapshots: hono: 4.4.7(patch_hash=ycbk46disqruhfjducp47b5fl4) react: 18.3.1 + '@hono/node-server@1.12.0': {} + '@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@18.3.1))': dependencies: react-hook-form: 7.52.1(react@18.3.1) diff --git a/src/hono.ts b/src/hono.ts index 62292c268..4b3e756af 100644 --- a/src/hono.ts +++ b/src/hono.ts @@ -1,13 +1,17 @@ import * as hono_hono_base from 'hono/hono-base'; import * as hono_utils_http_status from 'hono/utils/http-status'; import * as hono from 'hono'; -import * as hono_types from 'hono/types'; +import { HttpBindings } from '@hono/node-server'; import * as drizzle_orm from 'drizzle-orm'; import { InferInsertModel } from 'drizzle-orm'; import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core'; import * as zod from 'zod'; import { z } from 'zod'; +type Env = { + Bindings: HttpBindings; +}; + declare const actions: drizzle_orm_pg_core.PgTableWithColumns<{ name: "actions"; schema: undefined; @@ -2424,7 +2428,7 @@ declare const feedPowerTokensRelations: drizzle_orm.Relations<"feedPowerTokens", feed: drizzle_orm.One<"feeds", true>; }>; -declare const _routes: hono_hono_base.HonoBase { + const dateObj = new Date(date) + const dateString = dateObj.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }) + const startOfDay = new Date(dateObj.setHours(0, 0, 0, 0)).getTime() + const endOfDay = new Date(dateObj.setHours(23, 59, 59, 999)).getTime() + + const className = cn(isFirst ? "pt-2" : "pt-8", "flex items-center gap-1 pl-2 text-sm font-bold text-zinc-800 dark:text-neutral-400") + + if (view === FeedViewType.SocialMedia) { + return + } + + return ( +
+ + {dateString} +
+ ) +} diff --git a/src/renderer/src/modules/entry-column/hooks.ts b/src/renderer/src/modules/entry-column/hooks.ts index dbe77fd18..7a28ee1f8 100644 --- a/src/renderer/src/modules/entry-column/hooks.ts +++ b/src/renderer/src/modules/entry-column/hooks.ts @@ -62,7 +62,10 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { const { feedId, view, isAllFeeds, isCollection } = routeParams - const folderIds = useFolderFeedsByFeedId(feedId) + const folderIds = useFolderFeedsByFeedId({ + feedId, + view, + }) const entriesOptions = { id: folderIds?.join(",") || feedId, @@ -107,7 +110,7 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { ) const currentEntries = useEntryIdsByFeedIdOrView( - isAllFeeds ? view : feedId!, + isAllFeeds ? view : folderIds || feedId!, { unread: unreadOnly, view, @@ -152,10 +155,30 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { setMergedEntries(nextIds) }, [entryIdsAsDeps]) - const sortEntries = () => - isCollection ? - sortEntriesIdByStarAt(mergedEntries) : - sortEntriesIdByEntryPublishedAt(mergedEntries) + const sortEntries = isCollection ? + sortEntriesIdByStarAt(mergedEntries) : + sortEntriesIdByEntryPublishedAt(mergedEntries) + + const entriesWithDate = useMemo(() => { + if (views[view].gridMode) { + return sortEntries + } + const entriesId2Map = entryActions.getFlattenMapEntries() + let lastDate = "" + const entriesWithDate = [] as string[] + for (const id of sortEntries) { + const entry = entriesId2Map[id] + if (entry) { + const date = new Date(entry.entries.publishedAt).toDateString() + if (date !== lastDate) { + entriesWithDate.push(date) + lastDate = date + } + } + entriesWithDate.push(id) + } + return entriesWithDate + }, [sortEntries, view]) return { ...query, @@ -164,7 +187,7 @@ export const useEntriesByView = ({ onReset }: { onReset?: () => void }) => { refetch: useCallback(() => { query.refetch() }, [query]), - entriesIds: sortEntries(), + entriesIds: entriesWithDate, totalCount: query.data?.pages?.[0]?.total ?? mergedEntries.length, } } diff --git a/src/renderer/src/modules/entry-column/index.tsx b/src/renderer/src/modules/entry-column/index.tsx index 2648d4bdd..2a580efd8 100644 --- a/src/renderer/src/modules/entry-column/index.tsx +++ b/src/renderer/src/modules/entry-column/index.tsx @@ -6,40 +6,28 @@ import { useMe } from "@renderer/atoms/user" import { m } from "@renderer/components/common/Motion" import { EmptyIcon } from "@renderer/components/icons/empty" import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height" -import { ActionButton, StyledButton } from "@renderer/components/ui/button" +import { ActionButton } from "@renderer/components/ui/button" import { DividerVertical } from "@renderer/components/ui/divider" import { LoadingCircle } from "@renderer/components/ui/loading" -import { - Popover, - PopoverClose, - PopoverContent, - PopoverTrigger, -} from "@renderer/components/ui/popover" import { ScrollArea } from "@renderer/components/ui/scroll-area" import { EllipsisHorizontalTextWithTooltip } from "@renderer/components/ui/typography" import { FEED_COLLECTION_LIST, ROUTE_ENTRY_PENDING, - ROUTE_FEED_IN_FOLDER, views, } from "@renderer/constants" import { shortcuts } from "@renderer/constants/shortcuts" import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry" import { useRouteParms } from "@renderer/hooks/biz/useRouteParams" import { useIsOnline } from "@renderer/hooks/common/useIsOnline" -import { apiClient } from "@renderer/lib/api-fetch" -import { cn, getEntriesParams, getOS, isBizId } from "@renderer/lib/utils" +import { cn, getOS, isBizId } from "@renderer/lib/utils" import { EntryHeader } from "@renderer/modules/entry-content/header" import { useRefreshFeedMutation } from "@renderer/queries/feed" import { entryActions, useEntry } from "@renderer/store/entry" import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed" -import { - subscriptionActions, - useFolderFeedsByFeedId, -} from "@renderer/store/subscription" import type { HTMLMotionProps } from "framer-motion" import type { FC } from "react" -import { forwardRef, useCallback, useEffect, useRef, useState } from "react" +import { forwardRef, useCallback, useEffect, useRef } from "react" import type { ScrollSeekConfiguration, VirtuosoHandle, @@ -47,6 +35,7 @@ import type { } from "react-virtuoso" import { Virtuoso, VirtuosoGrid } from "react-virtuoso" +import { DateItem } from "./date-item" import { EntryColumnShortcutHandler } from "./EntryColumnShortcutHandler" import { useEntriesByView, useEntryMarkReadHandler } from "./hooks" import { @@ -54,6 +43,7 @@ import { EntryItemSkeleton, EntryItemSkeletonWithDelayShow, } from "./item" +import { MarkAllButton } from "./mark-all-button" import { girdClassNames } from "./styles" const scrollSeekConfiguration: ScrollSeekConfiguration = { @@ -129,10 +119,14 @@ export function EntryColumn() { } }, itemContent: useCallback( - (_, entryId: string) => { + (index, entryId) => { if (!entryId) return null - return + if (entryId.includes(" ")) { + return + } else { + return + } }, [view], ), @@ -215,33 +209,6 @@ const ListHeader: FC<{ const unreadOnly = useGeneralSettingKey("unreadOnly") const { feedId, entryId, view } = routerParams - const folderIds = useFolderFeedsByFeedId(feedId) - - const [markPopoverOpen, setMarkPopoverOpen] = useState(false) - const handleMarkAllAsRead = useCallback(async () => { - if (!routerParams) return - await apiClient.reads.all.$post({ - json: { - ...getEntriesParams({ - id: folderIds?.join(",") || feedId, - view: routerParams?.view, - }), - }, - }) - - if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) { - subscriptionActions.markReadByView(routerParams.view) - } else if (routerParams.feedId?.startsWith(ROUTE_FEED_IN_FOLDER)) { - subscriptionActions.markReadByFolder( - routerParams.feedId.replace(ROUTE_FEED_IN_FOLDER, ""), - ) - } else { - routerParams.feedId?.split(",").forEach((feedId) => { - entryActions.markReadByFeedId(feedId) - }) - } - setMarkPopoverOpen(false) - }, [feedId, folderIds, routerParams]) const headerTitle = useFeedHeaderTitle() const os = getOS() @@ -275,12 +242,12 @@ const ListHeader: FC<{ const feed = useFeedById(routerParams.feedId) const titleStyleBasedView = [ - "pl-11", - "pl-4", + "pl-12", + "pl-7", "pl-7", "pl-7", "px-5", - "pl-11", + "pl-12", ] return ( @@ -361,28 +328,7 @@ const ListHeader: FC<{ )} - - - - - - - -
Mark all as read?
-
- - Cancel - - {/* TODO */} - - Confirm - -
-
-
+ {titleAtBottom && titleInfo} diff --git a/src/renderer/src/modules/entry-column/mark-all-button.tsx b/src/renderer/src/modules/entry-column/mark-all-button.tsx new file mode 100644 index 000000000..f187b490f --- /dev/null +++ b/src/renderer/src/modules/entry-column/mark-all-button.tsx @@ -0,0 +1,77 @@ +import { ActionButton, StyledButton } from "@renderer/components/ui/button" +import { + Popover, + PopoverClose, + PopoverContent, + PopoverTrigger, +} from "@renderer/components/ui/popover" +import { shortcuts } from "@renderer/constants/shortcuts" +import { useRouteParms } from "@renderer/hooks/biz/useRouteParams" +import { subscriptionActions, useFolderFeedsByFeedId } from "@renderer/store/subscription" +import { useCallback, useState } from "react" + +export const MarkAllButton = ({ + filter, + className, +}: { + filter?: { + startTime: number + endTime: number + } + className?: string +}) => { + const [markPopoverOpen, setMarkPopoverOpen] = useState(false) + + const routerParams = useRouteParms() + const { feedId, view } = routerParams + const folderIds = useFolderFeedsByFeedId({ + feedId, + view, + }) + + const handleMarkAllAsRead = useCallback(async () => { + if (!routerParams) return + + if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) { + subscriptionActions.markReadByView(view, filter) + } else if (folderIds) { + subscriptionActions.markReadByFeedIds( + view, + folderIds, + filter, + ) + } else if (routerParams.feedId) { + subscriptionActions.markReadByFeedIds(view, routerParams.feedId?.split(","), filter) + } + }, [feedId, folderIds, filter, routerParams]) + + return ( + + + + + + + +
Mark all as read?
+
+ + Cancel + + {/* TODO */} + { + handleMarkAllAsRead() + setMarkPopoverOpen(false) + }} + > + Confirm + +
+
+
+ ) +} diff --git a/src/renderer/src/modules/entry-column/social-media-item.tsx b/src/renderer/src/modules/entry-column/social-media-item.tsx index f1796e578..934f1c525 100644 --- a/src/renderer/src/modules/entry-column/social-media-item.tsx +++ b/src/renderer/src/modules/entry-column/social-media-item.tsx @@ -13,6 +13,7 @@ import { useEntry } from "@renderer/store/entry/hooks" import { useFeedById } from "@renderer/store/feed" import { ReactVirtuosoItemPlaceholder } from "../../components/ui/placeholder" +import { MarkAllButton } from "./mark-all-button" import { StarIcon } from "./star-icon" import { EntryTranslation } from "./translation" import type { EntryListItemFC } from "./types" @@ -164,3 +165,28 @@ export const SocialMediaItemSkeleton = ( ) + +export const SocialMediaDateItem = ({ + date, + className, +}: { + date: string + className?: string +}) => { + const dateObj = new Date(date) + const dateString = dateObj.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }) + const startOfDay = new Date(dateObj.setHours(0, 0, 0, 0)).getTime() + const endOfDay = new Date(dateObj.setHours(23, 59, 59, 999)).getTime() + + return ( +
+ + {dateString} +
+ ) +} diff --git a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx index 0d5f108f6..da8ea4f58 100644 --- a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx +++ b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx @@ -49,14 +49,14 @@ export const ClaimDailyReward = () => { disabled={!canClaim} > {canClaim ? - "Claim Daily Power" : - `Claim in ${hour}:${minute}`} + "Mint Daily Power" : + `Mint in ${hour}:${minute}`} {canClaim ? - "Claim your 2 Daily Power now!" : - `You can claim your Daily Power in ${hour}:${minute}.`} + "Mint your 2 Daily Power now!" : + `You can mint your Daily Power in ${hour}:${minute}.`} ) diff --git a/src/renderer/src/queries/wallet.ts b/src/renderer/src/queries/wallet.ts index ff6f00efc..9793ab955 100644 --- a/src/renderer/src/queries/wallet.ts +++ b/src/renderer/src/queries/wallet.ts @@ -25,7 +25,11 @@ export const wallet = { apiClient.wallets.transactions.claim_daily_ttl.$get()), transactions: { - get: (query: Parameters[0]["query"] = {}) => + get: ( + query: Parameters< + typeof apiClient.wallets.transactions.$get + >[0]["query"] = {}, + ) => defineQuery( ["wallet", "transactions", query].filter(Boolean), async () => { @@ -41,15 +45,11 @@ export const wallet = { } export const useWallet = ({ userId }: { userId?: string } = {}) => - useAuthQuery( - wallet.get({ userId }), - { enabled: !!userId }, - ) + useAuthQuery(wallet.get({ userId }), { enabled: !!userId }) -export const useWalletTransactions = (query: Parameters[0] = {}) => - useAuthQuery( - wallet.transactions.get(query), - ) +export const useWalletTransactions = ( + query: Parameters[0] = {}, +) => useAuthQuery(wallet.transactions.get(query)) export const useCreateWalletMutation = () => useMutation({ @@ -65,36 +65,41 @@ export const useCreateWalletMutation = () => }) export const useClaimWalletDailyRewardTtl = () => - useAuthQuery( - wallet.claimDailyRewardTtl(), - { refetchInterval: 5000 }, - ) + useAuthQuery(wallet.claimDailyRewardTtl(), { refetchInterval: 5000 }) -export const useClaimWalletDailyRewardMutation = () => useMutation({ - mutationKey: ["claimWalletDailyReward"], - mutationFn: () => apiClient.wallets.transactions.claim_daily.$post(), - async onError(err) { - toast.error(getFetchErrorMessage(err)) - }, - onSuccess() { - wallet.get().invalidate() - wallet.claimDailyRewardTtl().invalidate() - window.posthog?.capture("daily_reward_claimed") - toast("🎉 Daily reward claimed.") - }, -}) +export const useClaimWalletDailyRewardMutation = () => + useMutation({ + mutationKey: ["claimWalletDailyReward"], + mutationFn: () => apiClient.wallets.transactions.claim_daily.$post(), + async onError(err) { + toast.error(getFetchErrorMessage(err)) + }, + onSuccess() { + wallet.get().invalidate() + wallet.claimDailyRewardTtl().invalidate() + window.posthog?.capture("daily_reward_claimed") + toast("🎉 Daily reward claimed.") + }, + }) -export const useWalletTipMutation = () => useMutation({ - mutationKey: ["walletTip"], - mutationFn: (data: Parameters[0]["json"]) => - apiClient.wallets.transactions.tip.$post({ json: data }), - async onError(err) { - toast.error(getFetchErrorMessage(err)) - }, - onSuccess(_, variables) { - wallet.get().invalidate() - wallet.transactions.get().invalidate() - window.posthog?.capture("tip_sent", { amount: variables.amount, feedId: variables.feedId }) - toast("🎉 Tipped.") - }, -}) +export const useWalletTipMutation = () => + useMutation({ + mutationKey: ["walletTip"], + mutationFn: ( + data: Parameters< + typeof apiClient.wallets.transactions.tip.$post + >[0]["json"], + ) => apiClient.wallets.transactions.tip.$post({ json: data }), + async onError(err) { + toast.error(getFetchErrorMessage(err)) + }, + onSuccess(_, variables) { + wallet.get().invalidate() + wallet.transactions.get().invalidate() + window.posthog?.capture("tip_sent", { + amount: variables.amount, + feedId: variables.feedId, + }) + toast("🎉 Tipped.") + }, + }) diff --git a/src/renderer/src/store/entry/hooks.ts b/src/renderer/src/store/entry/hooks.ts index 9373cedd2..3a9ed5120 100644 --- a/src/renderer/src/store/entry/hooks.ts +++ b/src/renderer/src/store/entry/hooks.ts @@ -6,7 +6,7 @@ import type { FeedViewType } from "@renderer/lib/enum" import type { EntryReadHistoriesModel } from "src/hono" import { useShallow } from "zustand/react/shallow" -import { useFeedIdByView, useFolderFeedsByFeedId } from "../subscription" +import { useFeedIdByView } from "../subscription" import { getEntryIsInView, getFilteredFeedIds } from "./helper" import { useEntryStore } from "./store" import type { EntryFilter, FlatEntryModel } from "./types" @@ -72,30 +72,31 @@ export const useEntryIdsByView = (view: FeedViewType, filter?: EntryFilter) => { return useEntryStore(useShallow(() => getFilteredFeedIds(feedIds, filter))) } -export const useEntryIdsByFolderName = ( - folderName: string, +export const useEntryIdsByFeedIds = ( + feedIds: string[], filter: EntryFilter = {}, -) => { - const feedIds = useFolderFeedsByFeedId(folderName) - return useEntryStore( - useShallow(() => { - if (!feedIds) return null +) => useEntryStore( + useShallow(() => { + if (!feedIds) return null + if ((!Array.isArray(feedIds))) return null - return getFilteredFeedIds(feedIds, filter) - }), - ) -} + return getFilteredFeedIds(feedIds, filter) + }), +) export const useEntryIdsByFeedIdOrView = ( - feedIdOrView: string | FeedViewType, + feedIdOrView: string | string[] | FeedViewType, filter: EntryFilter = {}, ) => { const byView = useEntryIdsByView(feedIdOrView as FeedViewType, filter) const byId = useEntryIdsByFeedId(feedIdOrView as string, filter) - const byFolder = useEntryIdsByFolderName(feedIdOrView as string, filter) - if (typeof feedIdOrView === "string") { - return feedIdOrView.startsWith(ROUTE_FEED_IN_FOLDER) ? byFolder : byId + const byFolder = useEntryIdsByFeedIds(feedIdOrView as string[], filter) + if (Array.isArray(feedIdOrView)) { + return byFolder + } else if (typeof feedIdOrView === "string") { + return byId + } else { + return byView } - return byView } export const useEntryReadHistory = (entryId: string): Omit | null => useEntryStore(useShallow((state) => state.readHistory[entryId])) diff --git a/src/renderer/src/store/entry/store.ts b/src/renderer/src/store/entry/store.ts index f94c000c0..4406ee0e7 100644 --- a/src/renderer/src/store/entry/store.ts +++ b/src/renderer/src/store/entry/store.ts @@ -121,13 +121,25 @@ class EntryActions { ) } - patchManyByFeedId(feedId: string, changed: Partial) { + patchManyByFeedId(feedId: string, changed: Partial, filter?: { + startTime: number + endTime: number + }) { set((state) => produce(state, (draft) => { const ids = draft.entries[feedId] if (!ids) return ids.forEach((entryId) => { + if (filter) { + const entry = draft.flatMapEntries[entryId] + if ( + +new Date(entry.entries.publishedAt) < filter.startTime || + +new Date(entry.entries.publishedAt) > filter.endTime + ) { + return + } + } Object.assign(draft.flatMapEntries[entryId], changed) }) @@ -136,6 +148,15 @@ class EntryActions { ) } + async markReadByFeedId(feedId: string) { + const state = get() + const entries = state.entries[feedId] || [] + await Promise.all( + entries.map((entryId) => this.markRead(feedId, entryId, true)), + ) + feedUnreadActions.updateByFeedId(feedId, 0) + } + private patchAll(changed: Partial) { set((state) => produce(state, (draft) => { @@ -308,15 +329,6 @@ class EntryActions { ) } - async markReadByFeedId(feedId: string) { - const state = get() - const entries = state.entries[feedId] || [] - await Promise.all( - entries.map((entryId) => this.markRead(feedId, entryId, true)), - ) - feedUnreadActions.updateByFeedId(feedId, 0) - } - async markStar(entryId: string, star: boolean) { this.patch(entryId, { collections: star ? diff --git a/src/renderer/src/store/subscription/hooks.ts b/src/renderer/src/store/subscription/hooks.ts index 4530c0846..3df6aa563 100644 --- a/src/renderer/src/store/subscription/hooks.ts +++ b/src/renderer/src/store/subscription/hooks.ts @@ -17,7 +17,13 @@ export const useSubscriptionByView = (view: FeedViewType) => export const useSubscriptionByFeedId = (feedId: FeedId) => useSubscriptionStore((state) => state.data[feedId]) -export const useFolderFeedsByFeedId = (feedId?: string) => +export const useFolderFeedsByFeedId = ({ + feedId, + view, +}: { + feedId?: string + view: FeedViewType +}) => useSubscriptionStore((state): string[] | null => { if (typeof feedId !== "string") return null if (feedId === FEED_COLLECTION_LIST) { @@ -32,7 +38,7 @@ export const useFolderFeedsByFeedId = (feedId?: string) => const feedIds: string[] = [] for (const feedId in state.data) { const subscription = state.data[feedId] - if (subscription.category === folderName || subscription.defaultCategory === folderName) { + if (subscription.view === view && (subscription.category === folderName || subscription.defaultCategory === folderName)) { feedIds.push(feedId) } } diff --git a/src/renderer/src/store/subscription/store.ts b/src/renderer/src/store/subscription/store.ts index 910d4ed6c..cadef701e 100644 --- a/src/renderer/src/store/subscription/store.ts +++ b/src/renderer/src/store/subscription/store.ts @@ -66,6 +66,11 @@ export const useSubscriptionStore = createZustandStore( const set = useSubscriptionStore.setState const get = useSubscriptionStore.getState +type MarkReadFilter = { + startTime: number + endTime: number +} + class SubscriptionActions { async fetchByView(view?: FeedViewType) { const res = await apiClient.subscriptions.$get({ @@ -110,24 +115,53 @@ class SubscriptionActions { ) } - markReadByView(view?: FeedViewType) { - const state = get() - for (const feedId in state.data) { - if (state.data[feedId].view === view) { - feedUnreadActions.updateByFeedId(feedId, 0) - entryActions.patchManyByFeedId(feedId, { read: true }) - } - } + async markReadByView(view: FeedViewType, filter?: MarkReadFilter) { + doMutationAndTransaction( + () => + apiClient.reads.all.$post({ + json: { + view, + ...filter, + }, + }), + async () => { + const state = get() + for (const feedId in state.data) { + if (state.data[feedId].view === view) { + feedUnreadActions.updateByFeedId(feedId, 0) + entryActions.patchManyByFeedId(feedId, { read: true }, filter) + } + } + if (filter) { + feedUnreadActions.fetchUnreadByView(view) + } + }, + ) } - markReadByFolder(folder: string) { - const state = get() - for (const feedId in state.data) { - if (state.data[feedId].category === folder) { - feedUnreadActions.updateByFeedId(feedId, 0) - entryActions.patchManyByFeedId(feedId, { read: true }) - } - } + async markReadByFeedIds( + view: FeedViewType, + feedIds: string[], + filter?: MarkReadFilter, + ) { + doMutationAndTransaction( + () => + apiClient.reads.all.$post({ + json: { + feedIdList: feedIds, + ...filter, + }, + }), + async () => { + for (const feedId of feedIds) { + feedUnreadActions.updateByFeedId(feedId, 0) + entryActions.patchManyByFeedId(feedId, { read: true }, filter) + } + if (filter) { + feedUnreadActions.fetchUnreadByView(view) + } + }, + ) } clear() { @@ -253,7 +287,11 @@ class SubscriptionActions { }), ) - await Promise.all(folderFeedIds.map((feedId) => SubscriptionService.changeView(feedId, changeToView))) + await Promise.all( + folderFeedIds.map((feedId) => + SubscriptionService.changeView(feedId, changeToView), + ), + ) } }