refactor: query define for type safe and fix type error (#18)
* refactor: query Signed-off-by: Innei <i@innei.in> * update Signed-off-by: Innei <i@innei.in> * fix: type Signed-off-by: Innei <i@innei.in> --------- Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
a16c493ccd
commit
91e80d4dcc
|
|
@ -0,0 +1,36 @@
|
|||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
name: CI Typecheck and Lint
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Lint and Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
lfs: true
|
||||
- name: Checkout LFS objects
|
||||
run: git lfs checkout
|
||||
|
||||
- uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Lint and Typecheck
|
||||
|
||||
run: |
|
||||
pnpm run typecheck && npm run lint
|
||||
|
|
@ -54,6 +54,7 @@
|
|||
"electron-updater": "^6.1.7",
|
||||
"framer-motion": "11.2.6",
|
||||
"hast-util-to-jsx-runtime": "2.3.0",
|
||||
"immer": "10.1.1",
|
||||
"jotai": "2.8.1",
|
||||
"jotai-dark": "^0.3.0",
|
||||
"jotai-effect": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ importers:
|
|||
hast-util-to-jsx-runtime:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0
|
||||
immer:
|
||||
specifier: 10.1.1
|
||||
version: 10.1.1
|
||||
jotai:
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1(@types/react@18.3.3)(react@18.3.1)
|
||||
|
|
@ -160,7 +163,7 @@ importers:
|
|||
version: 3.23.8
|
||||
zustand:
|
||||
specifier: 4.5.2
|
||||
version: 4.5.2(@types/react@18.3.3)(react@18.3.1)
|
||||
version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)
|
||||
devDependencies:
|
||||
'@egoist/tailwindcss-icons':
|
||||
specifier: 1.8.0
|
||||
|
|
@ -3094,6 +3097,7 @@ packages:
|
|||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
global-agent@3.0.0:
|
||||
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
||||
|
|
@ -3283,6 +3287,9 @@ packages:
|
|||
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
immer@10.1.1:
|
||||
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -4418,6 +4425,7 @@ packages:
|
|||
|
||||
rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
roarr@2.15.4:
|
||||
|
|
@ -8767,6 +8775,8 @@ snapshots:
|
|||
|
||||
ignore@5.3.1: {}
|
||||
|
||||
immer@10.1.1: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
|
|
@ -10602,11 +10612,12 @@ snapshots:
|
|||
|
||||
zod@3.23.8: {}
|
||||
|
||||
zustand@4.5.2(@types/react@18.3.3)(react@18.3.1):
|
||||
zustand@4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1):
|
||||
dependencies:
|
||||
use-sync-external-store: 1.2.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
immer: 10.1.1
|
||||
react: 18.3.1
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
export abstract class RequestError extends Error {
|
||||
name = "RequestError"
|
||||
}
|
||||
export class UnAuthorizedError extends RequestError {
|
||||
name = "UnAuthorizedError"
|
||||
|
||||
constructor(message?: string) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class UnprocessableEntityError extends RequestError {
|
||||
name = "UnprocessableEntityError"
|
||||
|
||||
constructor(message?: string) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import type { ReactNode } from "react"
|
||||
import { useState } from "react"
|
||||
|
||||
function Versions(): JSX.Element {
|
||||
const [versions] = useState(window.electron.process.versions)
|
||||
function Versions(): ReactNode {
|
||||
const [versions] = useState(window.electron?.process.versions)
|
||||
|
||||
if (!versions) return null
|
||||
return (
|
||||
<ul className="versions">
|
||||
<li className="electron-version">
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export function EntryItemWrapper({
|
|||
e.stopPropagation()
|
||||
feedActions.setActiveEntry(entry.id)
|
||||
}}
|
||||
onDoubleClick={() => window.open(entry.url, "_blank")}
|
||||
onDoubleClick={() => entry.url && window.open(entry.url, "_blank")}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault()
|
||||
showNativeMenu(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import { useBizQuery } from "@renderer/hooks/useBizQuery"
|
||||
import { parseHtml } from "@renderer/lib/parse-html"
|
||||
import type { ActiveEntry } from "@renderer/lib/types"
|
||||
import { useEntry } from "@renderer/queries/entries"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { m } from "framer-motion"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { EntryShare } from "./share"
|
||||
|
||||
export function EntryContent({ entryId }: { entryId: ActiveEntry }) {
|
||||
const entry = useEntry({
|
||||
id: entryId,
|
||||
})
|
||||
const entry = useBizQuery(Queries.entries.byId(entryId))
|
||||
|
||||
const [content, setContent] = useState<JSX.Element>()
|
||||
|
||||
|
|
@ -23,6 +22,8 @@ export function EntryContent({ entryId }: { entryId: ActiveEntry }) {
|
|||
}
|
||||
}, [entry.data?.content])
|
||||
|
||||
if (!entry.data) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<EntryShare entry={entry.data} view={0} />
|
||||
|
|
@ -35,7 +36,7 @@ export function EntryContent({ entryId }: { entryId: ActiveEntry }) {
|
|||
>
|
||||
<div>
|
||||
<a
|
||||
href={entry.data?.url}
|
||||
href={entry.data?.url || void 0}
|
||||
target="_blank"
|
||||
className="mx-auto block max-w-[598px] rounded-md p-6 transition-colors hover:bg-zinc-100"
|
||||
rel="noreferrer"
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import {
|
|||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@renderer/components/ui/alert-dialog"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
|
||||
export function CategoryRemoveDialog({
|
||||
feedIdList,
|
||||
|
|
@ -21,7 +22,6 @@ export function CategoryRemoveDialog({
|
|||
category: string
|
||||
view?: number
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const renameMutation = useMutation({
|
||||
mutationFn: async () =>
|
||||
apiFetch("/categories", {
|
||||
|
|
@ -32,9 +32,8 @@ export function CategoryRemoveDialog({
|
|||
},
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", view],
|
||||
})
|
||||
Queries.subscription.byView(view).invalidate()
|
||||
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ import {
|
|||
FormMessage,
|
||||
} from "@renderer/components/ui/form"
|
||||
import { Input } from "@renderer/components/ui/input"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
|
||||
|
|
@ -40,7 +41,6 @@ export function CategoryRenameDialog({
|
|||
},
|
||||
})
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const renameMutation = useMutation({
|
||||
mutationFn: async (values: z.infer<typeof formSchema>) =>
|
||||
apiFetch("/categories", {
|
||||
|
|
@ -51,9 +51,8 @@ export function CategoryRenameDialog({
|
|||
},
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", view],
|
||||
})
|
||||
Queries.subscription.byView(view).invalidate()
|
||||
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import { client } from "@renderer/lib/client"
|
|||
import { levels } from "@renderer/lib/constants"
|
||||
import { showNativeMenu } from "@renderer/lib/native-menu"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import type { Response as SubscriptionsResponse } from "@renderer/queries/subscriptions"
|
||||
import { feedActions, useFeedActiveList } from "@renderer/store"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
|
|
@ -33,7 +34,6 @@ export function FeedCategory({
|
|||
const [open, setOpen] = useState(!data.name)
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const feedIdList = data.list.map((feed) => feed.feedId)
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: async () =>
|
||||
|
|
@ -45,9 +45,7 @@ export function FeedCategory({
|
|||
},
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", view],
|
||||
})
|
||||
Queries.subscription.byView(view).invalidate()
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ import dayjs from "@renderer/lib/dayjs"
|
|||
import { showNativeMenu } from "@renderer/lib/native-menu"
|
||||
import type { SubscriptionResponse } from "@renderer/lib/types"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { feedActions, useFeedActiveList } from "@renderer/store"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useState } from "react"
|
||||
|
||||
export function FeedItem({
|
||||
|
|
@ -45,7 +46,6 @@ export function FeedItem({
|
|||
|
||||
const { toast } = useToast()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: async (feed: SubscriptionResponse[number]) =>
|
||||
apiFetch("/subscriptions", {
|
||||
|
|
@ -55,9 +55,8 @@ export function FeedItem({
|
|||
},
|
||||
}),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", feed.view],
|
||||
})
|
||||
Queries.subscription.byView(variables.view).invalidate()
|
||||
|
||||
toast({
|
||||
duration: 3000,
|
||||
description: (
|
||||
|
|
@ -82,9 +81,8 @@ export function FeedItem({
|
|||
isPrivate: variables.isPrivate,
|
||||
},
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", feed.view],
|
||||
})
|
||||
|
||||
Queries.subscription.byView(feed.view).invalidate()
|
||||
}}
|
||||
>
|
||||
Undo
|
||||
|
|
@ -142,7 +140,7 @@ export function FeedItem({
|
|||
{
|
||||
type: "text",
|
||||
label: "Open Site in Browser",
|
||||
click: () => window.open(feed.feeds.siteUrl, "_blank"),
|
||||
click: () => feed.feeds.siteUrl && window.open(feed.feeds.siteUrl, "_blank"),
|
||||
},
|
||||
],
|
||||
e,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useBizQuery } from "@renderer/hooks/useBizQuery"
|
||||
import { levels, views } from "@renderer/lib/constants"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { useSubscriptions } from "@renderer/queries/subscriptions"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { feedActions } from "@renderer/store"
|
||||
import { useState } from "react"
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ export function FeedList({
|
|||
view?: number
|
||||
hideTitle?: boolean
|
||||
}) {
|
||||
const subscriptions = useSubscriptions(view)
|
||||
const subscriptions = useBizQuery(Queries.subscription.byView(view))
|
||||
const [expansion, setExpansion] = useState(false)
|
||||
const { setActiveList } = feedActions
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function FollowSummary({
|
|||
return (
|
||||
<div className="max-w-[462px] select-text space-y-1 text-sm">
|
||||
<a
|
||||
href={feed.siteUrl}
|
||||
href={feed.siteUrl || void 0}
|
||||
target="_blank"
|
||||
className="flex items-center"
|
||||
rel="noreferrer"
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import {
|
|||
SelectValue,
|
||||
} from "@renderer/components/ui/select"
|
||||
import { Switch } from "@renderer/components/ui/switch"
|
||||
import { useBizQuery } from "@renderer/hooks/useBizQuery"
|
||||
import { views } from "@renderer/lib/constants"
|
||||
import type { SubscriptionResponse } from "@renderer/lib/types"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useSubscriptionCategories } from "@renderer/queries/subscriptions"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ import { FollowSummary } from "../feed-summary"
|
|||
|
||||
const formSchema = z.object({
|
||||
view: z.string(),
|
||||
category: z.string().optional(),
|
||||
category: z.string().nullable().optional(),
|
||||
isPrivate: z.boolean().optional(),
|
||||
})
|
||||
|
||||
|
|
@ -68,7 +69,6 @@ export function FollowDialog({
|
|||
},
|
||||
})
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const followMutation = useMutation({
|
||||
mutationFn: async (values: z.infer<typeof formSchema>) =>
|
||||
apiFetch("/subscriptions", {
|
||||
|
|
@ -83,13 +83,10 @@ export function FollowDialog({
|
|||
}),
|
||||
onSuccess: (_, variables) => {
|
||||
if (isSubscribed && variables.view !== `${feed.view}`) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", feed.view],
|
||||
})
|
||||
Queries.subscription.byView(feed.view).invalidate()
|
||||
}
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions", Number.parseInt(variables.view)],
|
||||
})
|
||||
Queries.subscription.byView(Number.parseInt(variables.view)).invalidate()
|
||||
|
||||
onSuccess?.(variables)
|
||||
},
|
||||
})
|
||||
|
|
@ -98,7 +95,9 @@ export function FollowDialog({
|
|||
followMutation.mutate(values)
|
||||
}
|
||||
|
||||
const categories = useSubscriptionCategories(Number.parseInt(form.watch("view")))
|
||||
const categories = useBizQuery(
|
||||
Queries.subscription.categories(Number.parseInt(form.watch("view"))),
|
||||
)
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
|
|
@ -153,8 +152,8 @@ export function FollowDialog({
|
|||
</div>
|
||||
<FormControl>
|
||||
<AutoComplete
|
||||
options={categories.data}
|
||||
emptyMessage="No resulsts."
|
||||
options={categories.data || []}
|
||||
emptyMessage="No results."
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
|
|
|||
|
|
@ -30,10 +30,13 @@ const formSchema = z.object({
|
|||
keyword: z.string().min(1),
|
||||
})
|
||||
|
||||
const info: Record<string, {
|
||||
label: string
|
||||
prefix?: string
|
||||
}> = {
|
||||
const info: Record<
|
||||
string,
|
||||
{
|
||||
label: string
|
||||
prefix?: string
|
||||
}
|
||||
> = {
|
||||
general: {
|
||||
label: "Any URL or Keyword",
|
||||
},
|
||||
|
|
@ -150,31 +153,34 @@ export function FollowForm({ type }: { type: string }) {
|
|||
<div className="grid grid-cols-4 gap-4">
|
||||
{item.entries
|
||||
.filter((e) => !!e)
|
||||
.map((entry) => (
|
||||
<a
|
||||
key={entry.id}
|
||||
href={entry.url}
|
||||
target="_blank"
|
||||
className="flex min-w-0 flex-1 flex-col items-center gap-1"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{entry.images?.[0] ?
|
||||
(
|
||||
<Image
|
||||
src={entry.images?.[0]}
|
||||
className="aspect-square w-full"
|
||||
/>
|
||||
) :
|
||||
(
|
||||
<div className="flex aspect-square w-full overflow-hidden rounded bg-stone-100 p-2 text-xs leading-tight text-zinc-500">
|
||||
{entry.title}
|
||||
</div>
|
||||
)}
|
||||
<div className="line-clamp-2 w-full text-xs leading-tight">
|
||||
{entry.title}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
.map((entry) => {
|
||||
const assertEntry = entry as EntriesResponse[number]
|
||||
return (
|
||||
<a
|
||||
key={assertEntry.id}
|
||||
href={assertEntry.url || void 0}
|
||||
target="_blank"
|
||||
className="flex min-w-0 flex-1 flex-col items-center gap-1"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{assertEntry.images?.[0] ?
|
||||
(
|
||||
<Image
|
||||
src={assertEntry.images?.[0]}
|
||||
className="aspect-square w-full"
|
||||
/>
|
||||
) :
|
||||
(
|
||||
<div className="flex aspect-square w-full overflow-hidden rounded bg-stone-100 p-2 text-xs leading-tight text-zinc-500">
|
||||
{assertEntry.title}
|
||||
</div>
|
||||
)}
|
||||
<div className="line-clamp-2 w-full text-xs leading-tight">
|
||||
{assertEntry.title}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ import {
|
|||
import { Input } from "@renderer/components/ui/input"
|
||||
import type { FeedResponse } from "@renderer/lib/types"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
|
||||
|
|
@ -54,7 +55,6 @@ export function FollowImport() {
|
|||
resolver: zodResolver(formSchema),
|
||||
})
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
const formData = new FormData()
|
||||
|
|
@ -72,9 +72,7 @@ export function FollowImport() {
|
|||
return data
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["subscriptions"],
|
||||
})
|
||||
Queries.subscription.byView().invalidateRoot()
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import { useCallback, useState } from "react"
|
|||
import * as React from "react"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value"> {
|
||||
// TODO isLoading
|
||||
options: string[]
|
||||
emptyMessage: string
|
||||
value?: string
|
||||
value?: string | null
|
||||
}
|
||||
|
||||
export const AutoComplete = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
|
|
@ -51,7 +52,7 @@ export const AutoComplete = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
setOpen(true)
|
||||
props.onFocus?.(e)
|
||||
}}
|
||||
value={value}
|
||||
value={value || void 0}
|
||||
onValueChange={(value) => {
|
||||
setOpen(true)
|
||||
onValueChange(value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import type { FC, PropsWithChildren } from "react"
|
||||
|
||||
declare global {
|
||||
|
||||
export type Component<P = object> = FC<ComponentType & P>
|
||||
|
||||
export type ComponentType<P = object> = {
|
||||
className?: string
|
||||
} & PropsWithChildren &
|
||||
P
|
||||
|
||||
}
|
||||
|
||||
export {}
|
||||
|
|
@ -518,7 +518,7 @@ declare const routes: hono_hono_base.HonoBase<hono.Env, {
|
|||
author: string | null;
|
||||
changedAt: string;
|
||||
publishedAt: string;
|
||||
images: string | null;
|
||||
images: string[] | null;
|
||||
categories: string | null;
|
||||
collected: boolean;
|
||||
read: boolean;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { RequestError } from "@renderer/biz/error"
|
||||
import type { DefinedQuery } from "@renderer/lib/defineQuery"
|
||||
import type {
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseInfiniteQueryResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query"
|
||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
|
||||
import type { FetchError } from "ofetch"
|
||||
|
||||
// TODO split normal define query and infinite define query for better type checking
|
||||
export type SafeReturnType<T> = T extends (...args: any[]) => infer R
|
||||
? R
|
||||
: never
|
||||
|
||||
export function useBizQuery<
|
||||
TQuery extends DefinedQuery<QueryKey, any>,
|
||||
TError = FetchError | RequestError,
|
||||
TQueryFnData = Awaited<ReturnType<TQuery["fn"]>>,
|
||||
TData = TQueryFnData,
|
||||
>(
|
||||
query: TQuery,
|
||||
options: Omit<
|
||||
UseQueryOptions<TQueryFnData, TError>,
|
||||
"queryKey" | "queryFn"
|
||||
> = {},
|
||||
): UseQueryResult<TData, FetchError> {
|
||||
// @ts-expect-error
|
||||
return useQuery({
|
||||
queryKey: query.key,
|
||||
queryFn: query.fn,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export function useBizInfiniteQuery<
|
||||
T extends DefinedQuery<any, any>,
|
||||
E = FetchError | RequestError,
|
||||
FNR = Awaited<ReturnType<T["fn"]>>,
|
||||
R = FNR,
|
||||
>(
|
||||
query: T,
|
||||
options: Omit<UseInfiniteQueryOptions<FNR, E>, "queryKey" | "queryFn">,
|
||||
): UseInfiniteQueryResult<InfiniteData<R>, FetchError | RequestError> {
|
||||
// @ts-expect-error
|
||||
return useInfiniteQuery<T, E>({
|
||||
// @ts-expect-error
|
||||
queryFn: query.fn,
|
||||
queryKey: query.key,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,49 +1,36 @@
|
|||
import type { EntriesResponse, ListResponse } from "@renderer/lib/types"
|
||||
import { Queries } from "@renderer/queries"
|
||||
import type { InfiniteData, QueryKey } from "@tanstack/react-query"
|
||||
import {
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
export const useUpdateEntry = ({
|
||||
entryId,
|
||||
}: {
|
||||
entryId?: string
|
||||
}) => {
|
||||
export const useUpdateEntry = ({ entryId }: { entryId?: string }) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const updateEntry = (
|
||||
changed: Partial<EntriesResponse[number]>,
|
||||
) => {
|
||||
const key = ["entry", entryId]
|
||||
const data = queryClient.getQueryData(key)
|
||||
if (data) {
|
||||
queryClient.setQueryData(
|
||||
key,
|
||||
Object.assign({}, data, changed),
|
||||
)
|
||||
}
|
||||
const updateEntry = (changed: Partial<EntriesResponse[number]>) => {
|
||||
const query = Queries.entries.byId(entryId)
|
||||
|
||||
query.setData((draft) => {
|
||||
if (!draft) return
|
||||
Object.assign(draft, changed)
|
||||
})
|
||||
|
||||
const entriesData = queryClient.getQueriesData({
|
||||
queryKey: ["entries"],
|
||||
})
|
||||
entriesData.forEach(
|
||||
([key, data]: [
|
||||
QueryKey,
|
||||
unknown,
|
||||
]) => {
|
||||
const list = (data as InfiniteData<ListResponse<EntriesResponse>>)?.pages?.[0]?.data
|
||||
if (list) {
|
||||
for (const item of list) {
|
||||
if (item.id === entryId) {
|
||||
for (const [key, value] of Object.entries(changed)) {
|
||||
item[key] = value
|
||||
}
|
||||
queryClient.setQueryData(key, data)
|
||||
entriesData.forEach(([key, data]: [QueryKey, unknown]) => {
|
||||
const assertData = data as InfiniteData<ListResponse<EntriesResponse>>
|
||||
const list = assertData?.pages?.[0]?.data
|
||||
if (list) {
|
||||
for (const item of list) {
|
||||
if (item.id === entryId) {
|
||||
for (const [key, value] of Object.entries(changed)) {
|
||||
item[key] = value
|
||||
}
|
||||
queryClient.setQueryData<typeof assertData>(key, assertData)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return updateEntry
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type {
|
||||
InfiniteData,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
} from "@tanstack/react-query"
|
||||
import type { Draft, nothing } from "immer"
|
||||
import { produce } from "immer"
|
||||
|
||||
import { queryClient } from "./query-client"
|
||||
|
||||
// From immer, but immer not export this type
|
||||
type ValidRecipeReturnType<State> =
|
||||
| State
|
||||
| void
|
||||
| undefined
|
||||
| (State extends undefined ? typeof nothing : never)
|
||||
|
||||
export type DefinedQuery<TQueryKey extends QueryKey, TData> = Readonly<{
|
||||
key: TQueryKey
|
||||
fn: QueryFunction<TData>
|
||||
rootKey?: QueryKey
|
||||
|
||||
cancel: (key?: (key: TQueryKey) => QueryKey) => Promise<void>
|
||||
remove: (key?: (key: TQueryKey) => QueryKey) => Promise<void>
|
||||
|
||||
invalidate: (key?: (key: TQueryKey) => QueryKey) => Promise<void>
|
||||
invalidateRoot: () => void
|
||||
|
||||
refetch: () => Promise<TData | undefined>
|
||||
prefetch: () => Promise<void>
|
||||
|
||||
setData: <Data = TData>(
|
||||
updater: (draft: Draft<Data>) => ValidRecipeReturnType<Draft<Data>>
|
||||
) => void
|
||||
setInfiniteData: (
|
||||
updater: (
|
||||
draft: Draft<InfiniteData<TData>>
|
||||
) => ValidRecipeReturnType<Draft<InfiniteData<TData>>>
|
||||
) => void
|
||||
getData: () => TData | undefined
|
||||
|
||||
optimisticUpdate: <Data = TData>(
|
||||
updater: (draft: Draft<Data>) => ValidRecipeReturnType<Draft<Data>> | void
|
||||
) => Promise<{
|
||||
previousData: Awaited<Data> | undefined
|
||||
restore: () => void
|
||||
invalidate: () => void
|
||||
}>
|
||||
|
||||
optimisticInfiniteUpdate: (
|
||||
updater: (
|
||||
draft: Draft<InfiniteData<TData>>
|
||||
) => ValidRecipeReturnType<Draft<InfiniteData<TData>>>
|
||||
) => Promise<{
|
||||
previousData: Awaited<InfiniteData<TData>> | undefined
|
||||
restore: () => void
|
||||
invalidate: () => void
|
||||
}>
|
||||
}>
|
||||
|
||||
export type DefinedQueryOptions<TData> = {
|
||||
// shouldPersist?: boolean;
|
||||
rootKey?: QueryKey
|
||||
|
||||
onCancel?: () => void | Promise<void>
|
||||
onInvalidate?: () => void | Promise<void>
|
||||
onInvalidateRoot?: () => void
|
||||
onRefetch?: (data?: TData) => void
|
||||
onRefetchRoot?: () => void
|
||||
onOptimisticUpdate?: () => void
|
||||
onOptimisticUpdateRestore?: () => void
|
||||
}
|
||||
|
||||
export function defineQuery<
|
||||
TQueryKey extends QueryKey,
|
||||
TQueryFn extends QueryFunction<unknown>,
|
||||
TData = Awaited<ReturnType<TQueryFn>>,
|
||||
>(
|
||||
key: TQueryKey,
|
||||
fn: TQueryFn,
|
||||
options?: DefinedQueryOptions<TData>
|
||||
): DefinedQuery<TQueryKey, TData>
|
||||
|
||||
export function defineQuery<
|
||||
TQueryKey extends QueryKey,
|
||||
TQueryFn extends QueryFunction<any>,
|
||||
TData = Awaited<ReturnType<TQueryFn>>,
|
||||
>(key: TQueryKey, fn: TQueryFn, options?: DefinedQueryOptions<TData>) {
|
||||
const queryDefine: DefinedQuery<TQueryKey, TData> = {
|
||||
key,
|
||||
fn,
|
||||
rootKey: options?.rootKey,
|
||||
|
||||
invalidateRoot: () => {
|
||||
if (options?.rootKey) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: options.rootKey,
|
||||
refetchType: "all",
|
||||
})
|
||||
options?.onInvalidateRoot?.()
|
||||
}
|
||||
},
|
||||
prefetch: async () => {
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: key,
|
||||
queryFn: fn,
|
||||
})
|
||||
},
|
||||
cancel: async (keyExtactor) => {
|
||||
const queryKey =
|
||||
typeof keyExtactor === "function" ? keyExtactor(key) : key
|
||||
await queryClient.cancelQueries({
|
||||
queryKey,
|
||||
})
|
||||
options?.onCancel?.()
|
||||
},
|
||||
remove: async (keyExtactor) => {
|
||||
const queryKey =
|
||||
typeof keyExtactor === "function" ? keyExtactor(key) : key
|
||||
queryClient.removeQueries({ queryKey })
|
||||
},
|
||||
invalidate: async (keyExtactor) => {
|
||||
const queryKey =
|
||||
typeof keyExtactor === "function" ? keyExtactor(key) : key
|
||||
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey,
|
||||
refetchType: "all",
|
||||
})
|
||||
options?.onInvalidate?.()
|
||||
},
|
||||
|
||||
refetch: async () => {
|
||||
await queryClient.refetchQueries({
|
||||
queryKey: key,
|
||||
})
|
||||
options?.onRefetch?.()
|
||||
return queryClient.getQueryData<TData>(key)
|
||||
},
|
||||
|
||||
setData: (updater) =>
|
||||
queryClient.setQueryData<TData>(key, (old) => {
|
||||
if (!old) return
|
||||
if (typeof updater !== "function") return old
|
||||
|
||||
return produce(old, updater)
|
||||
}),
|
||||
|
||||
setInfiniteData: (updater) =>
|
||||
queryDefine.setData<InfiniteData<TData>>(updater),
|
||||
getData: () => queryClient.getQueryData<TData>(key),
|
||||
|
||||
optimisticUpdate: async <Data = TData>(
|
||||
updater: (draft: Draft<Data>) => ValidRecipeReturnType<Draft<Data>>,
|
||||
) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: key,
|
||||
})
|
||||
const previousData = await queryClient.getQueryData<Data>(key)
|
||||
await queryClient.setQueryData<Data>(key, (old) => {
|
||||
if (!old) return
|
||||
if (typeof updater !== "function") return old
|
||||
|
||||
return produce(old, updater)
|
||||
})
|
||||
options?.onOptimisticUpdate?.()
|
||||
|
||||
return {
|
||||
previousData,
|
||||
restore: () => {
|
||||
queryClient.setQueryData<Data>(key, previousData)
|
||||
options?.onOptimisticUpdateRestore?.()
|
||||
},
|
||||
invalidate: () => {
|
||||
queryClient.invalidateQueries({ queryKey: key, refetchType: "all" })
|
||||
options?.onInvalidate?.()
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
optimisticInfiniteUpdate(updater) {
|
||||
return queryDefine.optimisticUpdate<InfiniteData<TData>>(updater)
|
||||
},
|
||||
}
|
||||
|
||||
return Object.freeze(queryDefine)
|
||||
}
|
||||
|
|
@ -44,8 +44,10 @@ export const parseHtml = async (content: string) => {
|
|||
content: toJsxRuntime(hastTree, {
|
||||
Fragment,
|
||||
ignoreInvalidStyle: true,
|
||||
jsx,
|
||||
jsxs,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
jsx: (type, props, key) => jsx(type as any, props, key),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
jsxs: (type, props, key) => jsxs(type as any, props, key),
|
||||
passNode: true,
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,7 @@ export type ActiveList = {
|
|||
preventNavigate?: boolean
|
||||
} | null
|
||||
|
||||
export type FeedResponse = {
|
||||
id: string
|
||||
url: string
|
||||
title?: string
|
||||
description?: string
|
||||
siteUrl?: string
|
||||
image?: string
|
||||
checkedAt: string
|
||||
nextCheckAt: string
|
||||
lastModifiedHeader?: string
|
||||
etagHeader?: string
|
||||
ttl: number
|
||||
errorAt?: string
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
export type FeedResponse = SubscriptionResponse[number]["feeds"]
|
||||
export type SubscriptionResponse = Array<
|
||||
Exclude<
|
||||
Extract<
|
||||
|
|
@ -35,31 +20,40 @@ export type SubscriptionResponse = Array<
|
|||
>[number] & { unread?: number }
|
||||
>
|
||||
|
||||
export type EntriesResponse = {
|
||||
author?: string
|
||||
category?: string[]
|
||||
changedAt: string
|
||||
content?: string
|
||||
description?: string
|
||||
enclosure?: {
|
||||
url: string
|
||||
type?: string
|
||||
length?: number
|
||||
title?: string
|
||||
}
|
||||
feedId: string
|
||||
guid: string
|
||||
id: string
|
||||
images?: string[]
|
||||
publishedAt: string
|
||||
readingTime?: number
|
||||
title?: string
|
||||
url?: string
|
||||
// export type EntriesResponse = {
|
||||
// author?: string
|
||||
// category?: string[]
|
||||
// changedAt: string
|
||||
// content?: string
|
||||
// description?: string
|
||||
// enclosure?: {
|
||||
// url: string
|
||||
// type?: string
|
||||
// length?: number
|
||||
// title?: string
|
||||
// }
|
||||
// feedId: string
|
||||
// guid: string
|
||||
// id: string
|
||||
// images?: string[]
|
||||
// publishedAt: string
|
||||
// readingTime?: number
|
||||
// title?: string
|
||||
// url?: string
|
||||
|
||||
feeds: FeedResponse
|
||||
collected: boolean
|
||||
read: boolean
|
||||
}[]
|
||||
// feeds: FeedResponse
|
||||
// collected: boolean
|
||||
// read: boolean
|
||||
// }[]
|
||||
export type EntriesResponse = Array<
|
||||
Exclude<
|
||||
Extract<
|
||||
InferResponseType<typeof apiClient.entries.$get>,
|
||||
{ code: 0 }
|
||||
>["data"],
|
||||
undefined
|
||||
>
|
||||
>
|
||||
|
||||
export type ListResponse<T> = {
|
||||
code: number
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ export function Component() {
|
|||
}, [navigate, preventNavigate])
|
||||
|
||||
if (status !== "authenticated") {
|
||||
return navigate("/login")
|
||||
navigate("/login")
|
||||
return null
|
||||
}
|
||||
|
||||
return <Outlet />
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ export function Component() {
|
|||
}, [])
|
||||
|
||||
if (status === "authenticated") {
|
||||
return navigate("/redirect?app=follow")
|
||||
navigate("/redirect?app=follow")
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,69 @@
|
|||
import {
|
||||
UnAuthorizedError,
|
||||
UnprocessableEntityError,
|
||||
} from "@renderer/biz/error"
|
||||
import { useBizInfiniteQuery } from "@renderer/hooks/useBizQuery"
|
||||
import { levels } from "@renderer/lib/constants"
|
||||
import type { DataResponse, EntriesResponse, ListResponse } from "@renderer/lib/types"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
|
||||
import { defineQuery } from "@renderer/lib/defineQuery"
|
||||
import type { EntriesResponse, ListResponse } from "@renderer/lib/types"
|
||||
import { apiClient, apiFetch } from "@renderer/queries/api-fetch"
|
||||
|
||||
export const entries = {
|
||||
entries: ({
|
||||
level,
|
||||
id,
|
||||
view,
|
||||
}: {
|
||||
level?: string
|
||||
id?: number | string
|
||||
view?: number
|
||||
}) =>
|
||||
defineQuery(
|
||||
["entries", level, id],
|
||||
async ({ pageParam }) => {
|
||||
const params: {
|
||||
category?: string
|
||||
view?: number
|
||||
feedId?: string
|
||||
feedIdList?: string[]
|
||||
} = {}
|
||||
if (level === levels.folder) {
|
||||
params.feedIdList = `${id}`.split(",")
|
||||
} else if (level === levels.feed) {
|
||||
params.feedId = `${id}`
|
||||
}
|
||||
return await apiFetch<ListResponse<EntriesResponse>>("/entries", {
|
||||
method: "POST",
|
||||
body: {
|
||||
offset: pageParam,
|
||||
view,
|
||||
...params,
|
||||
},
|
||||
})
|
||||
},
|
||||
{
|
||||
rootKey: ["entries"],
|
||||
},
|
||||
),
|
||||
byId: (id?: string | null) =>
|
||||
defineQuery(
|
||||
["entry", id],
|
||||
async () => {
|
||||
if (!id) {
|
||||
throw new UnprocessableEntityError("id is required")
|
||||
}
|
||||
const res = await apiClient.entries.$get({ query: { id } })
|
||||
const json = await res.json()
|
||||
if (json.code === 1) {
|
||||
throw new UnAuthorizedError()
|
||||
}
|
||||
return json.data
|
||||
},
|
||||
{
|
||||
rootKey: ["entries"],
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
export const useEntries = ({
|
||||
level,
|
||||
|
|
@ -12,48 +74,9 @@ export const useEntries = ({
|
|||
id?: number | string
|
||||
view?: number
|
||||
}) =>
|
||||
useInfiniteQuery({
|
||||
queryKey: ["entries", level, id],
|
||||
useBizInfiniteQuery(entries.entries({ level, id, view }), {
|
||||
enabled: level !== undefined && id !== undefined,
|
||||
queryFn: async ({ pageParam }) => {
|
||||
const params: {
|
||||
category?: string
|
||||
view?: number
|
||||
feedId?: string
|
||||
feedIdList?: string[]
|
||||
} = {}
|
||||
if (level === levels.folder) {
|
||||
params.feedIdList = (`${id}`).split(",")
|
||||
} else if (level === levels.feed) {
|
||||
params.feedId = `${id}`
|
||||
}
|
||||
return await apiFetch<ListResponse<EntriesResponse>>("/entries", {
|
||||
method: "POST",
|
||||
body: {
|
||||
offset: pageParam,
|
||||
view,
|
||||
...params,
|
||||
},
|
||||
})
|
||||
},
|
||||
getNextPageParam: (lastPage, _, lastPageParam) =>
|
||||
lastPageParam + (lastPage.data?.length || 0),
|
||||
(lastPageParam as number) + (lastPage.data?.length || 0),
|
||||
initialPageParam: 0,
|
||||
})
|
||||
|
||||
export const useEntry = ({ id }: { id?: string | null }) =>
|
||||
useQuery({
|
||||
queryKey: ["entry", id],
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const data = await apiFetch<DataResponse<EntriesResponse[number]>>(
|
||||
"/entries",
|
||||
{
|
||||
query: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
)
|
||||
return data.data
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import { entries } from "./entries"
|
||||
import { subscription } from "./subscriptions"
|
||||
|
||||
export const Queries = {
|
||||
subscription,
|
||||
entries,
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { UnAuthorizedError } from "@renderer/biz/error"
|
||||
import { defineQuery } from "@renderer/lib/defineQuery"
|
||||
import type { SubscriptionResponse } from "@renderer/lib/types"
|
||||
import { apiClient, apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { apiClient } from "@renderer/queries/api-fetch"
|
||||
import { parse } from "tldts"
|
||||
|
||||
export type Response = {
|
||||
|
|
@ -12,16 +13,16 @@ export type Response = {
|
|||
unread: number
|
||||
}
|
||||
|
||||
export const useSubscriptions = (view?: number) =>
|
||||
useQuery({
|
||||
queryKey: ["subscriptions", view],
|
||||
queryFn: async () => {
|
||||
const res = await (await apiClient.subscriptions.$get({ query: { view: String(view) } })).json()
|
||||
export const subscription = {
|
||||
byView: (view?: number) =>
|
||||
defineQuery(["subscriptions", view], async () => {
|
||||
const res = await (
|
||||
await apiClient.subscriptions.$get({ query: { view: String(view) } })
|
||||
).json()
|
||||
if (res.code === 1) {
|
||||
throw new Error(res.error)
|
||||
throw new UnAuthorizedError()
|
||||
}
|
||||
const subscriptions = res.data as SubscriptionResponse
|
||||
|
||||
const categories = {
|
||||
list: {},
|
||||
unread: 0,
|
||||
|
|
@ -56,7 +57,7 @@ export const useSubscriptions = (view?: number) =>
|
|||
const { domain } = parse(subscription.feeds.siteUrl)
|
||||
if (domain && domains[domain] > 1) {
|
||||
subscription.category =
|
||||
domain.slice(0, 1).toUpperCase() + domain.slice(1)
|
||||
domain.slice(0, 1).toUpperCase() + domain.slice(1)
|
||||
}
|
||||
}
|
||||
if (!subscription.category) {
|
||||
|
|
@ -88,22 +89,19 @@ export const useSubscriptions = (view?: number) =>
|
|||
list,
|
||||
unread: categories.unread,
|
||||
} as Response
|
||||
},
|
||||
})
|
||||
|
||||
export const useSubscriptionCategories = (view?: number) =>
|
||||
useQuery({
|
||||
queryKey: ["subscription-categories", view],
|
||||
queryFn: async () => {
|
||||
const { data: categories } = await apiFetch<{
|
||||
code: number
|
||||
data: string[]
|
||||
}>("/categories", {
|
||||
query: {
|
||||
view,
|
||||
},
|
||||
})
|
||||
|
||||
return categories || []
|
||||
},
|
||||
})
|
||||
}, {
|
||||
rootKey: ["subscriptions"],
|
||||
}),
|
||||
categories: (view?: number) =>
|
||||
defineQuery(["subscription-categories", view], async () => {
|
||||
const res = await (
|
||||
await apiClient.categories.$get({
|
||||
query: { view: String(view) },
|
||||
})
|
||||
).json()
|
||||
if (res.code === 1) {
|
||||
throw new UnAuthorizedError()
|
||||
}
|
||||
return res.data
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*?asset" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
Loading…
Reference in New Issue