diff options
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx | 38 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/LinkCard.tsx | 20 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/TagModal.tsx | 160 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/TagsEditor.tsx | 133 | ||||
| -rw-r--r-- | apps/web/components/dashboard/bookmarks/TextCard.tsx | 23 | ||||
| -rw-r--r-- | apps/web/next.config.mjs | 2 |
6 files changed, 179 insertions, 197 deletions
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx b/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx new file mode 100644 index 00000000..0d98cc1f --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/BookmarkActionBar.tsx @@ -0,0 +1,38 @@ +import { Button } from "@/components/ui/button"; +import { DialogContent } from "@/components/ui/dialog"; +import { Dialog, DialogTrigger } from "@radix-ui/react-dialog"; +import { Maximize2, Star } from "lucide-react"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; + +import BookmarkOptions from "./BookmarkOptions"; +import BookmarkPreview from "./BookmarkPreview"; + +export default function BookmarkActionBar({ + bookmark, +}: { + bookmark: ZBookmark; +}) { + return ( + <div className="flex text-gray-500"> + {bookmark.favourited && ( + <Star + className="m-1 size-8 rounded p-1" + color="#ebb434" + fill="#ebb434" + /> + )} + <Dialog> + <DialogTrigger asChild> + <Button variant="ghost" className="my-auto block px-2"> + <Maximize2 size="20" /> + </Button> + </DialogTrigger> + <DialogContent className="h-[90%] max-w-[90%] overflow-hidden"> + <BookmarkPreview initialData={bookmark} /> + </DialogContent> + </Dialog> + <BookmarkOptions bookmark={bookmark} /> + </div> + ); +} diff --git a/apps/web/components/dashboard/bookmarks/LinkCard.tsx b/apps/web/components/dashboard/bookmarks/LinkCard.tsx index 808e6d91..20f4dd79 100644 --- a/apps/web/components/dashboard/bookmarks/LinkCard.tsx +++ b/apps/web/components/dashboard/bookmarks/LinkCard.tsx @@ -15,11 +15,10 @@ import { isBookmarkStillTagging, } from "@/lib/bookmarkUtils"; import { api } from "@/lib/trpc"; -import { Maximize2, Star } from "lucide-react"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import BookmarkOptions from "./BookmarkOptions"; +import BookmarkActionBar from "./BookmarkActionBar"; import TagList from "./TagList"; export default function LinkCard({ @@ -92,22 +91,7 @@ export default function LinkCard({ {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> + <BookmarkActionBar bookmark={bookmark} /> </div> </ImageCardFooter> </ImageCardContent> diff --git a/apps/web/components/dashboard/bookmarks/TagModal.tsx b/apps/web/components/dashboard/bookmarks/TagModal.tsx index 367e6e7d..6bc16a89 100644 --- a/apps/web/components/dashboard/bookmarks/TagModal.tsx +++ b/apps/web/components/dashboard/bookmarks/TagModal.tsx @@ -1,6 +1,4 @@ -import type { KeyboardEvent } from "react"; -import { useEffect, useState } from "react"; -import { ActionButton } from "@/components/ui/action-button"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -10,105 +8,10 @@ import { 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 { cn } from "@/lib/utils"; -import { Sparkles, X } from "lucide-react"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import type { ZAttachedByEnum } from "@hoarder/trpc/types/tags"; -interface 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> - ); -} +import { TagsEditor } from "./TagsEditor"; export default function TagModal({ bookmark, @@ -119,76 +22,19 @@ export default function TagModal({ 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} /> + <TagsEditor bookmark={bookmark} /> <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> diff --git a/apps/web/components/dashboard/bookmarks/TagsEditor.tsx b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx new file mode 100644 index 00000000..8bfbce19 --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx @@ -0,0 +1,133 @@ +import type { KeyboardEvent } from "react"; +import { useEffect, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { toast } from "@/components/ui/use-toast"; +import { api } from "@/lib/trpc"; +import { cn } from "@/lib/utils"; +import { Sparkles, X } from "lucide-react"; + +import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import type { ZAttachedByEnum } from "@hoarder/trpc/types/tags"; + +interface 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 bg-transparent 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> + ); +} + +export function TagsEditor({ bookmark }: { bookmark: ZBookmark }) { + 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 } = 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.", + }); + }, + }); + + return ( + <div className="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); + if (t.id) { + mutate({ + bookmarkId: bookmark.id, + attach: [], + detach: [{ tagId: t.id }], + }); + } + 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 }); + mutate({ + bookmarkId: bookmark.id, + attach: [{ tag: val }], + detach: [], + }); + return newMap; + }); + }} + /> + </div> + </div> + ); +} diff --git a/apps/web/components/dashboard/bookmarks/TextCard.tsx b/apps/web/components/dashboard/bookmarks/TextCard.tsx index 5028c1bb..75733063 100644 --- a/apps/web/components/dashboard/bookmarks/TextCard.tsx +++ b/apps/web/components/dashboard/bookmarks/TextCard.tsx @@ -1,17 +1,15 @@ "use client"; import { useState } from "react"; -import Link from "next/link"; import { isBookmarkStillTagging } from "@/lib/bookmarkUtils"; import { api } from "@/lib/trpc"; import { cn } from "@/lib/utils"; -import { Maximize2, Star } from "lucide-react"; import Markdown from "react-markdown"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; +import BookmarkActionBar from "./BookmarkActionBar"; import { BookmarkedTextViewer } from "./BookmarkedTextViewer"; -import BookmarkOptions from "./BookmarkOptions"; import TagList from "./TagList"; export default function TextCard({ @@ -71,24 +69,7 @@ export default function TextCard({ </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> + <BookmarkActionBar bookmark={bookmark} /> </div> </div> </> diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index b661bd5c..a8d60f07 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -39,7 +39,7 @@ const nextConfig = withPWA({ ]; }, - transpilePackages: ["@hoarder/shared", "@hoarder/db", "@hoarder/trpc"], + // transpilePackages: ["@hoarder/shared", "@hoarder/db", "@hoarder/trpc"], /** We already do linting and typechecking as separate tasks in CI */ eslint: { ignoreDuringBuilds: true }, |
