From 3fe20dda157cbae282f55d6afb8e8f99e795945a Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Mon, 26 Feb 2024 12:47:36 +0000 Subject: feature: Add support for adding/removing tags --- .../bookmarks/components/BookmarkOptions.tsx | 115 +++++++----- .../dashboard/bookmarks/components/TagModal.tsx | 207 +++++++++++++++++++++ 2 files changed, 273 insertions(+), 49 deletions(-) create mode 100644 packages/web/app/dashboard/bookmarks/components/TagModal.tsx (limited to 'packages/web/app') diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx index a72478c1..6c1133fb 100644 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx +++ b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx @@ -10,12 +10,22 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Archive, MoreHorizontal, RotateCw, Star, Trash2 } from "lucide-react"; +import { + Archive, + MoreHorizontal, + RotateCw, + Star, + Tags, + Trash2, +} from "lucide-react"; +import { useTagModel } from "./TagModal"; export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); const linkId = bookmark.id; + const [_, setTagModalIsOpen, tagModal] = useTagModel(bookmark); + const invalidateBookmarksCache = api.useUtils().bookmarks.invalidate; const onError = () => { @@ -59,53 +69,60 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { }); return ( - - - - - - - updateBookmarkMutator.mutate({ - bookmarkId: linkId, - favourited: !bookmark.favourited, - }) - } - > - - {bookmark.favourited ? "Un-favourite" : "Favourite"} - - - updateBookmarkMutator.mutate({ - bookmarkId: linkId, - archived: !bookmark.archived, - }) - } - > - - {bookmark.archived ? "Un-archive" : "Archive"} - - - crawlBookmarkMutator.mutate({ bookmarkId: bookmark.id }) - } - > - - Refresh - - - deleteBookmarkMutator.mutate({ bookmarkId: bookmark.id }) - } - > - - Delete - - - + <> + {tagModal} + + + + + + + updateBookmarkMutator.mutate({ + bookmarkId: linkId, + favourited: !bookmark.favourited, + }) + } + > + + {bookmark.favourited ? "Un-favourite" : "Favourite"} + + + updateBookmarkMutator.mutate({ + bookmarkId: linkId, + archived: !bookmark.archived, + }) + } + > + + {bookmark.archived ? "Un-archive" : "Archive"} + + setTagModalIsOpen(true)}> + + Edit Tags + + + crawlBookmarkMutator.mutate({ bookmarkId: bookmark.id }) + } + > + + Refresh + + + deleteBookmarkMutator.mutate({ bookmarkId: bookmark.id }) + } + > + + Delete + + + + ); } diff --git a/packages/web/app/dashboard/bookmarks/components/TagModal.tsx b/packages/web/app/dashboard/bookmarks/components/TagModal.tsx new file mode 100644 index 00000000..c1618541 --- /dev/null +++ b/packages/web/app/dashboard/bookmarks/components/TagModal.tsx @@ -0,0 +1,207 @@ +import { ActionButton } from "@/components/ui/action-button"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + 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 "@/lib/types/api/bookmarks"; +import { ZAttachedByEnum } from "@/lib/types/api/tags"; +import { cn } from "@/lib/utils"; +import { Sparkles, X } from "lucide-react"; +import { useState, KeyboardEvent } from "react"; + +type EditableTag = { attachedBy: ZAttachedByEnum; id?: string; name: string }; + +function TagAddInput({ addTag }: { addTag: (tag: string) => void }) { + const onKeyUp = (e: KeyboardEvent) => { + if (e.key === "Enter") { + addTag(e.currentTarget.value); + e.currentTarget.value = ""; + } + }; + return ( + + ); +} + +function TagPill({ + tag, + deleteCB, +}: { + tag: { attachedBy: ZAttachedByEnum; id?: string; name: string }; + deleteCB: () => void; +}) { + const isAttachedByAI = tag.attachedBy == "ai"; + return ( +
+ {isAttachedByAI && } +

{tag.name}

+ +
+ ); +} + +function TagEditor({ + tags, + setTags, +}: { + tags: Map; + setTags: ( + cb: (m: Map) => Map, + ) => void; +}) { + return ( +
+ {[...tags.values()].map((t) => ( + + setTags((m) => { + const newMap = new Map(m); + newMap.delete(t.name); + return newMap; + }) + } + /> + ))} +
+ { + 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; + }); + }} + /> +
+
+ ); +} + +export default function TagModal({ + bookmark, + open, + setOpen, +}: { + bookmark: ZBookmark; + open: boolean; + setOpen: (open: boolean) => void; +}) { + const [tags, setTags] = useState(() => { + const m = new Map(); + for (const t of bookmark.tags) { + m.set(t.name, { attachedBy: t.attachedBy, id: t.id, name: t.name }); + } + return m; + }); + + const bookmarkInvalidationFunction = + api.useUtils().bookmarks.getBookmark.invalidate; + + const { mutate, isPending } = api.bookmarks.updateTags.useMutation({ + onSuccess: () => { + toast({ + description: "Tags has been updated!", + }); + bookmarkInvalidationFunction({ id: 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(t.id); + } + } + mutate({ + bookmarkId: bookmark.id, + attach, + detach, + }); + }; + + return ( + + + + Edit Tags + + + + + + + + + + Save + + + + + ); +} + +export function useTagModel(bookmark: ZBookmark) { + const [open, setOpen] = useState(false); + + return [ + open, + setOpen, + , + ] as const; +} -- cgit v1.2.3-70-g09d2