diff options
| author | MohamedBassem <me@mbassem.com> | 2024-04-07 18:30:00 +0100 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-04-07 19:00:00 +0100 |
| commit | 79d61be7e15dc5d23fb687a5f71e0097088a99ac (patch) | |
| tree | da72f19cdb74ef4ed2a75bcfddd13bdfb874f205 /apps | |
| parent | 44918316007ed3153dc802a4b11db3ea09024a8b (diff) | |
| download | karakeep-79d61be7e15dc5d23fb687a5f71e0097088a99ac.tar.zst | |
feature: Extract hook logic into separate package and add a new action bar in bookmark preview
Diffstat (limited to 'apps')
15 files changed, 270 insertions, 141 deletions
diff --git a/apps/browser-extension/package.json b/apps/browser-extension/package.json index 35c899bc..20cc843c 100644 --- a/apps/browser-extension/package.json +++ b/apps/browser-extension/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hoarder/trpc": "workspace:^0.1.0", + "@hoarder/shared-react": "workspace:^0.1.0", "@tanstack/react-query": "^5.24.8", "@trpc/client": "11.0.0-next-beta.308", "@trpc/next": "11.0.0-next-beta.308", diff --git a/apps/browser-extension/src/BookmarkSavedPage.tsx b/apps/browser-extension/src/BookmarkSavedPage.tsx index 54f62796..3535ade8 100644 --- a/apps/browser-extension/src/BookmarkSavedPage.tsx +++ b/apps/browser-extension/src/BookmarkSavedPage.tsx @@ -2,24 +2,24 @@ import { useState } from "react"; import { ArrowUpRightFromSquare, Trash } from "lucide-react"; import { Link, useNavigate, useParams } from "react-router-dom"; +import { useDeleteBookmark } from "@hoarder/shared-react/hooks/bookmarks"; + import Spinner from "./Spinner"; import usePluginSettings from "./utils/settings"; -import { api } from "./utils/trpc"; export default function BookmarkSavedPage() { const { bookmarkId } = useParams(); const navigate = useNavigate(); const [error, setError] = useState(""); - const { mutate: deleteBookmark, isPending } = - api.bookmarks.deleteBookmark.useMutation({ - onSuccess: () => { - navigate("/bookmarkdeleted"); - }, - onError: (e) => { - setError(e.message); - }, - }); + const { mutate: deleteBookmark, isPending } = useDeleteBookmark({ + onSuccess: () => { + navigate("/bookmarkdeleted"); + }, + onError: (e) => { + setError(e.message); + }, + }); const { settings } = usePluginSettings(); diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index 89ce7924..d4fbcb58 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -17,6 +17,10 @@ import { MenuView } from "@react-native-menu/menu"; import { Ellipsis, Star } from "lucide-react-native"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import { + useDeleteBookmark, + useUpdateBookmark, +} from "@hoarder/shared-react/hooks/bookmarks"; import { Divider } from "../ui/Divider"; import { Skeleton } from "../ui/Skeleton"; @@ -45,7 +49,6 @@ export function isBookmarkStillLoading(bookmark: ZBookmark) { function ActionBar({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); - const apiUtils = api.useUtils(); const onError = () => { toast({ @@ -56,37 +59,27 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { }; const { mutate: deleteBookmark, isPending: isDeletionPending } = - api.bookmarks.deleteBookmark.useMutation({ + useDeleteBookmark({ onSuccess: () => { toast({ message: "The bookmark has been deleted!", showProgress: false, }); - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); }, onError, }); - const { mutate: favouriteBookmark, variables } = - api.bookmarks.updateBookmark.useMutation({ - onSuccess: () => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); - }, - onError, - }); + const { mutate: favouriteBookmark, variables } = useUpdateBookmark({ + onError, + }); const { mutate: archiveBookmark, isPending: isArchivePending } = - api.bookmarks.updateBookmark.useMutation({ + useUpdateBookmark({ onSuccess: (resp) => { toast({ message: `The bookmark has been ${resp.archived ? "archived" : "un-archived"}!`, showProgress: false, }); - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); - apiUtils.bookmarks.searchBookmarks.invalidate(); }, onError, }); diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 6ccd5622..9f170040 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@hoarder/trpc": "workspace:^0.1.0", + "@hoarder/shared-react": "workspace:^0.1.0", "@react-native-menu/menu": "^0.9.1", "@tanstack/react-query": "^5.24.8", "class-variance-authority": "^0.7.0", diff --git a/apps/web/app/dashboard/preview/[bookmarkId]/page.tsx b/apps/web/app/dashboard/preview/[bookmarkId]/page.tsx index 201cbcaf..6cac7377 100644 --- a/apps/web/app/dashboard/preview/[bookmarkId]/page.tsx +++ b/apps/web/app/dashboard/preview/[bookmarkId]/page.tsx @@ -1,4 +1,4 @@ -import BookmarkPreview from "@/components/dashboard/bookmarks/BookmarkPreview"; +import BookmarkPreview from "@/components/dashboard/preview/BookmarkPreview"; import { api } from "@/server/api/client"; export default async function BookmarkPreviewPage({ 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/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} /> + ); +} diff --git a/apps/web/components/dashboard/preview/ActionBar.tsx b/apps/web/components/dashboard/preview/ActionBar.tsx new file mode 100644 index 00000000..f2e3023e --- /dev/null +++ b/apps/web/components/dashboard/preview/ActionBar.tsx @@ -0,0 +1,115 @@ +import { ActionButton } from "@/components/ui/action-button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { toast } from "@/components/ui/use-toast"; +import { Trash2 } from "lucide-react"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import { + useDeleteBookmark, + useUpdateBookmark, +} from "@hoarder/shared-react/hooks/bookmarks"; + +import { ArchivedActionIcon, FavouritedActionIcon } from "../bookmarks/icons"; + +export default function ActionBar({ bookmark }: { bookmark: ZBookmark }) { + const onError = () => { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + }; + const { mutate: favBookmark, isPending: pendingFav } = useUpdateBookmark({ + onSuccess: () => { + toast({ + description: "The bookmark has been updated!", + }); + }, + onError, + }); + const { mutate: archiveBookmark, isPending: pendingArchive } = + useUpdateBookmark({ + onSuccess: (resp) => { + toast({ + description: `The bookmark has been ${resp.archived ? "Archived" : "Un-archived"}!`, + }); + }, + onError, + }); + const { mutate: deleteBookmark, isPending: pendingDeletion } = + useDeleteBookmark({ + onSuccess: () => { + toast({ + description: "The bookmark has been deleted!", + }); + }, + onError, + }); + + return ( + <TooltipProvider> + <div className="flex items-center justify-center gap-3"> + <Tooltip delayDuration={0}> + <TooltipTrigger> + <ActionButton + variant="none" + className="size-14 rounded-full bg-background" + loading={pendingFav} + onClick={() => { + favBookmark({ + bookmarkId: bookmark.id, + favourited: !bookmark.favourited, + }); + }} + > + <FavouritedActionIcon favourited={bookmark.favourited} /> + </ActionButton> + </TooltipTrigger> + <TooltipContent side="bottom"> + {bookmark.favourited ? "Un-favourite" : "Favourite"} + </TooltipContent> + </Tooltip> + <Tooltip delayDuration={0}> + <TooltipTrigger> + <ActionButton + variant="none" + loading={pendingArchive} + className="size-14 rounded-full bg-background" + onClick={() => { + archiveBookmark({ + bookmarkId: bookmark.id, + archived: !bookmark.archived, + }); + }} + > + <ArchivedActionIcon archived={bookmark.archived} /> + </ActionButton> + </TooltipTrigger> + <TooltipContent side="bottom"> + {bookmark.archived ? "Un-archive" : "Archive"} + </TooltipContent> + </Tooltip> + <Tooltip delayDuration={0}> + <TooltipTrigger> + <ActionButton + loading={pendingDeletion} + className="size-14 rounded-full bg-background" + variant="none" + onClick={() => { + deleteBookmark({ bookmarkId: bookmark.id }); + }} + > + <Trash2 /> + </ActionButton> + </TooltipTrigger> + <TooltipContent side="bottom">Delete</TooltipContent> + </Tooltip> + </div> + </TooltipProvider> + ); +} diff --git a/apps/web/components/dashboard/preview/AssetContentSection.tsx b/apps/web/components/dashboard/preview/AssetContentSection.tsx new file mode 100644 index 00000000..3fbbc519 --- /dev/null +++ b/apps/web/components/dashboard/preview/AssetContentSection.tsx @@ -0,0 +1,31 @@ +import Image from "next/image"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; + +export 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; +} diff --git a/apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx b/apps/web/components/dashboard/preview/BookmarkPreview.tsx index 102e0788..bd7881a3 100644 --- a/apps/web/components/dashboard/bookmarks/BookmarkPreview.tsx +++ b/apps/web/components/dashboard/preview/BookmarkPreview.tsx @@ -1,8 +1,7 @@ "use client"; -import Image from "next/image"; import Link from "next/link"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { TagsEditor } from "@/components/dashboard/bookmarks/TagsEditor"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -20,12 +19,13 @@ 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 ActionBar from "./ActionBar"; +import { AssetContentSection } from "./AssetContentSection"; import { NoteEditor } from "./NoteEditor"; -import { TagsEditor } from "./TagsEditor"; +import { TextContentSection } from "./TextContentSection"; dayjs.extend(relativeTime); @@ -89,69 +89,6 @@ function LinkHeader({ bookmark }: { bookmark: ZBookmark }) { ); } -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, }: { @@ -210,6 +147,7 @@ export default function BookmarkPreview({ <p className="text-sm text-gray-400">Note</p> <NoteEditor bookmark={bookmark} /> </div> + <ActionBar bookmark={bookmark} /> </div> </div> ); diff --git a/apps/web/components/dashboard/bookmarks/NoteEditor.tsx b/apps/web/components/dashboard/preview/NoteEditor.tsx index d712d523..6011e89d 100644 --- a/apps/web/components/dashboard/bookmarks/NoteEditor.tsx +++ b/apps/web/components/dashboard/preview/NoteEditor.tsx @@ -1,17 +1,14 @@ 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"; +import { useUpdateBookmark } from "@hoarder/shared-react/hooks/bookmarks"; export function NoteEditor({ bookmark }: { bookmark: ZBookmark }) { const demoMode = !!useClientConfig().demoMode; - const invalidateBookmarkCache = - api.useUtils().bookmarks.getBookmark.invalidate; - - const updateBookmarkMutator = api.bookmarks.updateBookmark.useMutation({ + const updateBookmarkMutator = useUpdateBookmark({ onSuccess: () => { toast({ description: "The bookmark has been updated!", @@ -23,9 +20,6 @@ export function NoteEditor({ bookmark }: { bookmark: ZBookmark }) { variant: "destructive", }); }, - onSettled: () => { - invalidateBookmarkCache({ bookmarkId: bookmark.id }); - }, }); return ( diff --git a/apps/web/components/dashboard/preview/TextContentSection.tsx b/apps/web/components/dashboard/preview/TextContentSection.tsx new file mode 100644 index 00000000..35ee1b33 --- /dev/null +++ b/apps/web/components/dashboard/preview/TextContentSection.tsx @@ -0,0 +1,39 @@ +import { ScrollArea } from "@radix-ui/react-scroll-area"; +import Markdown from "react-markdown"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; + +export 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>; +} diff --git a/apps/web/components/ui/button.tsx b/apps/web/components/ui/button.tsx index 42c8f137..40794eb2 100644 --- a/apps/web/components/ui/button.tsx +++ b/apps/web/components/ui/button.tsx @@ -9,6 +9,7 @@ const buttonVariants = cva( { variants: { variant: { + none: "", default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", @@ -19,6 +20,7 @@ const buttonVariants = cva( secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", + border: "border border-input hover:bg-accent", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/apps/web/package.json b/apps/web/package.json index c959e30c..6b8e0805 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,6 +21,7 @@ "@hoarder/db": "workspace:^0.1.0", "@hoarder/shared": "workspace:^0.1.0", "@hoarder/trpc": "workspace:^0.1.0", + "@hoarder/shared-react": "workspace:^0.1.0", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", |
