225 lines
7.0 KiB
TypeScript
225 lines
7.0 KiB
TypeScript
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { Button } from "@renderer/components/ui/button"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardFooter,
|
|
CardHeader,
|
|
} from "@renderer/components/ui/card"
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@renderer/components/ui/form"
|
|
import { Image } from "@renderer/components/ui/image"
|
|
import { Input } from "@renderer/components/ui/input"
|
|
import type { DiscoverResponse } from "@renderer/lib/types"
|
|
import { apiFetch } from "@renderer/queries/api-fetch"
|
|
import { DEEPLINK_SCHEME } from "@shared/constants"
|
|
import { openElectronWindow } from "@shared/electron"
|
|
import { useMutation } from "@tanstack/react-query"
|
|
import { useEffect } from "react"
|
|
import { useForm } from "react-hook-form"
|
|
import { z } from "zod"
|
|
|
|
import { FollowSummary } from "../feed-summary"
|
|
|
|
const formSchema = z.object({
|
|
keyword: z.string().min(1),
|
|
})
|
|
|
|
const info: Record<
|
|
string,
|
|
{
|
|
label: string
|
|
prefix?: string
|
|
}
|
|
> = {
|
|
general: {
|
|
label: "Any URL or Keyword",
|
|
},
|
|
rss: {
|
|
label: "RSS URL",
|
|
},
|
|
rsshub: {
|
|
label: "RSSHub Route",
|
|
prefix: "rsshub://",
|
|
},
|
|
user: {
|
|
label: "User Handle",
|
|
prefix: DEEPLINK_SCHEME,
|
|
},
|
|
}
|
|
|
|
export function DiscoverForm({ type }: { type: string }) {
|
|
const { prefix } = info[type]
|
|
const form = useForm<z.infer<typeof formSchema>>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
keyword: prefix || "",
|
|
},
|
|
})
|
|
const mutation = useMutation({
|
|
mutationFn: async (keyword: string) => {
|
|
const { data } = await apiFetch<{
|
|
data: DiscoverResponse
|
|
}>("/discover", {
|
|
method: "POST",
|
|
body: {
|
|
keyword,
|
|
type: type === "rss" ? "rss" : "auto",
|
|
},
|
|
})
|
|
return data
|
|
},
|
|
})
|
|
|
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
|
mutation.mutate(values.keyword)
|
|
}
|
|
|
|
const keyword = form.watch("keyword")
|
|
useEffect(() => {
|
|
if (prefix) {
|
|
if (!keyword.startsWith(prefix)) {
|
|
form.setValue("keyword", prefix)
|
|
} else if (keyword.startsWith(`${prefix}${prefix}`)) {
|
|
form.setValue("keyword", keyword.slice(prefix.length))
|
|
}
|
|
}
|
|
}, [keyword])
|
|
|
|
return (
|
|
<>
|
|
<Form {...form}>
|
|
<form
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
className="w-[512px] space-y-8"
|
|
>
|
|
<FormField
|
|
control={form.control}
|
|
name="keyword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{info[type]?.label}</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<Button type="submit" isLoading={mutation.isPending}>
|
|
Search
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
{mutation.isSuccess && (
|
|
<div className="mt-8 max-w-lg">
|
|
<div className="mb-4 text-zinc-500">
|
|
Found
|
|
{" "}
|
|
{mutation.data?.length || 0}
|
|
{" "}
|
|
feed
|
|
{mutation.data?.length > 1 && "s"}
|
|
</div>
|
|
<div className="space-y-6 text-sm">
|
|
{mutation.data?.map((item) => (
|
|
<Card key={item.feed.url || item.docs} className="select-text">
|
|
<CardHeader>
|
|
<FollowSummary
|
|
className="max-w-[462px]"
|
|
feed={item.feed}
|
|
docs={item.docs}
|
|
/>
|
|
</CardHeader>
|
|
{item.docs ? (
|
|
<CardFooter>
|
|
<a href={item.docs} target="_blank" rel="noreferrer">
|
|
<Button>View Docs</Button>
|
|
</a>
|
|
</CardFooter>
|
|
) : (
|
|
<>
|
|
<CardContent>
|
|
{!!item.entries?.length && (
|
|
<div className="grid grid-cols-4 gap-4">
|
|
{item.entries
|
|
.filter((e) => !!e)
|
|
.map((entry) => {
|
|
const assertEntry = entry
|
|
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>
|
|
<CardFooter>
|
|
{item.isSubscribed ? (
|
|
<Button variant="outline" disabled>
|
|
Followed
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={
|
|
() =>
|
|
openElectronWindow(
|
|
`${DEEPLINK_SCHEME}add${
|
|
item.feed.id ?
|
|
`?id=${item.feed.id}` :
|
|
`?url=${item.feed.url}`
|
|
}`,
|
|
{
|
|
resizeable: false,
|
|
height: 550,
|
|
},
|
|
)
|
|
}
|
|
>
|
|
Follow
|
|
</Button>
|
|
)}
|
|
<div className="ml-6 text-zinc-500">
|
|
<span className="font-medium text-zinc-800">
|
|
{item.subscriptionCount}
|
|
</span>
|
|
{" "}
|
|
Followers
|
|
</div>
|
|
</CardFooter>
|
|
</>
|
|
)}
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|