diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-10 17:59:58 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-10 17:59:58 +0000 |
| commit | d6dd76021226802adf5295b3243d6f2ae4fa5cc2 (patch) | |
| tree | 7a25423d46db9e0e224b5f58b73cec5768953b44 /packages/web/app/dashboard/bookmarks/components | |
| parent | 8ab868d3f94cc6609d278dc952432f1a244c3f84 (diff) | |
| download | karakeep-d6dd76021226802adf5295b3243d6f2ae4fa5cc2.tar.zst | |
refactor: Move all components to the top level directory
Diffstat (limited to 'packages/web/app/dashboard/bookmarks/components')
12 files changed, 0 insertions, 1162 deletions
diff --git a/packages/web/app/dashboard/bookmarks/components/AddBookmark.tsx b/packages/web/app/dashboard/bookmarks/components/AddBookmark.tsx deleted file mode 100644 index d12fc663..00000000 --- a/packages/web/app/dashboard/bookmarks/components/AddBookmark.tsx +++ /dev/null @@ -1,100 +0,0 @@ -"use client"; - -import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Pencil, Plus } from "lucide-react"; -import { useForm, SubmitErrorHandler } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { toast } from "@/components/ui/use-toast"; -import { api } from "@/lib/trpc"; -import { ActionButton } from "@/components/ui/action-button"; -import { Button } from "@/components/ui/button"; -import { BookmarkedTextEditor } from "./BookmarkedTextEditor"; -import { useState } from "react"; - -function AddText() { - const [isEditorOpen, setEditorOpen] = useState(false); - - return ( - <div className="flex"> - <BookmarkedTextEditor open={isEditorOpen} setOpen={setEditorOpen} /> - <Button className="m-auto" onClick={() => setEditorOpen(true)}> - <Pencil /> - </Button> - </div> - ); -} - -function AddLink() { - const formSchema = z.object({ - url: z.string().url({ message: "The link must be a valid URL" }), - }); - const form = useForm<z.infer<typeof formSchema>>({ - resolver: zodResolver(formSchema), - defaultValues: { - url: "", - }, - }); - - const invalidateBookmarksCache = api.useUtils().bookmarks.invalidate; - const createBookmarkMutator = api.bookmarks.createBookmark.useMutation({ - onSuccess: () => { - invalidateBookmarksCache(); - form.reset(); - }, - onError: () => { - toast({ description: "Something went wrong", variant: "destructive" }); - }, - }); - - const onError: SubmitErrorHandler<z.infer<typeof formSchema>> = (errors) => { - toast({ - description: Object.values(errors) - .map((v) => v.message) - .join("\n"), - variant: "destructive", - }); - }; - - return ( - <Form {...form}> - <form - className="flex-grow" - onSubmit={form.handleSubmit( - (value) => - createBookmarkMutator.mutate({ url: value.url, type: "link" }), - onError, - )} - > - <div className="flex w-full items-center space-x-2 py-4"> - <FormField - control={form.control} - name="url" - render={({ field }) => { - return ( - <FormItem className="flex-1"> - <FormControl> - <Input type="text" placeholder="Link" {...field} /> - </FormControl> - </FormItem> - ); - }} - /> - <ActionButton type="submit" loading={createBookmarkMutator.isPending}> - <Plus /> - </ActionButton> - </div> - </form> - </Form> - ); -} - -export default function AddBookmark() { - return ( - <div className="container flex gap-2"> - <AddLink /> - <AddText /> - </div> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/AddToListModal.tsx b/packages/web/app/dashboard/bookmarks/components/AddToListModal.tsx deleted file mode 100644 index c9fd5da0..00000000 --- a/packages/web/app/dashboard/bookmarks/components/AddToListModal.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { ActionButton } from "@/components/ui/action-button"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; - -import { toast } from "@/components/ui/use-toast"; -import { api } from "@/lib/trpc"; -import { useState } from "react"; - -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import LoadingSpinner from "@/components/ui/spinner"; -import { z } from "zod"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; - -export default function AddToListModal({ - bookmarkId, - open, - setOpen, -}: { - bookmarkId: string; - open: boolean; - setOpen: (open: boolean) => void; -}) { - const formSchema = z.object({ - listId: z.string({ - required_error: "Please select a list", - }), - }); - const form = useForm<z.infer<typeof formSchema>>({ - resolver: zodResolver(formSchema), - }); - - const { data: lists, isPending: isFetchingListsPending } = - api.lists.list.useQuery(); - - const listInvalidationFunction = api.useUtils().lists.get.invalidate; - const bookmarksInvalidationFunction = - api.useUtils().bookmarks.getBookmarks.invalidate; - - const { mutate: addToList, isPending: isAddingToListPending } = - api.lists.addToList.useMutation({ - onSuccess: (_resp, req) => { - toast({ - description: "List has been updated!", - }); - listInvalidationFunction({ listId: req.listId }); - bookmarksInvalidationFunction(); - }, - onError: (e) => { - if (e.data?.code == "BAD_REQUEST") { - toast({ - variant: "destructive", - description: e.message, - }); - } else { - toast({ - variant: "destructive", - title: "Something went wrong", - }); - } - }, - }); - - const isPending = isFetchingListsPending || isAddingToListPending; - - return ( - <Dialog open={open} onOpenChange={setOpen}> - <DialogContent> - <Form {...form}> - <form - onSubmit={form.handleSubmit((value) => { - addToList({ - bookmarkId: bookmarkId, - listId: value.listId, - }); - })} - > - <DialogHeader> - <DialogTitle>Add to List</DialogTitle> - </DialogHeader> - - <div className="py-4"> - {lists ? ( - <FormField - control={form.control} - name="listId" - render={({ field }) => { - return ( - <FormItem> - <FormControl> - <Select onValueChange={field.onChange}> - <SelectTrigger className="w-full"> - <SelectValue placeholder="Select a list" /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - {lists && - lists.lists.map((l) => ( - <SelectItem key={l.id} value={l.id}> - {l.icon} {l.name} - </SelectItem> - ))} - </SelectGroup> - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - ); - }} - /> - ) : ( - <LoadingSpinner /> - )} - </div> - <DialogFooter className="sm:justify-end"> - <DialogClose asChild> - <Button type="button" variant="secondary"> - Close - </Button> - </DialogClose> - <ActionButton - type="submit" - loading={isAddingToListPending} - disabled={isPending} - > - Add - </ActionButton> - </DialogFooter> - </form> - </Form> - </DialogContent> - </Dialog> - ); -} - -export function useAddToListModal(bookmarkId: string) { - const [open, setOpen] = useState(false); - - return { - open, - setOpen, - content: ( - <AddToListModal bookmarkId={bookmarkId} open={open} setOpen={setOpen} /> - ), - }; -} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkCardSkeleton.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkCardSkeleton.tsx deleted file mode 100644 index 1f5fa433..00000000 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkCardSkeleton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - ImageCard, - ImageCardBody, - ImageCardContent, - ImageCardFooter, - ImageCardTitle, - ImageCardBanner, -} from "@/components/ui/imageCard"; -import { Skeleton } from "@/components/ui/skeleton"; - -export default function BookmarkCardSkeleton() { - return ( - <ImageCard - className={ - "border-grey-100 border bg-gray-50 duration-300 ease-in hover:border-blue-300 hover:transition-all" - } - > - <ImageCardBanner src="/blur.avif" /> - <ImageCardContent> - <ImageCardTitle></ImageCardTitle> - <ImageCardBody className="space-y-2"> - <Skeleton className="h-4 w-full" /> - <Skeleton className="h-4 w-full" /> - <Skeleton className="h-4 w-full" /> - </ImageCardBody> - <ImageCardFooter></ImageCardFooter> - </ImageCardContent> - </ImageCard> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx deleted file mode 100644 index 4f08ebee..00000000 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx +++ /dev/null @@ -1,185 +0,0 @@ -"use client"; - -import { useToast } from "@/components/ui/use-toast"; -import { api } from "@/lib/trpc"; -import { ZBookmark, ZBookmarkedLink } from "@hoarder/trpc/types/bookmarks"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - Archive, - Link, - List, - MoreHorizontal, - Pencil, - RotateCw, - Star, - Tags, - Trash2, -} from "lucide-react"; -import { useTagModel } from "./TagModal"; -import { useState } from "react"; -import { BookmarkedTextEditor } from "./BookmarkedTextEditor"; -import { useAddToListModal } from "./AddToListModal"; - -export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { - const { toast } = useToast(); - const linkId = bookmark.id; - - const { setOpen: setTagModalIsOpen, content: tagModal } = - useTagModel(bookmark); - const { setOpen: setAddToListModalOpen, content: addToListModal } = - useAddToListModal(bookmark.id); - - const [isTextEditorOpen, setTextEditorOpen] = useState(false); - - const invalidateAllBookmarksCache = - api.useUtils().bookmarks.getBookmarks.invalidate; - - const invalidateBookmarkCache = - api.useUtils().bookmarks.getBookmark.invalidate; - - const onError = () => { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - }; - const deleteBookmarkMutator = api.bookmarks.deleteBookmark.useMutation({ - onSuccess: () => { - toast({ - description: "The bookmark has been deleted!", - }); - }, - onError, - onSettled: () => { - invalidateAllBookmarksCache(); - }, - }); - - const updateBookmarkMutator = api.bookmarks.updateBookmark.useMutation({ - onSuccess: () => { - toast({ - description: "The bookmark has been updated!", - }); - }, - onError, - onSettled: () => { - invalidateBookmarkCache({ bookmarkId: bookmark.id }); - invalidateAllBookmarksCache(); - }, - }); - - const crawlBookmarkMutator = api.bookmarks.recrawlBookmark.useMutation({ - onSuccess: () => { - toast({ - description: "Re-fetch has been enqueued!", - }); - }, - onError, - onSettled: () => { - invalidateBookmarkCache({ bookmarkId: bookmark.id }); - }, - }); - - return ( - <> - {tagModal} - {addToListModal} - <BookmarkedTextEditor - bookmark={bookmark} - open={isTextEditorOpen} - setOpen={setTextEditorOpen} - /> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - variant="ghost" - className="px-1 focus-visible:ring-0 focus-visible:ring-offset-0" - > - <MoreHorizontal /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-fit"> - {bookmark.content.type === "text" && ( - <DropdownMenuItem onClick={() => setTextEditorOpen(true)}> - <Pencil className="mr-2 size-4" /> - <span>Edit</span> - </DropdownMenuItem> - )} - <DropdownMenuItem - onClick={() => - updateBookmarkMutator.mutate({ - bookmarkId: linkId, - favourited: !bookmark.favourited, - }) - } - > - <Star className="mr-2 size-4" /> - <span>{bookmark.favourited ? "Un-favourite" : "Favourite"}</span> - </DropdownMenuItem> - <DropdownMenuItem - onClick={() => - updateBookmarkMutator.mutate({ - bookmarkId: linkId, - archived: !bookmark.archived, - }) - } - > - <Archive className="mr-2 size-4" /> - <span>{bookmark.archived ? "Un-archive" : "Archive"}</span> - </DropdownMenuItem> - {bookmark.content.type === "link" && ( - <DropdownMenuItem - onClick={() => { - navigator.clipboard.writeText( - (bookmark.content as ZBookmarkedLink).url, - ); - toast({ - description: "Link was added to your clipboard!", - }); - }} - > - <Link className="mr-2 size-4" /> - <span>Copy Link</span> - </DropdownMenuItem> - )} - <DropdownMenuItem onClick={() => setTagModalIsOpen(true)}> - <Tags className="mr-2 size-4" /> - <span>Edit Tags</span> - </DropdownMenuItem> - - <DropdownMenuItem onClick={() => setAddToListModalOpen(true)}> - <List className="mr-2 size-4" /> - <span>Add to List</span> - </DropdownMenuItem> - - {bookmark.content.type === "link" && ( - <DropdownMenuItem - onClick={() => - crawlBookmarkMutator.mutate({ bookmarkId: bookmark.id }) - } - > - <RotateCw className="mr-2 size-4" /> - <span>Refresh</span> - </DropdownMenuItem> - )} - <DropdownMenuItem - className="text-destructive" - onClick={() => - deleteBookmarkMutator.mutate({ bookmarkId: bookmark.id }) - } - > - <Trash2 className="mr-2 size-4" /> - <span>Delete</span> - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - </> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx deleted file mode 100644 index a5b58f1a..00000000 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { ActionButton } from "@/components/ui/action-button"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { api } from "@/lib/trpc"; -import { useState } from "react"; -import { toast } from "@/components/ui/use-toast"; - -export function BookmarkedTextEditor({ - bookmark, - open, - setOpen, -}: { - bookmark?: ZBookmark; - open: boolean; - setOpen: (open: boolean) => void; -}) { - const isNewBookmark = bookmark === undefined; - const [noteText, setNoteText] = useState( - bookmark && bookmark.content.type == "text" ? bookmark.content.text : "", - ); - - const invalidateAllBookmarksCache = - api.useUtils().bookmarks.getBookmarks.invalidate; - const invalidateOneBookmarksCache = - api.useUtils().bookmarks.getBookmark.invalidate; - - const { mutate: createBookmarkMutator, isPending: isCreationPending } = - api.bookmarks.createBookmark.useMutation({ - onSuccess: () => { - invalidateAllBookmarksCache(); - toast({ - description: "Note created!", - }); - setOpen(false); - setNoteText(""); - }, - onError: () => { - toast({ description: "Something went wrong", variant: "destructive" }); - }, - }); - const { mutate: updateBookmarkMutator, isPending: isUpdatePending } = - api.bookmarks.updateBookmarkText.useMutation({ - onSuccess: () => { - invalidateOneBookmarksCache({ - bookmarkId: bookmark!.id, - }); - toast({ - description: "Note updated!", - }); - setOpen(false); - }, - onError: () => { - toast({ description: "Something went wrong", variant: "destructive" }); - }, - }); - const isPending = isCreationPending || isUpdatePending; - - const onSave = () => { - if (isNewBookmark) { - createBookmarkMutator({ - type: "text", - text: noteText, - }); - } else { - updateBookmarkMutator({ - bookmarkId: bookmark.id, - text: noteText, - }); - } - }; - - return ( - <Dialog open={open} onOpenChange={setOpen}> - <DialogContent> - <DialogHeader> - <DialogTitle>{isNewBookmark ? "New Note" : "Edit Note"}</DialogTitle> - <DialogDescription> - Write your note with markdown support - </DialogDescription> - </DialogHeader> - <Textarea - value={noteText} - onChange={(e) => setNoteText(e.target.value)} - className="h-52 grow" - /> - <DialogFooter className="flex-shrink gap-1 sm:justify-end"> - <DialogClose asChild> - <Button type="button" variant="secondary"> - Close - </Button> - </DialogClose> - <ActionButton type="button" loading={isPending} onClick={onSave}> - Save - </ActionButton> - </DialogFooter> - </DialogContent> - </Dialog> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextViewer.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkedTextViewer.tsx deleted file mode 100644 index 8a620341..00000000 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextViewer.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Dialog, DialogContent } from "@/components/ui/dialog"; -import Markdown from "react-markdown"; - -export function BookmarkedTextViewer({ - content, - open, - setOpen, -}: { - content: string; - open: boolean; - setOpen: (open: boolean) => void; -}) { - return ( - <Dialog open={open} onOpenChange={setOpen}> - <DialogContent className="max-h-[75%] overflow-auto"> - <Markdown className="prose">{content}</Markdown> - </DialogContent> - </Dialog> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx b/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx deleted file mode 100644 index 1ad3670c..00000000 --- a/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { redirect } from "next/navigation"; -import BookmarksGrid from "./BookmarksGrid"; -import { ZGetBookmarksRequest } from "@hoarder/trpc/types/bookmarks"; -import { api } from "@/server/api/client"; -import { getServerAuthSession } from "@/server/auth"; - -export default async function Bookmarks({ - favourited, - archived, - title, - showDivider, -}: ZGetBookmarksRequest & { title: string; showDivider?: boolean }) { - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } - - const query = { - favourited, - archived, - }; - - const bookmarks = await api.bookmarks.getBookmarks(query); - - return ( - <div className="container flex flex-col gap-3"> - <div className="text-2xl">{title}</div> - {showDivider && <hr />} - <BookmarksGrid query={query} bookmarks={bookmarks.bookmarks} /> - </div> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx deleted file mode 100644 index 4d5b6b0a..00000000 --- a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx +++ /dev/null @@ -1,64 +0,0 @@ -"use client"; - -import LinkCard from "./LinkCard"; -import { ZBookmark, ZGetBookmarksRequest } from "@hoarder/trpc/types/bookmarks"; -import { api } from "@/lib/trpc"; -import TextCard from "./TextCard"; -import { Slot } from "@radix-ui/react-slot"; -import Masonry from "react-masonry-css"; -import resolveConfig from "tailwindcss/resolveConfig"; -import tailwindConfig from "@/tailwind.config"; -import { useMemo } from "react"; - -function getBreakpointConfig() { - const fullConfig = resolveConfig(tailwindConfig); - - const breakpointColumnsObj: { [key: number]: number; default: number } = { - default: 3, - }; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.lg)] = 2; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.md)] = 1; - breakpointColumnsObj[parseInt(fullConfig.theme.screens.sm)] = 1; - return breakpointColumnsObj; -} - -function renderBookmark(bookmark: ZBookmark) { - let comp; - switch (bookmark.content.type) { - case "link": - comp = <LinkCard bookmark={bookmark} />; - break; - case "text": - comp = <TextCard bookmark={bookmark} />; - break; - } - return ( - <Slot - key={bookmark.id} - className="border-grey-100 mb-4 border bg-gray-50 duration-300 ease-in hover:border-blue-300 hover:transition-all" - > - {comp} - </Slot> - ); -} - -export default function BookmarksGrid({ - query, - bookmarks: initialBookmarks, -}: { - query: ZGetBookmarksRequest; - bookmarks: ZBookmark[]; -}) { - const { data } = api.bookmarks.getBookmarks.useQuery(query, { - initialData: { bookmarks: initialBookmarks }, - }); - const breakpointConfig = useMemo(() => getBreakpointConfig(), []); - if (data.bookmarks.length == 0) { - return <p>No bookmarks</p>; - } - return ( - <Masonry className="flex gap-4" breakpointCols={breakpointConfig}> - {data.bookmarks.map((b) => renderBookmark(b))} - </Masonry> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx deleted file mode 100644 index 50f30e47..00000000 --- a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import { - ImageCard, - ImageCardBanner, - ImageCardBody, - ImageCardContent, - ImageCardFooter, - ImageCardTitle, -} from "@/components/ui/imageCard"; -import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import Link from "next/link"; -import BookmarkOptions from "./BookmarkOptions"; -import { api } from "@/lib/trpc"; -import { Maximize2, Star } from "lucide-react"; -import TagList from "./TagList"; -import { - isBookmarkStillCrawling, - isBookmarkStillLoading, - isBookmarkStillTagging, -} from "@/lib/bookmarkUtils"; - -export default function LinkCard({ - bookmark: initialData, - className, -}: { - bookmark: ZBookmark; - className?: string; -}) { - 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; - }, - }, - ); - const link = bookmark.content; - if (link.type != "link") { - throw new Error("Unexpected bookmark type"); - } - const parsedUrl = new URL(link.url); - - // A dummy white pixel for when there's no image. - // TODO: Better handling for cards with no images - const image = - link.imageUrl ?? - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+P///38ACfsD/QVDRcoAAAAASUVORK5CYII="; - - return ( - <ImageCard className={className}> - <Link href={link.url}> - <ImageCardBanner - src={isBookmarkStillCrawling(bookmark) ? "/blur.avif" : image} - /> - </Link> - <ImageCardContent> - <ImageCardTitle> - <Link className="line-clamp-2" href={link.url} target="_blank"> - {link?.title ?? parsedUrl.host} - </Link> - </ImageCardTitle> - {/* There's a hack here. Every tag has the full hight of the container itself. That why, when we enable flex-wrap, - the overflowed don't show up. */} - <ImageCardBody className="flex h-full flex-wrap space-x-1 overflow-hidden"> - <TagList - bookmark={bookmark} - loading={isBookmarkStillTagging(bookmark)} - /> - </ImageCardBody> - <ImageCardFooter> - <div className="mt-1 flex justify-between text-gray-500"> - <div className="my-auto"> - <Link - className="line-clamp-1 hover:text-black" - href={link.url} - target="_blank" - > - {parsedUrl.host} - </Link> - </div> - <div className="flex"> - {bookmark.favourited && ( - <Star - className="m-1 size-8 rounded p-1" - color="#ebb434" - fill="#ebb434" - /> - )} - <Link - className="my-auto block px-2" - href={`/dashboard/preview/${bookmark.id}`} - > - <Maximize2 size="20" /> - </Link> - <BookmarkOptions bookmark={bookmark} /> - </div> - </div> - </ImageCardFooter> - </ImageCardContent> - </ImageCard> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/TagList.tsx b/packages/web/app/dashboard/bookmarks/components/TagList.tsx deleted file mode 100644 index 6c9d2d22..00000000 --- a/packages/web/app/dashboard/bookmarks/components/TagList.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { badgeVariants } from "@/components/ui/badge"; -import Link from "next/link"; -import { Skeleton } from "@/components/ui/skeleton"; -import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import { cn } from "@/lib/utils"; - -export default function TagList({ - bookmark, - loading, -}: { - bookmark: ZBookmark; - loading?: boolean; -}) { - if (loading) { - return ( - <div className="flex w-full flex-col justify-end space-y-2 p-2"> - <Skeleton className="h-4 w-full" /> - <Skeleton className="h-4 w-full" /> - </div> - ); - } - return ( - <> - {bookmark.tags.map((t) => ( - <div key={t.id} className="flex h-full flex-col justify-end"> - <Link - className={cn( - badgeVariants({ variant: "outline" }), - "hover:bg-foreground hover:text-secondary text-nowrap", - )} - href={`/dashboard/tags/${t.name}`} - > - {t.name} - </Link> - </div> - ))} - </> - ); -} diff --git a/packages/web/app/dashboard/bookmarks/components/TagModal.tsx b/packages/web/app/dashboard/bookmarks/components/TagModal.tsx deleted file mode 100644 index 8c09d00e..00000000 --- a/packages/web/app/dashboard/bookmarks/components/TagModal.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { ActionButton } from "@/components/ui/action-button"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { toast } from "@/components/ui/use-toast"; -import { api } from "@/lib/trpc"; -import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import { ZAttachedByEnum } from "@hoarder/trpc/types/tags"; -import { cn } from "@/lib/utils"; -import { Sparkles, X } from "lucide-react"; -import { useState, KeyboardEvent, useEffect } from "react"; - -type EditableTag = { attachedBy: ZAttachedByEnum; id?: string; name: string }; - -function TagAddInput({ addTag }: { addTag: (tag: string) => void }) { - const onKeyUp = (e: KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter") { - addTag(e.currentTarget.value); - e.currentTarget.value = ""; - } - }; - return ( - <Input - onKeyUp={onKeyUp} - className="h-8 w-full border-none focus-visible:ring-0 focus-visible:ring-offset-0" - /> - ); -} - -function TagPill({ - tag, - deleteCB, -}: { - tag: { attachedBy: ZAttachedByEnum; id?: string; name: string }; - deleteCB: () => void; -}) { - const isAttachedByAI = tag.attachedBy == "ai"; - return ( - <div - className={cn( - "flex min-h-8 space-x-1 rounded px-2", - isAttachedByAI - ? "bg-gradient-to-tr from-purple-500 to-purple-400 text-white" - : "bg-gray-200", - )} - > - {isAttachedByAI && <Sparkles className="m-auto size-4" />} - <p className="m-auto">{tag.name}</p> - <button className="m-auto size-4" onClick={deleteCB}> - <X className="size-4" /> - </button> - </div> - ); -} - -function TagEditor({ - tags, - setTags, -}: { - tags: Map<string, EditableTag>; - setTags: ( - cb: (m: Map<string, EditableTag>) => Map<string, EditableTag>, - ) => void; -}) { - return ( - <div className="mt-4 flex flex-wrap gap-2 rounded border p-2"> - {[...tags.values()].map((t) => ( - <TagPill - key={t.name} - tag={t} - deleteCB={() => - setTags((m) => { - const newMap = new Map(m); - newMap.delete(t.name); - return newMap; - }) - } - /> - ))} - <div className="flex-1"> - <TagAddInput - addTag={(val) => { - setTags((m) => { - if (m.has(val)) { - // Tag already exists - // Do nothing - return m; - } - const newMap = new Map(m); - newMap.set(val, { attachedBy: "human", name: val }); - return newMap; - }); - }} - /> - </div> - </div> - ); -} - -export default function TagModal({ - bookmark, - open, - setOpen, -}: { - bookmark: ZBookmark; - open: boolean; - setOpen: (open: boolean) => void; -}) { - const [tags, setTags] = useState<Map<string, EditableTag>>(new Map()); - useEffect(() => { - const m = new Map<string, EditableTag>(); - for (const t of bookmark.tags) { - m.set(t.name, { attachedBy: t.attachedBy, id: t.id, name: t.name }); - } - setTags(m); - }, [bookmark.tags]); - - const bookmarkInvalidationFunction = - api.useUtils().bookmarks.getBookmark.invalidate; - - const { mutate, isPending } = api.bookmarks.updateTags.useMutation({ - onSuccess: () => { - toast({ - description: "Tags has been updated!", - }); - bookmarkInvalidationFunction({ bookmarkId: bookmark.id }); - }, - onError: () => { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - }, - }); - - const onSaveButton = () => { - const exitingTags = new Set(bookmark.tags.map((t) => t.name)); - - const attach = []; - const detach = []; - for (const t of tags.values()) { - if (!exitingTags.has(t.name)) { - attach.push({ tag: t.name }); - } - } - for (const t of bookmark.tags) { - if (!tags.has(t.name)) { - detach.push({ tagId: t.id }); - } - } - mutate({ - bookmarkId: bookmark.id, - attach, - detach, - }); - }; - - return ( - <Dialog open={open} onOpenChange={setOpen}> - <DialogContent> - <DialogHeader> - <DialogTitle>Edit Tags</DialogTitle> - </DialogHeader> - <TagEditor tags={tags} setTags={setTags} /> - <DialogFooter className="sm:justify-end"> - <DialogClose asChild> - <Button type="button" variant="secondary"> - Close - </Button> - </DialogClose> - <ActionButton - type="button" - loading={isPending} - onClick={onSaveButton} - > - Save - </ActionButton> - </DialogFooter> - </DialogContent> - </Dialog> - ); -} - -export function useTagModel(bookmark: ZBookmark) { - const [open, setOpen] = useState(false); - - return { - open, - setOpen, - content: ( - <TagModal - key={bookmark.id} - bookmark={bookmark} - open={open} - setOpen={setOpen} - /> - ), - }; -} diff --git a/packages/web/app/dashboard/bookmarks/components/TextCard.tsx b/packages/web/app/dashboard/bookmarks/components/TextCard.tsx deleted file mode 100644 index 2565e69d..00000000 --- a/packages/web/app/dashboard/bookmarks/components/TextCard.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; - -import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import BookmarkOptions from "./BookmarkOptions"; -import { api } from "@/lib/trpc"; -import { Maximize2, Star } from "lucide-react"; -import { cn } from "@/lib/utils"; -import TagList from "./TagList"; -import Markdown from "react-markdown"; -import { useState } from "react"; -import { BookmarkedTextViewer } from "./BookmarkedTextViewer"; -import Link from "next/link"; -import { isBookmarkStillTagging } from "@/lib/bookmarkUtils"; - -export default function TextCard({ - bookmark: initialData, - className, -}: { - bookmark: ZBookmark; - className?: string; -}) { - const { data: bookmark } = api.bookmarks.getBookmark.useQuery( - { - bookmarkId: initialData.id, - }, - { - initialData, - refetchInterval: (query) => { - const data = query.state.data; - if (!data) { - return false; - } - if (isBookmarkStillTagging(data)) { - return 1000; - } - return false; - }, - }, - ); - const [previewModalOpen, setPreviewModalOpen] = useState(false); - const bookmarkedText = bookmark.content; - if (bookmarkedText.type != "text") { - throw new Error("Unexpected bookmark type"); - } - - return ( - <> - <BookmarkedTextViewer - content={bookmarkedText.text} - open={previewModalOpen} - setOpen={setPreviewModalOpen} - /> - <div - className={cn( - className, - cn( - "flex h-min max-h-96 flex-col gap-y-1 overflow-hidden rounded-lg p-2 shadow-md", - ), - )} - > - <Markdown className="prose grow overflow-hidden"> - {bookmarkedText.text} - </Markdown> - <div className="mt-4 flex flex-none flex-wrap gap-1 overflow-hidden"> - <TagList - bookmark={bookmark} - loading={isBookmarkStillTagging(bookmark)} - /> - </div> - <div className="flex w-full justify-between"> - <div /> - <div className="flex gap-0 text-gray-500"> - <div> - {bookmark.favourited && ( - <Star - className="my-1 size-8 rounded p-1" - color="#ebb434" - fill="#ebb434" - /> - )} - </div> - <Link - className="my-auto block px-2" - href={`/dashboard/preview/${bookmark.id}`} - > - <Maximize2 size="20" /> - </Link> - <BookmarkOptions bookmark={bookmark} /> - </div> - </div> - </div> - </> - ); -} |
