From 2da67ccd6820959daad2a927b386a11cdee744e9 Mon Sep 17 00:00:00 2001 From: kamtschatka Date: Fri, 17 May 2024 12:13:54 +0200 Subject: [PATCH] [Feature request] Netscape HTML format import/export #96 added the possibility to add exported bookmarks via the webUI for ease of use --- .../components/dashboard/UploadDropzone.tsx | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/apps/web/components/dashboard/UploadDropzone.tsx b/apps/web/components/dashboard/UploadDropzone.tsx index be30a77f..240cec4c 100644 --- a/apps/web/components/dashboard/UploadDropzone.tsx +++ b/apps/web/components/dashboard/UploadDropzone.tsx @@ -1,9 +1,11 @@ "use client"; import React, { useState } from "react"; +import Link from "next/link"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { TRPCClientError } from "@trpc/client"; +import { ExternalLink } from "lucide-react"; import DropZone from "react-dropzone"; import { useCreateBookmarkWithPostHook } from "@hoarder/shared-react/hooks/bookmarks"; @@ -60,10 +62,12 @@ function useUploadAssets({ onFileUpload, onFileError, onAllUploaded, + addBookmark, }: { onFileUpload: () => void; onFileError: (name: string, e: Error) => void; onAllUploaded: () => void; + addBookmark: (bookmark: { type: "link"; url: string }) => void; }) { const runUpload = useUploadAsset({ onComplete: onFileUpload }); @@ -73,7 +77,11 @@ function useUploadAssets({ } for (const file of files) { try { - await runUpload(file); + if (file.type === "text/html") { + await handleBookmarkFile(file, addBookmark); + } else { + await runUpload(file); + } } catch (e) { if (e instanceof TRPCClientError || e instanceof Error) { onFileError(file.name, e); @@ -84,11 +92,71 @@ function useUploadAssets({ }; } +async function handleBookmarkFile( + file: File, + addBookmark: (bookmark: { type: "link"; url: string }) => void, +): Promise { + const textContent = Buffer.from(await file.arrayBuffer()).toString(); + if (!textContent.startsWith("")) { + toast({ + description: + "The uploaded html file does not appear to be a bookmark file", + variant: "destructive", + }); + throw Error("The uploaded html file does not seem to be a bookmark file"); + } + + const extractedUrls = extractUrls(textContent); + + for (const extractedUrl of extractedUrls) { + const url = new URL(extractedUrl); + addBookmark({ type: "link", url: url.toString() }); + } +} + +function extractUrls(html: string): string[] { + const regex = /]*?\s+)?href="(http[^"]*)"/gi; + let match; + const urls = []; + + while ((match = regex.exec(html)) !== null) { + urls.push(match[1]); + } + + return urls; +} + export default function UploadDropzone({ children, }: { children: React.ReactNode; }) { + const { mutate: addBookmark } = useCreateBookmarkWithPostHook({ + onSuccess: (resp) => { + if (resp.alreadyExists) { + toast({ + description: ( +
+ Bookmark already exists. + + Open + +
+ ), + variant: "default", + }); + } else { + toast({ description: "Bookmark uploaded" }); + } + }, + onError: () => { + toast({ description: "Something went wrong", variant: "destructive" }); + }, + }); + const [numUploading, setNumUploading] = useState(0); const [numUploaded, setNumUploaded] = useState(0); const uploadAssets = useUploadAssets({ @@ -103,6 +171,7 @@ export default function UploadDropzone({ setNumUploaded(0); return; }, + addBookmark: addBookmark, }); const [isDragging, setDragging] = useState(false); @@ -137,7 +206,7 @@ export default function UploadDropzone({ ) : (

- Drop Your Image + Drop Your Image / Bookmark file

)}