feat: mark read out of scroll (#27)
* feat: mark read out of scroll Signed-off-by: Innei <i@innei.in> * chore: rename Signed-off-by: Innei <i@innei.in> --------- Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
67bab248f5
commit
9920c46fd6
|
|
@ -59,6 +59,7 @@
|
|||
"jotai-dark": "^0.3.0",
|
||||
"jotai-effect": "^1.0.0",
|
||||
"lethargy": "1.0.9",
|
||||
"lodash-es": "4.17.21",
|
||||
"lucide-react": "0.379.0",
|
||||
"ofetch": "1.3.4",
|
||||
"react-hook-form": "7.51.5",
|
||||
|
|
@ -74,6 +75,7 @@
|
|||
"tldts": "6.1.21",
|
||||
"unified": "11.0.4",
|
||||
"unist-util-visit": "5.0.0",
|
||||
"usehooks-ts": "3.1.0",
|
||||
"vfile": "6.0.1",
|
||||
"zod": "3.23.8",
|
||||
"zustand": "4.5.2"
|
||||
|
|
@ -83,6 +85,7 @@
|
|||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@iconify-json/mingcute": "1.1.17",
|
||||
"@tailwindcss/typography": "0.5.13",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ importers:
|
|||
lethargy:
|
||||
specifier: 1.0.9
|
||||
version: 1.0.9
|
||||
lodash-es:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
lucide-react:
|
||||
specifier: 0.379.0
|
||||
version: 0.379.0(react@18.3.1)
|
||||
|
|
@ -161,6 +164,9 @@ importers:
|
|||
unist-util-visit:
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0
|
||||
usehooks-ts:
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0(react@18.3.1)
|
||||
vfile:
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1
|
||||
|
|
@ -183,6 +189,9 @@ importers:
|
|||
'@tailwindcss/typography':
|
||||
specifier: 0.5.13
|
||||
version: 0.5.13(tailwindcss@3.4.3)
|
||||
'@types/lodash-es':
|
||||
specifier: 4.17.12
|
||||
version: 4.17.12
|
||||
'@types/node':
|
||||
specifier: ^20.12.12
|
||||
version: 20.12.12
|
||||
|
|
@ -1843,6 +1852,12 @@ packages:
|
|||
'@types/keyv@3.1.4':
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||
|
||||
'@types/lodash@4.17.4':
|
||||
resolution: {integrity: sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==}
|
||||
|
||||
'@types/mdast@4.0.3':
|
||||
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
|
||||
|
||||
|
|
@ -3612,9 +3627,15 @@ packages:
|
|||
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
|
||||
lodash.castarray@4.4.0:
|
||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||
|
||||
lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
|
||||
lodash.defaults@4.2.0:
|
||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||
|
||||
|
|
@ -4896,6 +4917,12 @@ packages:
|
|||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
usehooks-ts@3.1.0:
|
||||
resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==}
|
||||
engines: {node: '>=16.15.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18
|
||||
|
||||
utf8-byte-length@1.0.4:
|
||||
resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==}
|
||||
|
||||
|
|
@ -6813,6 +6840,12 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 20.12.12
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.4
|
||||
|
||||
'@types/lodash@4.17.4': {}
|
||||
|
||||
'@types/mdast@4.0.3':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
|
|
@ -9064,8 +9097,12 @@ snapshots:
|
|||
dependencies:
|
||||
p-locate: 6.0.0
|
||||
|
||||
lodash-es@4.17.21: {}
|
||||
|
||||
lodash.castarray@4.4.0: {}
|
||||
|
||||
lodash.debounce@4.0.8: {}
|
||||
|
||||
lodash.defaults@4.2.0: {}
|
||||
|
||||
lodash.difference@4.5.0: {}
|
||||
|
|
@ -10519,6 +10556,11 @@ snapshots:
|
|||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
usehooks-ts@3.1.0(react@18.3.1):
|
||||
dependencies:
|
||||
lodash.debounce: 4.0.8
|
||||
react: 18.3.1
|
||||
|
||||
utf8-byte-length@1.0.4: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { FeedIcon } from "@renderer/components/feed-icon"
|
||||
import { Image } from "@renderer/components/ui/image"
|
||||
import dayjs from "@renderer/lib/dayjs"
|
||||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
|
||||
export function ArticleItem({ entry }: { entry: EntriesResponse[number] }) {
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function ArticleItem({ entry }: UniversalItemProps) {
|
||||
return (
|
||||
<div className="mb-5 flex px-2 py-3">
|
||||
<FeedIcon feed={entry.feeds} />
|
||||
<div className="-mt-0.5 line-clamp-5 flex-1 text-sm leading-tight">
|
||||
<div className="text-[10px] font-bold text-zinc-500 flex gap-1">
|
||||
<div className="flex gap-1 text-[10px] font-bold text-zinc-500">
|
||||
<span className="truncate">{entry.feeds.title}</span>
|
||||
<span>·</span>
|
||||
<span className="shrink-0">
|
||||
|
|
@ -30,7 +31,9 @@ export function ArticleItem({ entry }: { entry: EntriesResponse[number] }) {
|
|||
>
|
||||
{entry.entries.title}
|
||||
</div>
|
||||
<div className="text-[13px] text-zinc-500">{entry.entries.description}</div>
|
||||
<div className="text-[13px] text-zinc-500">
|
||||
{entry.entries.description}
|
||||
</div>
|
||||
</div>
|
||||
{entry.entries.images?.[0] && (
|
||||
<Image
|
||||
|
|
|
|||
|
|
@ -1,33 +1,53 @@
|
|||
import { Tabs, TabsList, TabsTrigger } from "@renderer/components/ui/tabs"
|
||||
import { buildStorageNS } from "@renderer/lib/ns"
|
||||
import type { EntryModel } from "@renderer/lib/types"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { apiClient } from "@renderer/queries/api-fetch"
|
||||
import { useEntries } from "@renderer/queries/entries"
|
||||
import { useFeedStore } from "@renderer/store"
|
||||
import { m } from "framer-motion"
|
||||
import type { LegacyRef } from "react"
|
||||
import { forwardRef, useMemo, useState } from "react"
|
||||
import { useAtom, useAtomValue } from "jotai"
|
||||
import { atomWithStorage } from "jotai/utils"
|
||||
import { debounce } from "lodash-es"
|
||||
import type { FC } from "react"
|
||||
import { forwardRef } from "react"
|
||||
import type { ListRange } from "react-virtuoso"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
import { useEventCallback } from "usehooks-ts"
|
||||
import { useShallow } from "zustand/react/shallow"
|
||||
|
||||
import { ArticleItem } from "./article-item"
|
||||
import { EntryItemWrapper } from "./item-wrapper"
|
||||
import { NotificationItem } from "./notification-item"
|
||||
import { PictureItem } from "./picture-item"
|
||||
import { SocialMediaItem } from "./social-media-item"
|
||||
import type { FilterTab, UniversalItemProps } from "./types"
|
||||
import { VideoItem } from "./video-item"
|
||||
|
||||
const gridMode = new Set([2, 3])
|
||||
|
||||
const filterTabAtom = atomWithStorage<FilterTab>(
|
||||
buildStorageNS("entry-tab"),
|
||||
"unread",
|
||||
)
|
||||
export function EntryColumn() {
|
||||
const [filterTab, setFilterTab] = useState("unread")
|
||||
|
||||
const activeList = useFeedStore((state) => state.activeList)
|
||||
const entries = useEntries({
|
||||
level: activeList?.level,
|
||||
id: activeList?.id,
|
||||
view: activeList?.view,
|
||||
...(filterTab === "unread" && { read: false }),
|
||||
})
|
||||
const entries = useEntriesByTab()
|
||||
|
||||
let Item
|
||||
const entriesIds = (entries.data?.pages?.flatMap((page) =>
|
||||
page.data?.map((entry) => entry.entries.id),
|
||||
) || []) as string[]
|
||||
|
||||
const entriesId2Map =
|
||||
entries.data?.pages?.reduce((acc, page) => {
|
||||
if (!page.data) return acc
|
||||
for (const entry of page.data) {
|
||||
acc[entry.entries.id] = entry
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, EntryModel>) ?? {}
|
||||
|
||||
let Item: FC<UniversalItemProps>
|
||||
switch (activeList?.view) {
|
||||
case 0: {
|
||||
Item = ArticleItem
|
||||
|
|
@ -54,7 +74,116 @@ export function EntryColumn() {
|
|||
}
|
||||
}
|
||||
|
||||
const List = useMemo(() => forwardRef((props, ref: LegacyRef<HTMLDivElement>) => (
|
||||
const handleRangeChange = useEventCallback(
|
||||
debounce(
|
||||
async ({ startIndex }: ListRange) => {
|
||||
const idSlice = entriesIds?.slice(0, startIndex)
|
||||
if (!idSlice) return
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const requestTasks = [] as Promise<any>[]
|
||||
for (const id of idSlice) {
|
||||
const entry = entriesId2Map[id]
|
||||
if (!entry) continue
|
||||
const isRead = entry.read
|
||||
if (!isRead) {
|
||||
// TODO csrfToken should omit and batch request
|
||||
requestTasks.push(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
apiClient.reads.$post({ json: { entryId: id } as any }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(requestTasks)
|
||||
|
||||
// TODO optimistic update
|
||||
|
||||
if (requestTasks.length > 0) entries.refetch()
|
||||
},
|
||||
1000,
|
||||
{ leading: false },
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-1 flex-col">
|
||||
<ListHeader />
|
||||
<Virtuoso
|
||||
className="h-0 grow"
|
||||
components={{
|
||||
List: ListContent,
|
||||
}}
|
||||
rangeChanged={handleRangeChange}
|
||||
totalCount={entriesIds?.length}
|
||||
endReached={() => entries.hasNextPage && entries.fetchNextPage()}
|
||||
data={entries.data?.pages.flatMap((page) => page.data)}
|
||||
itemContent={(_, entry) => {
|
||||
if (!entry) return null
|
||||
return (
|
||||
<EntryItemWrapper
|
||||
key={entry.entries.id}
|
||||
entry={entry}
|
||||
view={activeList?.view}
|
||||
>
|
||||
<Item entry={entry} />
|
||||
</EntryItemWrapper>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useEntriesByTab = () => {
|
||||
const activeList = useFeedStore(useShallow((state) => state.activeList))
|
||||
const filterTab = useAtomValue(filterTabAtom)
|
||||
|
||||
return useEntries({
|
||||
level: activeList?.level,
|
||||
id: activeList?.id,
|
||||
view: activeList?.view,
|
||||
...(filterTab === "unread" && { read: false }),
|
||||
})
|
||||
}
|
||||
|
||||
const ListHeader: FC = () => {
|
||||
const activeList = useFeedStore(useShallow((state) => state.activeList))
|
||||
const [filterTab, setFilterTab] = useAtom(filterTabAtom)
|
||||
const entries = useEntriesByTab()
|
||||
const total = entries.data?.pages?.reduce(
|
||||
(acc, page) => acc + (page.data?.length || 0),
|
||||
0,
|
||||
)
|
||||
return (
|
||||
<div className="mb-5 flex w-full items-center justify-between px-9">
|
||||
<div>
|
||||
<div className="text-lg font-bold">{activeList?.name}</div>
|
||||
<div className="text-xs font-medium text-zinc-400">
|
||||
{total}
|
||||
{" "}
|
||||
Items
|
||||
</div>
|
||||
</div>
|
||||
{/* @ts-expect-error */}
|
||||
<Tabs value={filterTab} onValueChange={setFilterTab}>
|
||||
<TabsList variant="rounded">
|
||||
<TabsTrigger variant="rounded" value="unread">
|
||||
Unread
|
||||
</TabsTrigger>
|
||||
<TabsTrigger variant="rounded" value="all">
|
||||
All
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ListContent = forwardRef<HTMLDivElement>((props, ref) => {
|
||||
const activeList = useFeedStore(useShallow((state) => state.activeList))
|
||||
|
||||
return (
|
||||
<m.div
|
||||
key={`${activeList?.level}-${activeList?.id}`}
|
||||
initial={{ opacity: 0.01, y: 100 }}
|
||||
|
|
@ -69,45 +198,5 @@ export function EntryColumn() {
|
|||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)), [filterTab, activeList])
|
||||
|
||||
const Header = useMemo(() => () => (
|
||||
<div className="mb-5 px-9 flex justify-between items-center w-full">
|
||||
<div>
|
||||
<div className="text-lg font-bold">{activeList?.name}</div>
|
||||
<div className="text-xs font-medium text-zinc-400">
|
||||
{entries.data?.pages?.[0].total}
|
||||
{" "}
|
||||
Items
|
||||
</div>
|
||||
</div>
|
||||
<Tabs value={filterTab} onValueChange={setFilterTab}>
|
||||
<TabsList variant="rounded">
|
||||
<TabsTrigger variant="rounded" value="unread">Unread</TabsTrigger>
|
||||
<TabsTrigger variant="rounded" value="all">All</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
), [activeList, entries.data?.pages?.[0].total])
|
||||
|
||||
return (
|
||||
<Virtuoso
|
||||
components={{
|
||||
Header,
|
||||
List,
|
||||
}}
|
||||
endReached={() =>
|
||||
entries.hasNextPage && entries.fetchNextPage()}
|
||||
data={entries.data?.pages}
|
||||
itemContent={(_, page) => page?.data?.map((entry) => (
|
||||
<EntryItemWrapper
|
||||
key={entry.entries.id}
|
||||
entry={entry}
|
||||
view={activeList?.view}
|
||||
>
|
||||
<Item entry={entry} />
|
||||
</EntryItemWrapper>
|
||||
))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useEntryActions } from "@renderer/hooks/useEntryActions"
|
||||
import { usePrevious } from "@renderer/hooks/usePrevious"
|
||||
import { useUpdateEntry } from "@renderer/hooks/useUpdateEntry"
|
||||
import { showNativeMenu } from "@renderer/lib/native-menu"
|
||||
import type { EntriesResponse, EntryResponse } from "@renderer/lib/types"
|
||||
|
|
@ -7,8 +6,6 @@ import { cn } from "@renderer/lib/utils"
|
|||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { feedActions, useFeedStore } from "@renderer/store"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useEffect } from "react"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
|
||||
export function EntryItemWrapper({
|
||||
entry,
|
||||
|
|
@ -46,18 +43,18 @@ export function EntryItemWrapper({
|
|||
},
|
||||
})
|
||||
|
||||
const { ref, inView } = useInView({
|
||||
threshold: 1,
|
||||
delay: 1000,
|
||||
})
|
||||
const prevInView = usePrevious(inView)
|
||||
useEffect(() => {
|
||||
if (prevInView && !inView && !entry.read) {
|
||||
read.mutate()
|
||||
}
|
||||
}, [entry.read, inView, read, prevInView])
|
||||
// const { ref, inView } = useInView({
|
||||
// threshold: 1,
|
||||
// delay: 1000,
|
||||
// })
|
||||
// const prevInView = usePrevious(inView)
|
||||
// useEffect(() => {
|
||||
// if (prevInView && !inView && !entry.read) {
|
||||
// read.mutate()
|
||||
// }
|
||||
// }, [entry.read, inView, read, prevInView])
|
||||
|
||||
if (!entry?.entries.url || view === undefined) return children
|
||||
// if (!entry?.entries.url || view === undefined) return children
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -67,7 +64,7 @@ export function EntryItemWrapper({
|
|||
activeEntry === entry.entries.id && "bg-native-active/50",
|
||||
entry.read && "text-foreground/80",
|
||||
)}
|
||||
ref={ref}
|
||||
// ref={ref}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
feedActions.setActiveEntry(entry.entries.id)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { FeedIcon } from "@renderer/components/feed-icon"
|
||||
import dayjs from "@renderer/lib/dayjs"
|
||||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
|
||||
export function NotificationItem({
|
||||
entry,
|
||||
}: {
|
||||
entry: EntriesResponse[number]
|
||||
}) {
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function NotificationItem({ entry }: UniversalItemProps) {
|
||||
return (
|
||||
<div className="mb-5 flex px-2 py-3">
|
||||
<FeedIcon feed={entry.feeds} />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { FeedIcon } from "@renderer/components/feed-icon"
|
||||
import { Image } from "@renderer/components/ui/image"
|
||||
import dayjs from "@renderer/lib/dayjs"
|
||||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
|
||||
export function PictureItem({ entry }: { entry: EntriesResponse[number] }) {
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function PictureItem({ entry }: UniversalItemProps) {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { FeedIcon } from "@renderer/components/feed-icon"
|
||||
import { Image } from "@renderer/components/ui/image"
|
||||
import dayjs from "@renderer/lib/dayjs"
|
||||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
|
||||
export function SocialMediaItem({ entry }: { entry: EntriesResponse[number] }) {
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function SocialMediaItem({ entry }: UniversalItemProps) {
|
||||
return (
|
||||
<div className="mb-5 flex px-2 py-3">
|
||||
<FeedIcon feed={entry.feeds} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
|
||||
export interface UniversalItemProps { entry: EntriesResponse[number] }
|
||||
|
||||
export type FilterTab = "all" | "unread"
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { FeedIcon } from "@renderer/components/feed-icon"
|
||||
import { Image } from "@renderer/components/ui/image"
|
||||
import dayjs from "@renderer/lib/dayjs"
|
||||
import type { EntriesResponse } from "@renderer/lib/types"
|
||||
|
||||
export function VideoItem({ entry }: { entry: EntriesResponse[number] }) {
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function VideoItem({ entry }: UniversalItemProps) {
|
||||
return (
|
||||
<div className="flex">
|
||||
<div className="w-full">
|
||||
|
|
|
|||
|
|
@ -47,15 +47,15 @@ export const useUpdateEntry = ({
|
|||
queryKey: ["subscriptions"],
|
||||
})
|
||||
entriesData.forEach(([key, data]: [QueryKey, unknown]) => {
|
||||
const chage = changed.read ? -1 : 1
|
||||
const change = changed.read ? -1 : 1
|
||||
const assertData = data as SubscriptionsResponse
|
||||
const finaldata = produce(assertData, (assertData) => {
|
||||
for (const list of assertData.list) {
|
||||
for (const item of list.list) {
|
||||
if (item.feeds.id === feedId) {
|
||||
assertData.unread += chage
|
||||
list.unread += chage
|
||||
item.unread = (item.unread || 0) + chage
|
||||
assertData.unread += change
|
||||
list.unread += change
|
||||
item.unread = (item.unread || 0) + change
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
const ns = "follow"
|
||||
export const buildStorageNS = (key: string) => `${ns}:${key}`
|
||||
|
|
@ -38,6 +38,7 @@ export type EntriesResponse = Array<
|
|||
>[number]
|
||||
>
|
||||
|
||||
export type EntryModel = EntriesResponse[number]
|
||||
export type DiscoverResponse = Array<
|
||||
Exclude<
|
||||
InferResponseType<typeof apiClient.discover.$post>["data"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue