diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-05-31 18:46:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-31 18:46:04 +0100 |
| commit | 9695bba2e993b48ae333da622fa459dbaacb9349 (patch) | |
| tree | c6bffbcdd73151671343f27012e82bea5a05ab6b /apps/web/components/dashboard/lists/RssLink.tsx | |
| parent | b218118b84291de4a9c1cd400dc58afab7054b78 (diff) | |
| download | karakeep-9695bba2e993b48ae333da622fa459dbaacb9349.tar.zst | |
feat: Generate RSS feeds from lists (#1507)
* refactor: Move bookmark utils from shared-react to shared
* Expose RSS feeds for lists
* Add e2e tests
* Slightly improve the look of the share dialog
* allow specifying a limit in the rss endpoint
Diffstat (limited to 'apps/web/components/dashboard/lists/RssLink.tsx')
| -rw-r--r-- | apps/web/components/dashboard/lists/RssLink.tsx | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/apps/web/components/dashboard/lists/RssLink.tsx b/apps/web/components/dashboard/lists/RssLink.tsx new file mode 100644 index 00000000..152a3fe4 --- /dev/null +++ b/apps/web/components/dashboard/lists/RssLink.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Button, buttonVariants } from "@/components/ui/button"; +import CopyBtn from "@/components/ui/copy-button"; +import { Input } from "@/components/ui/input"; +import { useClientConfig } from "@/lib/clientConfig"; +import { api } from "@/lib/trpc"; +import { cn } from "@/lib/utils"; +import { Loader2, RotateCcw, Rss, Trash2 } from "lucide-react"; +import { useTranslation } from "react-i18next"; + +export default function RssLink({ listId }: { listId: string }) { + const { t } = useTranslation(); + const clientConfig = useClientConfig(); + const apiUtils = api.useUtils(); + + const { mutate: regenRssToken, isPending: isRegenPending } = + api.lists.regenRssToken.useMutation({ + onSuccess: () => { + apiUtils.lists.getRssToken.invalidate({ listId }); + }, + }); + const { mutate: clearRssToken, isPending: isClearPending } = + api.lists.clearRssToken.useMutation({ + onSuccess: () => { + apiUtils.lists.getRssToken.invalidate({ listId }); + }, + }); + const { data: rssToken, isLoading: isTokenLoading } = + api.lists.getRssToken.useQuery({ listId }); + + const rssUrl = useMemo(() => { + if (!rssToken || !rssToken.token) { + return null; + } + return `${clientConfig.publicApiUrl}/v1/rss/lists/${listId}?token=${rssToken.token}`; + }, [rssToken]); + + const isLoading = isRegenPending || isClearPending || isTokenLoading; + + return ( + <div className="flex items-center gap-3 rounded-lg border bg-white p-3"> + <Badge variant="outline" className="text-xs"> + RSS + </Badge> + {!rssUrl ? ( + <div className="flex items-center gap-2"> + <Button + size="sm" + variant="outline" + onClick={() => regenRssToken({ listId })} + disabled={isLoading} + > + {isLoading ? ( + <Loader2 className="h-3 w-3 animate-spin" /> + ) : ( + <span className="flex items-center"> + <Rss className="mr-2 h-4 w-4 flex-shrink-0 text-orange-500" /> + {t("lists.generate_rss_feed")} + </span> + )} + </Button> + </div> + ) : ( + <div className="flex min-w-0 flex-1 items-center gap-2"> + <Input + value={rssUrl} + readOnly + className="h-8 min-w-0 flex-1 font-mono text-xs" + /> + <div className="flex flex-shrink-0 gap-1"> + <CopyBtn + getStringToCopy={() => { + return rssUrl; + }} + className={cn( + buttonVariants({ variant: "outline", size: "sm" }), + "h-8 w-8 p-0", + )} + /> + <Button + variant="outline" + size="sm" + onClick={() => regenRssToken({ listId })} + disabled={isLoading} + className="h-8 w-8 p-0" + > + {isLoading ? ( + <Loader2 className="h-3 w-3 animate-spin" /> + ) : ( + <RotateCcw className="h-3 w-3" /> + )} + </Button> + <Button + variant="outline" + size="sm" + onClick={() => clearRssToken({ listId })} + disabled={isLoading} + className="h-8 w-8 p-0 text-destructive hover:text-destructive" + > + {isLoading ? ( + <Loader2 className="h-3 w-3 animate-spin" /> + ) : ( + <Trash2 className="h-3 w-3" /> + )} + </Button> + </div> + </div> + )} + </div> + ); +} |
