Skip to content

Commit

Permalink
[Feature request] Netscape HTML format import/export hoarder-app#96
Browse files Browse the repository at this point in the history
added the possibility to add exported bookmarks via the webUI for ease of use
  • Loading branch information
kamtschatka committed May 17, 2024
1 parent cbc8dde commit 2da67cc
Showing 1 changed file with 71 additions and 2 deletions.
73 changes: 71 additions & 2 deletions apps/web/components/dashboard/UploadDropzone.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 });

Expand All @@ -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);
Expand All @@ -84,11 +92,71 @@ function useUploadAssets({
};
}

async function handleBookmarkFile(
file: File,
addBookmark: (bookmark: { type: "link"; url: string }) => void,
): Promise<void> {
const textContent = Buffer.from(await file.arrayBuffer()).toString();
if (!textContent.startsWith("<!DOCTYPE NETSCAPE-Bookmark-file-1>")) {
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 = /<a\s+(?:[^>]*?\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: (
<div className="flex items-center gap-1">
Bookmark already exists.
<Link
className="flex underline-offset-4 hover:underline"
href={`/dashboard/preview/${resp.id}`}
>
Open <ExternalLink className="ml-1 size-4" />
</Link>
</div>
),
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({
Expand All @@ -103,6 +171,7 @@ export default function UploadDropzone({
setNumUploaded(0);
return;
},
addBookmark: addBookmark,
});

const [isDragging, setDragging] = useState(false);
Expand Down Expand Up @@ -137,7 +206,7 @@ export default function UploadDropzone({
</div>
) : (
<p className="text-2xl font-bold text-gray-700">
Drop Your Image
Drop Your Image / Bookmark file
</p>
)}
</div>
Expand Down

0 comments on commit 2da67cc

Please sign in to comment.