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 | |
| parent | 44918316007ed3153dc802a4b11db3ea09024a8b (diff) | |
| download | karakeep-79d61be7e15dc5d23fb687a5f71e0097088a99ac.tar.zst | |
feature: Extract hook logic into separate package and add a new action bar in bookmark preview
21 files changed, 483 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", diff --git a/packages/shared-react/hooks/bookmarks.ts b/packages/shared-react/hooks/bookmarks.ts new file mode 100644 index 00000000..7349e680 --- /dev/null +++ b/packages/shared-react/hooks/bookmarks.ts @@ -0,0 +1,43 @@ +import { api } from "../trpc"; + +export function useDeleteBookmark( + ...opts: Parameters<typeof api.bookmarks.deleteBookmark.useMutation> +) { + const apiUtils = api.useUtils(); + return api.bookmarks.deleteBookmark.useMutation({ + ...opts, + onSuccess: (res, req, meta) => { + apiUtils.bookmarks.getBookmarks.invalidate(); + apiUtils.bookmarks.searchBookmarks.invalidate(); + opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} + +export function useUpdateBookmark( + ...opts: Parameters<typeof api.bookmarks.updateBookmark.useMutation> +) { + const apiUtils = api.useUtils(); + return api.bookmarks.updateBookmark.useMutation({ + ...opts, + onSuccess: (res, req, meta) => { + apiUtils.bookmarks.getBookmarks.invalidate(); + apiUtils.bookmarks.searchBookmarks.invalidate(); + apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); + opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} + +export function useRecrawlBookmark( + ...opts: Parameters<typeof api.bookmarks.recrawlBookmark.useMutation> +) { + const apiUtils = api.useUtils(); + return api.bookmarks.recrawlBookmark.useMutation({ + ...opts, + onSuccess: (res, req, meta) => { + apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); + opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} diff --git a/packages/shared-react/hooks/lists.ts b/packages/shared-react/hooks/lists.ts new file mode 100644 index 00000000..5cfcd194 --- /dev/null +++ b/packages/shared-react/hooks/lists.ts @@ -0,0 +1,14 @@ +import { api } from "../trpc"; + +export function useRemoveBookmarkFromList( + ...opts: Parameters<typeof api.lists.removeFromList.useMutation> +) { + const apiUtils = api.useUtils(); + return api.lists.removeFromList.useMutation({ + ...opts, + onSuccess: (res, req, meta) => { + apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); + opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} diff --git a/packages/shared-react/package.json b/packages/shared-react/package.json new file mode 100644 index 00000000..46a2adb5 --- /dev/null +++ b/packages/shared-react/package.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@hoarder/shared-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "@hoarder/trpc": "workspace:^0.1.0", + "@tanstack/react-query": "^5.24.8" + }, + "devDependencies": { + "@hoarder/eslint-config": "workspace:^0.2.0", + "@hoarder/prettier-config": "workspace:^0.1.0", + "@hoarder/tsconfig": "workspace:^0.1.0" + }, +"peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + }, + "react-native-web": { + "optional": true + } + }, + "scripts": { + "typecheck": "tsc --noEmit", + "format": "prettier . --ignore-path ../../.prettierignore", + "lint": "eslint ." + }, + "eslintConfig": { + "root": true, + "extends": [ + "@hoarder/eslint-config/base", + "@hoarder/eslint-config/react" + ] + }, + "prettier": "@hoarder/prettier-config" +} diff --git a/packages/shared-react/trpc.ts b/packages/shared-react/trpc.ts new file mode 100644 index 00000000..99fdd8b5 --- /dev/null +++ b/packages/shared-react/trpc.ts @@ -0,0 +1,7 @@ +"use client"; + +import { createTRPCReact } from "@trpc/react-query"; + +import type { AppRouter } from "@hoarder/trpc/routers/_app"; + +export const api = createTRPCReact<AppRouter>(); diff --git a/packages/shared-react/tsconfig.json b/packages/shared-react/tsconfig.json new file mode 100644 index 00000000..8c3411bf --- /dev/null +++ b/packages/shared-react/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@hoarder/tsconfig/base.json", + "compilerOptions": { + "baseUrl": ".", + "plugins": [], + "paths": { + "@/*": ["./*"] + }, + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39f47b8b..00b895e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: apps/browser-extension: dependencies: + '@hoarder/shared-react': + specifier: workspace:^0.1.0 + version: link:../../packages/shared-react '@hoarder/trpc': specifier: workspace:^0.1.0 version: link:../../packages/trpc @@ -247,6 +250,9 @@ importers: apps/mobile: dependencies: + '@hoarder/shared-react': + specifier: workspace:^0.1.0 + version: link:../../packages/shared-react '@hoarder/trpc': specifier: workspace:^0.1.0 version: link:../../packages/trpc @@ -404,6 +410,9 @@ importers: '@hoarder/shared': specifier: workspace:^0.1.0 version: link:../../packages/shared + '@hoarder/shared-react': + specifier: workspace:^0.1.0 + version: link:../../packages/shared-react '@hoarder/trpc': specifier: workspace:^0.1.0 version: link:../../packages/trpc @@ -779,6 +788,34 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript + packages/shared-react: + dependencies: + '@hoarder/trpc': + specifier: workspace:^0.1.0 + version: link:../trpc + '@tanstack/react-query': + specifier: ^5.24.8 + version: 5.24.8(react@18.2.0) + react: + specifier: '*' + version: 18.2.0 + react-native: + specifier: '*' + version: 0.73.4(@babel/core@7.24.0)(@babel/preset-env@7.24.0(@babel/core@7.24.0))(react@18.2.0) + react-native-web: + specifier: '*' + version: 0.19.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + devDependencies: + '@hoarder/eslint-config': + specifier: workspace:^0.2.0 + version: link:../../tooling/eslint + '@hoarder/prettier-config': + specifier: workspace:^0.1.0 + version: link:../../tooling/prettier + '@hoarder/tsconfig': + specifier: workspace:^0.1.0 + version: link:../../tooling/typescript + packages/trpc: dependencies: '@hoarder/db': @@ -5442,6 +5479,9 @@ packages: peerDependencies: postcss: ^8.0.9 + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + css-loader@6.10.0: resolution: {integrity: sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==} engines: {node: '>= 12.13.0'} @@ -6529,6 +6569,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-loops@1.1.3: + resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} + fast-url-parser@1.1.3: resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} @@ -7192,6 +7235,9 @@ packages: engines: {node: '>=18'} hasBin: true + hyphenate-style-name@1.0.4: + resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -7277,6 +7323,9 @@ packages: inline-style-parser@0.2.2: resolution: {integrity: sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==} + inline-style-prefixer@6.0.4: + resolution: {integrity: sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==} + install-artifact-from-github@1.3.5: resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==} hasBin: true @@ -10032,6 +10081,12 @@ packages: react: '*' react-native: '*' + react-native-web@0.19.10: + resolution: {integrity: sha512-IQoHiTQq8egBCVVwmTrYcFLgEFyb4LMZYEktHn4k22JMk9+QTCEz5WTfvr+jdNoeqj/7rtE81xgowKbfGO74qg==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + react-native@0.73.4: resolution: {integrity: sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==} engines: {node: '>=18'} @@ -10891,6 +10946,9 @@ packages: peerDependencies: postcss: ^8.2.15 + styleq@0.1.3: + resolution: {integrity: sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==} + stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -19676,6 +19734,11 @@ snapshots: postcss: 8.4.35 dev: false + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.0.4 + dev: false + css-loader@6.10.0(webpack@5.90.3): dependencies: icss-utils: 5.1.0(postcss@8.4.35) @@ -21217,6 +21280,9 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-loops@1.1.3: + dev: false + fast-url-parser@1.1.3: dependencies: punycode: 1.4.1 @@ -22174,6 +22240,9 @@ snapshots: husky@9.0.11: dev: true + hyphenate-style-name@1.0.4: + dev: false + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -22253,6 +22322,12 @@ snapshots: inline-style-parser@0.2.2: {} + inline-style-prefixer@6.0.4: + dependencies: + css-in-js-utils: 3.1.0 + fast-loops: 1.1.3 + dev: false + install-artifact-from-github@1.3.5: dev: false @@ -25980,6 +26055,22 @@ snapshots: react-native: 0.73.4(@babel/core@7.23.9)(@babel/preset-env@7.24.0(@babel/core@7.23.9))(react@18.2.0) dev: false + react-native-web@0.19.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + dependencies: + '@babel/runtime': 7.23.9 + '@react-native/normalize-color': 2.1.0 + fbjs: 3.0.5 + inline-style-prefixer: 6.0.4 + memoize-one: 6.0.0 + nullthrows: 1.1.1 + postcss-value-parser: 4.2.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styleq: 0.1.3 + transitivePeerDependencies: + - encoding + dev: false + react-native@0.73.4(@babel/core@7.23.9)(@babel/preset-env@7.24.0(@babel/core@7.23.9))(react@18.2.0): dependencies: '@jest/create-cache-key-function': 29.7.0 @@ -27234,6 +27325,9 @@ snapshots: postcss-selector-parser: 6.0.15 dev: false + styleq@0.1.3: + dev: false + stylis@4.2.0: dev: false |
