diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index d4fbcb58..76a05aef 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -201,7 +201,7 @@ function LinkCard({ bookmark }: { bookmark: ZBookmark }) { className="line-clamp-2 text-xl font-bold" onPress={() => WebBrowser.openBrowserAsync(url)} > - {bookmark.content.title ?? parsedUrl.host} + {bookmark.title ?? bookmark.content.title ?? parsedUrl.host} @@ -220,6 +220,9 @@ function TextCard({ bookmark }: { bookmark: ZBookmark }) { } return ( + {bookmark.title && ( + {bookmark.title} + )} {bookmark.content.text} @@ -238,6 +241,7 @@ function AssetCard({ bookmark }: { bookmark: ZBookmark }) { if (bookmark.content.type !== "asset") { throw new Error("Wrong content type rendered"); } + const title = bookmark.title ?? bookmark.content.fileName; return ( @@ -251,6 +255,9 @@ function AssetCard({ bookmark }: { bookmark: ZBookmark }) { className="h-56 min-h-56 w-full object-cover" /> + {title && ( + {title} + )} diff --git a/apps/web/components/dashboard/bookmarks/AssetCard.tsx b/apps/web/components/dashboard/bookmarks/AssetCard.tsx index 3bda1ee8..ea0317aa 100644 --- a/apps/web/components/dashboard/bookmarks/AssetCard.tsx +++ b/apps/web/components/dashboard/bookmarks/AssetCard.tsx @@ -80,7 +80,7 @@ export default function AssetCard({ return ( - {link?.title ?? parsedUrl.host} + {bookmark.title ?? link?.title ?? parsedUrl.host} ); } diff --git a/apps/web/components/dashboard/bookmarks/TextCard.tsx b/apps/web/components/dashboard/bookmarks/TextCard.tsx index c715c8ab..e24108d2 100644 --- a/apps/web/components/dashboard/bookmarks/TextCard.tsx +++ b/apps/web/components/dashboard/bookmarks/TextCard.tsx @@ -51,6 +51,7 @@ export default function TextCard({ setOpen={setPreviewModalOpen} /> {bookmarkedText.text} diff --git a/apps/web/components/dashboard/preview/BookmarkPreview.tsx b/apps/web/components/dashboard/preview/BookmarkPreview.tsx index 73e49376..93f14c64 100644 --- a/apps/web/components/dashboard/preview/BookmarkPreview.tsx +++ b/apps/web/components/dashboard/preview/BookmarkPreview.tsx @@ -24,6 +24,7 @@ import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; import ActionBar from "./ActionBar"; import { AssetContentSection } from "./AssetContentSection"; +import { EditableTitle } from "./EditableTitle"; import { NoteEditor } from "./NoteEditor"; import { TextContentSection } from "./TextContentSection"; @@ -62,37 +63,6 @@ function CreationTime({ createdAt }: { createdAt: Date }) { ); } -function LinkHeader({ bookmark }: { bookmark: ZBookmark }) { - if (bookmark.content.type !== "link") { - throw new Error("Unexpected content type"); - } - - const title = bookmark.content.title ?? bookmark.content.url; - - return ( -
- - -

{title}

-
- - - {title} - - -
- - View Original - - - -
- ); -} - export default function BookmarkPreview({ initialData, }: { @@ -131,17 +101,26 @@ export default function BookmarkPreview({ } } - const linkHeader = bookmark.content.type == "link" && ( - - ); - return (
{isBookmarkStillCrawling(bookmark) ? : content}
- {linkHeader} +
+ + {bookmark.content.type == "link" && ( + + View Original + + + )} + +
+

Tags

diff --git a/apps/web/components/dashboard/preview/EditableTitle.tsx b/apps/web/components/dashboard/preview/EditableTitle.tsx new file mode 100644 index 00000000..1500212d --- /dev/null +++ b/apps/web/components/dashboard/preview/EditableTitle.tsx @@ -0,0 +1,165 @@ +import { useEffect, useRef, useState } from "react"; +import { ActionButtonWithTooltip } from "@/components/ui/action-button"; +import { ButtonWithTooltip } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { toast } from "@/components/ui/use-toast"; +import { Check, Pencil, X } from "lucide-react"; + +import { useUpdateBookmark } from "@hoarder/shared-react/hooks/bookmarks"; +import { ZBookmark } from "@hoarder/trpc/types/bookmarks"; + +interface Props { + bookmarkId: string; + originalTitle: string | null; + setEditable: (editable: boolean) => void; +} + +function EditMode({ bookmarkId, originalTitle, setEditable }: Props) { + const ref = useRef(null); + + const { mutate: updateBookmark, isPending } = useUpdateBookmark({ + onSuccess: () => { + toast({ + description: "Title updated!", + }); + }, + }); + + useEffect(() => { + if (ref.current) { + ref.current.focus(); + ref.current.textContent = originalTitle; + } + }, [ref]); + + const onSave = () => { + let toSave: string | null = ref.current?.textContent ?? null; + if (originalTitle == toSave) { + // Nothing to do here + return; + } + if (toSave == "") { + toSave = null; + } + updateBookmark({ + bookmarkId, + title: toSave, + }); + setEditable(false); + }; + + return ( +
+
{ + if (e.key === "Enter") { + e.preventDefault(); + } + }} + /> + onSave()} + > + + + { + setEditable(false); + }} + > + + +
+ ); +} + +function ViewMode({ originalTitle, setEditable }: Props) { + return ( + +
+ + {originalTitle ? ( +

{originalTitle}

+ ) : ( +

Untitled

+ )} +
+ { + setEditable(true); + }} + > + + +
+ + {originalTitle && ( + + {originalTitle} + + )} + +
+ ); +} + +export function EditableTitle({ bookmark }: { bookmark: ZBookmark }) { + const [editable, setEditable] = useState(false); + + let title: string | null = null; + switch (bookmark.content.type) { + case "link": + title = bookmark.content.title ?? bookmark.content.url; + break; + case "text": + title = null; + break; + case "asset": + title = bookmark.content.fileName ?? null; + break; + } + + title = bookmark.title ?? title; + if (title == "") { + title = null; + } + + return editable ? ( + + ) : ( + + ); +} diff --git a/apps/web/components/ui/action-button.tsx b/apps/web/components/ui/action-button.tsx index e9cdc3c9..2ac361f5 100644 --- a/apps/web/components/ui/action-button.tsx +++ b/apps/web/components/ui/action-button.tsx @@ -4,15 +4,20 @@ import { useClientConfig } from "@/lib/clientConfig"; import type { ButtonProps } from "./button"; import { Button } from "./button"; import LoadingSpinner from "./spinner"; +import { + Tooltip, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from "./tooltip"; -const ActionButton = React.forwardRef< - HTMLButtonElement, - ButtonProps & { - loading: boolean; - spinner?: React.ReactNode; - ignoreDemoMode?: boolean; - } ->( +interface ActionButtonProps extends ButtonProps { + loading: boolean; + spinner?: React.ReactNode; + ignoreDemoMode?: boolean; +} + +const ActionButton = React.forwardRef( ( { children, loading, spinner, disabled, ignoreDemoMode = false, ...props }, ref, @@ -35,4 +40,21 @@ const ActionButton = React.forwardRef< ); ActionButton.displayName = "ActionButton"; -export { ActionButton }; +const ActionButtonWithTooltip = React.forwardRef< + HTMLButtonElement, + ActionButtonProps & { tooltip: string; delayDuration?: number } +>(({ tooltip, delayDuration, ...props }, ref) => { + return ( + + + + + + {tooltip} + + + ); +}); +ActionButtonWithTooltip.displayName = "ActionButtonWithTooltip"; + +export { ActionButton, ActionButtonWithTooltip }; diff --git a/apps/web/components/ui/button.tsx b/apps/web/components/ui/button.tsx index 40794eb2..2d6dee6b 100644 --- a/apps/web/components/ui/button.tsx +++ b/apps/web/components/ui/button.tsx @@ -4,6 +4,13 @@ import { cn } from "@/lib/utils"; import { Slot } from "@radix-ui/react-slot"; import { cva } from "class-variance-authority"; +import { + Tooltip, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from "./tooltip"; + const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { @@ -24,6 +31,7 @@ const buttonVariants = cva( link: "text-primary underline-offset-4 hover:underline", }, size: { + none: "", default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", @@ -57,4 +65,21 @@ const Button = React.forwardRef( ); Button.displayName = "Button"; -export { Button, buttonVariants }; +const ButtonWithTooltip = React.forwardRef< + HTMLButtonElement, + ButtonProps & { tooltip: string; delayDuration?: number } +>(({ tooltip, delayDuration, ...props }, ref) => { + return ( + + +