diff options
| author | MohamedBassem <me@mbassem.com> | 2024-09-21 16:56:42 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-09-21 17:00:06 +0000 |
| commit | d62c9724b7f4cb728cd5b5496fdcc0eba8330772 (patch) | |
| tree | b0096e53cefdf1e2df2251c3845377697b5b9600 /apps/web/components/dashboard/settings | |
| parent | 52024ab52724b45c08f437f9f10805adefe2bf0e (diff) | |
| download | karakeep-d62c9724b7f4cb728cd5b5496fdcc0eba8330772.tar.zst | |
feature(web): Preserve title, tags and createdAt when importing a netscape html. Fixes #401
Diffstat (limited to 'apps/web/components/dashboard/settings')
| -rw-r--r-- | apps/web/components/dashboard/settings/ImportExport.tsx | 133 |
1 files changed, 103 insertions, 30 deletions
diff --git a/apps/web/components/dashboard/settings/ImportExport.tsx b/apps/web/components/dashboard/settings/ImportExport.tsx index 75de14ac..dcc3c8e8 100644 --- a/apps/web/components/dashboard/settings/ImportExport.tsx +++ b/apps/web/components/dashboard/settings/ImportExport.tsx @@ -1,14 +1,18 @@ "use client"; -import assert from "assert"; import { useRouter } from "next/navigation"; import FilePickerButton from "@/components/ui/file-picker-button"; import { toast } from "@/components/ui/use-toast"; import { parseNetscapeBookmarkFile } from "@/lib/netscapeBookmarkParser"; import { useMutation } from "@tanstack/react-query"; +import { TRPCClientError } from "@trpc/client"; import { Upload } from "lucide-react"; -import { useCreateBookmarkWithPostHook } from "@hoarder/shared-react/hooks/bookmarks"; +import { + useCreateBookmarkWithPostHook, + useUpdateBookmark, + useUpdateBookmarkTags, +} from "@hoarder/shared-react/hooks/bookmarks"; import { useAddBookmarkToList, useCreateBookmarkList, @@ -17,29 +21,112 @@ import { BookmarkTypes } from "@hoarder/shared/types/bookmarks"; export function Import() { const router = useRouter(); - const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook(); + const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook(); + const { mutateAsync: updateBookmark } = useUpdateBookmark(); const { mutateAsync: createList } = useCreateBookmarkList(); const { mutateAsync: addToList } = useAddBookmarkToList(); + const { mutateAsync: updateTags } = useUpdateBookmarkTags(); + + const { mutateAsync: parseAndCreateBookmark } = useMutation({ + mutationFn: async (toImport: { + bookmark: { + title: string; + url: string | undefined; + tags: string[]; + addDate?: number; + }; + listId: string; + }) => { + const bookmark = toImport.bookmark; + if (bookmark.url === undefined) { + throw new Error("URL is undefined"); + } + const url = new URL(bookmark.url); + const created = await createBookmark({ + type: BookmarkTypes.LINK, + url: url.toString(), + }); + + await Promise.all([ + // Update title and createdAt if they're set + bookmark.title.length > 0 || bookmark.addDate + ? updateBookmark({ + bookmarkId: created.id, + title: bookmark.title, + createdAt: bookmark.addDate + ? new Date(bookmark.addDate * 1000) + : undefined, + }) + : undefined, + + // Add to import list + addToList({ + bookmarkId: created.id, + listId: toImport.listId, + }).catch((e) => { + if ( + e instanceof TRPCClientError && + e.message.includes("already in the list") + ) { + /* empty */ + } else { + throw e; + } + }), + + // Update tags + updateTags({ + bookmarkId: created.id, + attach: bookmark.tags.map((t) => ({ tagName: t })), + detach: [], + }), + ]); + return created; + }, + }); const { mutateAsync: runUploadBookmarkFile } = useMutation({ mutationFn: async (file: File) => { return await parseNetscapeBookmarkFile(file); }, onSuccess: async (resp) => { - const results = await Promise.allSettled( - resp.map((url) => - createBookmark({ type: BookmarkTypes.LINK, url: url.toString() }), - ), - ); - - const failed = results.filter((r) => r.status == "rejected"); - const successes = results.filter( - (r) => r.status == "fulfilled" && !r.value.alreadyExists, - ); - const alreadyExisted = results.filter( - (r) => r.status == "fulfilled" && r.value.alreadyExists, - ); + const importList = await createList({ + name: `Imported Bookmarks`, + icon: "⬆️", + }); + + let done = 0; + const { id, update } = toast({ + description: `Processed 0 bookmarks of ${resp.length}`, + variant: "default", + }); + + const successes = []; + const failed = []; + const alreadyExisted = []; + // Do the imports one by one + for (const parsedBookmark of resp) { + try { + const result = await parseAndCreateBookmark({ + bookmark: parsedBookmark, + listId: importList.id, + }); + if (result.alreadyExists) { + alreadyExisted.push(parsedBookmark); + } else { + successes.push(parsedBookmark); + } + } catch (e) { + failed.push(parsedBookmark); + } + + update({ + id, + description: `Processed ${done + 1} bookmarks of ${resp.length}`, + }); + done++; + } if (successes.length > 0 || alreadyExisted.length > 0) { toast({ @@ -55,20 +142,6 @@ export function Import() { }); } - const importList = await createList({ - name: `Imported Bookmarks`, - icon: "⬆️", - }); - - if (successes.length > 0) { - await Promise.allSettled( - successes.map((r) => { - assert(r.status == "fulfilled"); - addToList({ bookmarkId: r.value.id, listId: importList.id }); - }), - ); - } - router.push(`/dashboard/lists/${importList.id}`); }, onError: (error) => { |
