From 033e8a2d26bb0ecaa8301609960d35d3467a88f4 Mon Sep 17 00:00:00 2001 From: kamtschatka Date: Sat, 25 May 2024 23:20:17 +0200 Subject: feature: Allow import Netscape HTML format (#163) * [Feature request] Netscape HTML format import/export #96 added the possibility to add exported bookmarks via the webUI for ease of use * [Feature request] Netscape HTML format import/export #96 updated the documentation * Extract the parser into its own file and reuse the existing bookmark upload logic --------- Co-authored-by: kamtschatka Co-authored-by: MohamedBassem --- apps/web/components/dashboard/UploadDropzone.tsx | 55 ++++++++++++++++++---- .../components/dashboard/bookmarks/EditorCard.tsx | 15 +----- 2 files changed, 48 insertions(+), 22 deletions(-) (limited to 'apps/web/components/dashboard') diff --git a/apps/web/components/dashboard/UploadDropzone.tsx b/apps/web/components/dashboard/UploadDropzone.tsx index be30a77f..60398ede 100644 --- a/apps/web/components/dashboard/UploadDropzone.tsx +++ b/apps/web/components/dashboard/UploadDropzone.tsx @@ -1,6 +1,7 @@ "use client"; -import React, { useState } from "react"; +import React, { useCallback, useState } from "react"; +import { parseNetscapeBookmarkFile } from "@/lib/netscapeBookmarkParser"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { TRPCClientError } from "@trpc/client"; @@ -14,19 +15,26 @@ import { import LoadingSpinner from "../ui/spinner"; import { toast } from "../ui/use-toast"; +import BookmarkAlreadyExistsToast from "../utils/BookmarkAlreadyExistsToast"; -function useUploadAsset({ onComplete }: { onComplete: () => void }) { +function useUploadAsset() { const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook({ - onSuccess: () => { - toast({ description: "Bookmark uploaded" }); - onComplete(); + onSuccess: (resp) => { + if (resp.alreadyExists) { + toast({ + description: , + variant: "default", + }); + } else { + toast({ description: "Bookmark uploaded" }); + } }, onError: () => { toast({ description: "Something went wrong", variant: "destructive" }); }, }); - const { mutateAsync: runUpload } = useMutation({ + const { mutateAsync: runUploadAsset } = useMutation({ mutationFn: async (file: File) => { const formData = new FormData(); formData.append("file", file); @@ -53,7 +61,35 @@ function useUploadAsset({ onComplete }: { onComplete: () => void }) { }, }); - return runUpload; + const { mutateAsync: runUploadBookmarkFile } = useMutation({ + mutationFn: async (file: File) => { + return await parseNetscapeBookmarkFile(file); + }, + onSuccess: async (resp) => { + return Promise.all( + resp.map((url) => + createBookmark({ type: "link", url: url.toString() }), + ), + ); + }, + onError: (error) => { + toast({ + description: error.message, + variant: "destructive", + }); + }, + }); + + return useCallback( + (file: File) => { + if (file.type === "text/html") { + return runUploadBookmarkFile(file); + } else { + return runUploadAsset(file); + } + }, + [runUploadAsset, runUploadBookmarkFile], + ); } function useUploadAssets({ @@ -65,7 +101,7 @@ function useUploadAssets({ onFileError: (name: string, e: Error) => void; onAllUploaded: () => void; }) { - const runUpload = useUploadAsset({ onComplete: onFileUpload }); + const runUpload = useUploadAsset(); return async (files: File[]) => { if (files.length == 0) { @@ -74,6 +110,7 @@ function useUploadAssets({ for (const file of files) { try { await runUpload(file); + onFileUpload(); } catch (e) { if (e instanceof TRPCClientError || e instanceof Error) { onFileError(file.name, e); @@ -137,7 +174,7 @@ export default function UploadDropzone({ ) : (

- Drop Your Image + Drop Your Image / Bookmark file

)} diff --git a/apps/web/components/dashboard/bookmarks/EditorCard.tsx b/apps/web/components/dashboard/bookmarks/EditorCard.tsx index 8425f669..44d68378 100644 --- a/apps/web/components/dashboard/bookmarks/EditorCard.tsx +++ b/apps/web/components/dashboard/bookmarks/EditorCard.tsx @@ -1,6 +1,5 @@ import type { SubmitErrorHandler, SubmitHandler } from "react-hook-form"; import React, { useEffect, useImperativeHandle, useRef } from "react"; -import Link from "next/link"; import { ActionButton } from "@/components/ui/action-button"; import { Form, FormControl, FormItem } from "@/components/ui/form"; import InfoTooltip from "@/components/ui/info-tooltip"; @@ -8,11 +7,11 @@ import MultipleChoiceDialog from "@/components/ui/multiple-choice-dialog"; import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/use-toast"; +import BookmarkAlreadyExistsToast from "@/components/utils/BookmarkAlreadyExistsToast"; import { useClientConfig } from "@/lib/clientConfig"; import { useBookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ExternalLink } from "lucide-react"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -64,17 +63,7 @@ export default function EditorCard({ className }: { className?: string }) { onSuccess: (resp) => { if (resp.alreadyExists) { toast({ - description: ( -
- Bookmark already exists. - - Open - -
- ), + description: , variant: "default", }); } -- cgit v1.2.3-70-g09d2