aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/bookmarks
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-04-07 18:30:00 +0100
committerMohamedBassem <me@mbassem.com>2024-04-07 19:00:00 +0100
commit79d61be7e15dc5d23fb687a5f71e0097088a99ac (patch)
treeda72f19cdb74ef4ed2a75bcfddd13bdfb874f205 /apps/web/components/dashboard/bookmarks
parent44918316007ed3153dc802a4b11db3ea09024a8b (diff)
downloadkarakeep-79d61be7e15dc5d23fb687a5f71e0097088a99ac.tar.zst
feature: Extract hook logic into separate package and add a new action bar in bookmark preview
Diffstat (limited to 'apps/web/components/dashboard/bookmarks')
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx11
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx52
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx216
-rw-r--r--apps/web/components/dashboard/bookmarks/NoteEditor.tsx48
-rw-r--r--apps/web/components/dashboard/bookmarks/icons.tsx29
5 files changed, 53 insertions, 303 deletions
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx b/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx
index d2beb8d4..420fed84 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx
@@ -1,11 +1,12 @@
+import BookmarkPreview from "@/components/dashboard/preview/BookmarkPreview";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
-import { Maximize2, Star } from "lucide-react";
+import { Maximize2 } from "lucide-react";
import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import BookmarkOptions from "./BookmarkOptions";
-import BookmarkPreview from "./BookmarkPreview";
+import { FavouritedActionIcon } from "./icons";
export default function BookmarkActionBar({
bookmark,
@@ -15,11 +16,7 @@ export default function BookmarkActionBar({
return (
<div className="flex text-gray-500">
{bookmark.favourited && (
- <Star
- className="m-1 size-8 rounded p-1"
- color="#ebb434"
- fill="#ebb434"
- />
+ <FavouritedActionIcon className="m-1 size-8 rounded p-1" favourited />
)}
<Dialog>
<DialogTrigger asChild>
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
index e95ec9a2..e3cfc796 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
@@ -11,24 +11,28 @@ import {
import { useToast } from "@/components/ui/use-toast";
import { useClientConfig } from "@/lib/clientConfig";
import { BookmarkListContext } from "@/lib/hooks/list-context";
-import { api } from "@/lib/trpc";
import {
- Archive,
Link,
List,
ListX,
MoreHorizontal,
Pencil,
RotateCw,
- Star,
Tags,
Trash2,
} from "lucide-react";
import type { ZBookmark, ZBookmarkedLink } from "@hoarder/trpc/types/bookmarks";
+import {
+ useDeleteBookmark,
+ useRecrawlBookmark,
+ useUpdateBookmark,
+} from "@hoarder/shared-react/hooks//bookmarks";
+import { useRemoveBookmarkFromList } from "@hoarder/shared-react/hooks//lists";
import { useAddToListModal } from "./AddToListModal";
import { BookmarkedTextEditor } from "./BookmarkedTextEditor";
+import { ArchivedActionIcon, FavouritedActionIcon } from "./icons";
import { useTagModel } from "./TagModal";
export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
@@ -46,15 +50,6 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
const { listId } = useContext(BookmarkListContext);
- const invalidateAllBookmarksCache =
- api.useUtils().bookmarks.getBookmarks.invalidate;
-
- const invalidateBookmarkCache =
- api.useUtils().bookmarks.getBookmark.invalidate;
-
- const invalidateSearchCache =
- api.useUtils().bookmarks.searchBookmarks.invalidate;
-
const onError = () => {
toast({
variant: "destructive",
@@ -62,48 +57,35 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
description: "There was a problem with your request.",
});
};
- const deleteBookmarkMutator = api.bookmarks.deleteBookmark.useMutation({
+ const deleteBookmarkMutator = useDeleteBookmark({
onSuccess: () => {
toast({
description: "The bookmark has been deleted!",
});
},
onError,
- onSettled: () => {
- invalidateAllBookmarksCache();
- invalidateSearchCache();
- },
});
- const updateBookmarkMutator = api.bookmarks.updateBookmark.useMutation({
+ const updateBookmarkMutator = useUpdateBookmark({
onSuccess: () => {
toast({
description: "The bookmark has been updated!",
});
},
onError,
- onSettled: () => {
- invalidateBookmarkCache({ bookmarkId: bookmark.id });
- invalidateAllBookmarksCache();
- invalidateSearchCache();
- },
});
- const crawlBookmarkMutator = api.bookmarks.recrawlBookmark.useMutation({
+ const crawlBookmarkMutator = useRecrawlBookmark({
onSuccess: () => {
toast({
description: "Re-fetch has been enqueued!",
});
},
onError,
- onSettled: () => {
- invalidateBookmarkCache({ bookmarkId: bookmark.id });
- },
});
- const removeFromListMutator = api.lists.removeFromList.useMutation({
- onSuccess: (_resp, req) => {
- invalidateAllBookmarksCache({ listId: req.listId });
+ const removeFromListMutator = useRemoveBookmarkFromList({
+ onSuccess: () => {
toast({
description: "The bookmark has been deleted from the list",
});
@@ -145,7 +127,10 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
})
}
>
- <Star className="mr-2 size-4" />
+ <FavouritedActionIcon
+ className="mr-2 size-4"
+ favourited={bookmark.favourited}
+ />
<span>{bookmark.favourited ? "Un-favourite" : "Favourite"}</span>
</DropdownMenuItem>
<DropdownMenuItem
@@ -157,7 +142,10 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
})
}
>
- <Archive className="mr-2 size-4" />
+ <ArchivedActionIcon
+ className="mr-2 size-4"
+ archived={bookmark.archived}
+ />
<span>{bookmark.archived ? "Un-archive" : "Archive"}</span>
</DropdownMenuItem>
{bookmark.content.type === "link" && (
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx b/apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx
deleted file mode 100644
index 102e0788..00000000
--- a/apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import Link from "next/link";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { Separator } from "@/components/ui/separator";
-import { Skeleton } from "@/components/ui/skeleton";
-import {
- Tooltip,
- TooltipContent,
- TooltipPortal,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import {
- isBookmarkStillCrawling,
- isBookmarkStillLoading,
-} from "@/lib/bookmarkUtils";
-import { api } from "@/lib/trpc";
-import dayjs from "dayjs";
-import relativeTime from "dayjs/plugin/relativeTime";
-import { CalendarDays, ExternalLink } from "lucide-react";
-import Markdown from "react-markdown";
-
-import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
-
-import { NoteEditor } from "./NoteEditor";
-import { TagsEditor } from "./TagsEditor";
-
-dayjs.extend(relativeTime);
-
-function ContentLoading() {
- return (
- <div className="flex w-full flex-col gap-2">
- <Skeleton className="h-4" />
- <Skeleton className="h-4" />
- <Skeleton className="h-4" />
- </div>
- );
-}
-
-function CreationTime({ createdAt }: { createdAt: Date }) {
- return (
- <TooltipProvider>
- <Tooltip delayDuration={0}>
- <TooltipTrigger asChild>
- <span className="flex w-fit gap-2">
- <CalendarDays /> {dayjs(createdAt).fromNow()}
- </span>
- </TooltipTrigger>
- <TooltipPortal>
- <TooltipContent>{createdAt.toLocaleString()}</TooltipContent>
- </TooltipPortal>
- </Tooltip>
- </TooltipProvider>
- );
-}
-
-function LinkHeader({ bookmark }: { bookmark: ZBookmark }) {
- if (bookmark.content.type !== "link") {
- throw new Error("Unexpected content type");
- }
-
- const title = bookmark.content.title ?? bookmark.content.url;
-
- return (
- <div className="flex w-full flex-col items-center justify-center space-y-3">
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <p className="line-clamp-2 text-center text-lg">{title}</p>
- </TooltipTrigger>
- <TooltipPortal>
- <TooltipContent side="bottom" className="w-96">
- {title}
- </TooltipContent>
- </TooltipPortal>
- </Tooltip>
- </TooltipProvider>
- <Link
- href={bookmark.content.url}
- className="mx-auto flex gap-2 text-gray-400"
- >
- <span className="my-auto">View Original</span>
- <ExternalLink />
- </Link>
- <Separator />
- </div>
- );
-}
-
-function TextContentSection({ bookmark }: { bookmark: ZBookmark }) {
- let content;
- switch (bookmark.content.type) {
- case "link": {
- if (!bookmark.content.htmlContent) {
- content = (
- <div className="text-destructive">
- Failed to fetch link content ...
- </div>
- );
- } else {
- content = (
- <div
- dangerouslySetInnerHTML={{
- __html: bookmark.content.htmlContent || "",
- }}
- className="prose mx-auto dark:prose-invert"
- />
- );
- }
- break;
- }
- case "text": {
- content = (
- <Markdown className="prose mx-auto dark:prose-invert">
- {bookmark.content.text}
- </Markdown>
- );
- break;
- }
- }
-
- return <ScrollArea className="h-full">{content}</ScrollArea>;
-}
-
-function AssetContentSection({ bookmark }: { bookmark: ZBookmark }) {
- if (bookmark.content.type != "asset") {
- throw new Error("Invalid content type");
- }
-
- let content;
- switch (bookmark.content.assetType) {
- case "image": {
- switch (bookmark.content.assetType) {
- case "image": {
- content = (
- <div className="relative h-full min-w-full">
- <Image
- alt="asset"
- fill={true}
- className="object-contain"
- src={`/api/assets/${bookmark.content.assetId}`}
- />
- </div>
- );
- }
- }
- break;
- }
- }
- return content;
-}
-
-export default function BookmarkPreview({
- initialData,
-}: {
- initialData: ZBookmark;
-}) {
- const { data: bookmark } = api.bookmarks.getBookmark.useQuery(
- {
- bookmarkId: initialData.id,
- },
- {
- initialData,
- refetchInterval: (query) => {
- const data = query.state.data;
- if (!data) {
- return false;
- }
- // If the link is not crawled or not tagged
- if (isBookmarkStillLoading(data)) {
- return 1000;
- }
- return false;
- },
- },
- );
-
- let content;
- switch (bookmark.content.type) {
- case "link":
- case "text": {
- content = <TextContentSection bookmark={bookmark} />;
- break;
- }
- case "asset": {
- content = <AssetContentSection bookmark={bookmark} />;
- break;
- }
- }
-
- const linkHeader = bookmark.content.type == "link" && (
- <LinkHeader bookmark={bookmark} />
- );
-
- return (
- <div className="grid grid-rows-3 gap-2 overflow-hidden bg-background lg:grid-cols-3 lg:grid-rows-none">
- <div className="row-span-2 h-full w-full overflow-hidden p-2 md:col-span-2 lg:row-auto">
- {isBookmarkStillCrawling(bookmark) ? <ContentLoading /> : content}
- </div>
- <div className="lg:col-span1 row-span-1 flex flex-col gap-4 overflow-auto bg-accent p-4 lg:row-auto">
- {linkHeader}
- <CreationTime createdAt={bookmark.createdAt} />
- <div className="flex gap-4">
- <p className="text-sm text-gray-400">Tags</p>
- <TagsEditor bookmark={bookmark} />
- </div>
- <div className="flex gap-4">
- <p className="text-sm text-gray-400">Note</p>
- <NoteEditor bookmark={bookmark} />
- </div>
- </div>
- </div>
- );
-}
diff --git a/apps/web/components/dashboard/bookmarks/NoteEditor.tsx b/apps/web/components/dashboard/bookmarks/NoteEditor.tsx
deleted file mode 100644
index d712d523..00000000
--- a/apps/web/components/dashboard/bookmarks/NoteEditor.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Textarea } from "@/components/ui/textarea";
-import { toast } from "@/components/ui/use-toast";
-import { useClientConfig } from "@/lib/clientConfig";
-import { api } from "@/lib/trpc";
-
-import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
-
-export function NoteEditor({ bookmark }: { bookmark: ZBookmark }) {
- const demoMode = !!useClientConfig().demoMode;
-
- const invalidateBookmarkCache =
- api.useUtils().bookmarks.getBookmark.invalidate;
-
- const updateBookmarkMutator = api.bookmarks.updateBookmark.useMutation({
- onSuccess: () => {
- toast({
- description: "The bookmark has been updated!",
- });
- },
- onError: () => {
- toast({
- description: "Something went wrong while saving the note",
- variant: "destructive",
- });
- },
- onSettled: () => {
- invalidateBookmarkCache({ bookmarkId: bookmark.id });
- },
- });
-
- return (
- <Textarea
- className="h-44 w-full overflow-auto rounded bg-background p-2 text-sm text-gray-400 dark:text-gray-300"
- defaultValue={bookmark.note ?? ""}
- disabled={demoMode}
- placeholder="Write some notes ..."
- onBlur={(e) => {
- if (e.currentTarget.value == bookmark.note) {
- return;
- }
- updateBookmarkMutator.mutate({
- bookmarkId: bookmark.id,
- note: e.currentTarget.value,
- });
- }}
- />
- );
-}
diff --git a/apps/web/components/dashboard/bookmarks/icons.tsx b/apps/web/components/dashboard/bookmarks/icons.tsx
new file mode 100644
index 00000000..d899f19d
--- /dev/null
+++ b/apps/web/components/dashboard/bookmarks/icons.tsx
@@ -0,0 +1,29 @@
+import { Archive, ArchiveRestore, Star } from "lucide-react";
+
+export function FavouritedActionIcon({
+ favourited,
+ className,
+}: {
+ favourited: boolean;
+ className?: string;
+}) {
+ return favourited ? (
+ <Star className={className} color="#ebb434" fill="#ebb434" />
+ ) : (
+ <Star className={className} />
+ );
+}
+
+export function ArchivedActionIcon({
+ archived,
+ className,
+}: {
+ archived: boolean;
+ className?: string;
+}) {
+ return archived ? (
+ <ArchiveRestore className={className} />
+ ) : (
+ <Archive className={className} />
+ );
+}