aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/lists/RssLink.tsx
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-05-31 18:46:04 +0100
committerGitHub <noreply@github.com>2025-05-31 18:46:04 +0100
commit9695bba2e993b48ae333da622fa459dbaacb9349 (patch)
treec6bffbcdd73151671343f27012e82bea5a05ab6b /apps/web/components/dashboard/lists/RssLink.tsx
parentb218118b84291de4a9c1cd400dc58afab7054b78 (diff)
downloadkarakeep-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.tsx114
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>
+ );
+}