aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-09-21 17:49:47 +0000
committerMohamedBassem <me@mbassem.com>2024-09-21 17:51:58 +0000
commit9dd6f216ad18c09a28eaad67411d3a0e7f57a04f (patch)
tree708ac6b5f9fa58ec41e80ba6e775b742a78f41bc /apps/web/components
parentd62c9724b7f4cb728cd5b5496fdcc0eba8330772 (diff)
downloadkarakeep-9dd6f216ad18c09a28eaad67411d3a0e7f57a04f.tar.zst
feature(web): Add support for importing bookmarks from Pocket
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/dashboard/settings/ImportExport.tsx115
-rw-r--r--apps/web/components/ui/progress.tsx27
2 files changed, 106 insertions, 36 deletions
diff --git a/apps/web/components/dashboard/settings/ImportExport.tsx b/apps/web/components/dashboard/settings/ImportExport.tsx
index dcc3c8e8..4827df93 100644
--- a/apps/web/components/dashboard/settings/ImportExport.tsx
+++ b/apps/web/components/dashboard/settings/ImportExport.tsx
@@ -1,9 +1,15 @@
"use client";
+import { useState } from "react";
import { useRouter } from "next/navigation";
import FilePickerButton from "@/components/ui/file-picker-button";
+import { Progress } from "@/components/ui/progress";
import { toast } from "@/components/ui/use-toast";
-import { parseNetscapeBookmarkFile } from "@/lib/netscapeBookmarkParser";
+import {
+ ParsedBookmark,
+ parseNetscapeBookmarkFile,
+ parsePocketBookmarkFile,
+} from "@/lib/importBookmarkParser";
import { useMutation } from "@tanstack/react-query";
import { TRPCClientError } from "@trpc/client";
import { Upload } from "lucide-react";
@@ -22,6 +28,11 @@ import { BookmarkTypes } from "@hoarder/shared/types/bookmarks";
export function Import() {
const router = useRouter();
+ const [importProgress, setImportProgress] = useState<{
+ done: number;
+ total: number;
+ } | null>(null);
+
const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook();
const { mutateAsync: updateBookmark } = useUpdateBookmark();
const { mutateAsync: createList } = useCreateBookmarkList();
@@ -30,12 +41,7 @@ export function Import() {
const { mutateAsync: parseAndCreateBookmark } = useMutation({
mutationFn: async (toImport: {
- bookmark: {
- title: string;
- url: string | undefined;
- tags: string[];
- addDate?: number;
- };
+ bookmark: ParsedBookmark;
listId: string;
}) => {
const bookmark = toImport.bookmark;
@@ -57,6 +63,8 @@ export function Import() {
createdAt: bookmark.addDate
? new Date(bookmark.addDate * 1000)
: undefined,
+ }).catch(() => {
+ /* empty */
})
: undefined,
@@ -76,31 +84,40 @@ export function Import() {
}),
// Update tags
- updateTags({
- bookmarkId: created.id,
- attach: bookmark.tags.map((t) => ({ tagName: t })),
- detach: [],
- }),
+ bookmark.tags.length > 0
+ ? updateTags({
+ bookmarkId: created.id,
+ attach: bookmark.tags.map((t) => ({ tagName: t })),
+ detach: [],
+ })
+ : undefined,
]);
return created;
},
});
const { mutateAsync: runUploadBookmarkFile } = useMutation({
- mutationFn: async (file: File) => {
- return await parseNetscapeBookmarkFile(file);
+ mutationFn: async ({
+ file,
+ source,
+ }: {
+ file: File;
+ source: "html" | "pocket";
+ }) => {
+ if (source === "html") {
+ return await parseNetscapeBookmarkFile(file);
+ } else if (source === "pocket") {
+ return await parsePocketBookmarkFile(file);
+ } else {
+ throw new Error("Unknown source");
+ }
},
onSuccess: async (resp) => {
const importList = await createList({
name: `Imported Bookmarks`,
icon: "⬆️",
});
-
- let done = 0;
- const { id, update } = toast({
- description: `Processed 0 bookmarks of ${resp.length}`,
- variant: "default",
- });
+ setImportProgress({ done: 0, total: resp.length });
const successes = [];
const failed = [];
@@ -120,12 +137,10 @@ export function Import() {
} catch (e) {
failed.push(parsedBookmark);
}
-
- update({
- id,
- description: `Processed ${done + 1} bookmarks of ${resp.length}`,
- });
- done++;
+ setImportProgress((prev) => ({
+ done: (prev?.done ?? 0) + 1,
+ total: resp.length,
+ }));
}
if (successes.length > 0 || alreadyExisted.length > 0) {
@@ -153,16 +168,44 @@ export function Import() {
});
return (
- <div>
- <FilePickerButton
- accept=".html"
- multiple={false}
- className="flex items-center gap-2"
- onFileSelect={runUploadBookmarkFile}
- >
- <Upload />
- <p>Import Bookmarks from HTML file</p>
- </FilePickerButton>
+ <div className="flex flex-col gap-3">
+ <div className="flex flex-row gap-2">
+ <FilePickerButton
+ accept=".html"
+ multiple={false}
+ className="flex items-center gap-2"
+ onFileSelect={(file) =>
+ runUploadBookmarkFile({ file, source: "html" })
+ }
+ >
+ <Upload />
+ <p>Import Bookmarks from HTML file</p>
+ </FilePickerButton>
+
+ <FilePickerButton
+ accept=".html"
+ multiple={false}
+ className="flex items-center gap-2"
+ onFileSelect={(file) =>
+ runUploadBookmarkFile({ file, source: "pocket" })
+ }
+ >
+ <Upload />
+ <p>Import Bookmarks from Pocket export</p>
+ </FilePickerButton>
+ </div>
+ {importProgress && (
+ <div className="flex flex-col gap-2">
+ <p className="shrink-0 text-sm">
+ Processed {importProgress.done} of {importProgress.total} bookmarks
+ </p>
+ <div className="w-full">
+ <Progress
+ value={(importProgress.done * 100) / importProgress.total}
+ />
+ </div>
+ </div>
+ )}
</div>
);
}
diff --git a/apps/web/components/ui/progress.tsx b/apps/web/components/ui/progress.tsx
new file mode 100644
index 00000000..d777b615
--- /dev/null
+++ b/apps/web/components/ui/progress.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import * as React from "react";
+import { cn } from "@/lib/utils";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+
+const Progress = React.forwardRef<
+ React.ElementRef<typeof ProgressPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
+>(({ className, value, ...props }, ref) => (
+ <ProgressPrimitive.Root
+ ref={ref}
+ className={cn(
+ "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
+ className,
+ )}
+ {...props}
+ >
+ <ProgressPrimitive.Indicator
+ className="h-full w-full flex-1 bg-primary transition-all"
+ style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
+ />
+ </ProgressPrimitive.Root>
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };