diff options
Diffstat (limited to 'apps/web/components/dashboard/preview')
5 files changed, 102 insertions, 47 deletions
diff --git a/apps/web/components/dashboard/preview/AssetContentSection.tsx b/apps/web/components/dashboard/preview/AssetContentSection.tsx index fd299320..5cab86bd 100644 --- a/apps/web/components/dashboard/preview/AssetContentSection.tsx +++ b/apps/web/components/dashboard/preview/AssetContentSection.tsx @@ -11,8 +11,8 @@ import { } from "@/components/ui/select"; import { useTranslation } from "@/lib/i18n/client"; -import { getAssetUrl } from "@karakeep/shared-react/utils/assetUtils"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; +import { getAssetUrl } from "@karakeep/shared/utils/assetUtils"; // 20 MB const BIG_FILE_SIZE = 20 * 1024 * 1024; diff --git a/apps/web/components/dashboard/preview/AttachmentBox.tsx b/apps/web/components/dashboard/preview/AttachmentBox.tsx index 15acd799..674f151c 100644 --- a/apps/web/components/dashboard/preview/AttachmentBox.tsx +++ b/apps/web/components/dashboard/preview/AttachmentBox.tsx @@ -19,8 +19,8 @@ import { useDetachBookmarkAsset, useReplaceBookmarkAsset, } from "@karakeep/shared-react/hooks/assets"; -import { getAssetUrl } from "@karakeep/shared-react/utils/assetUtils"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; +import { getAssetUrl } from "@karakeep/shared/utils/assetUtils"; import { humanFriendlyNameForAssertType, isAllowedToAttachAsset, diff --git a/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx b/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx index a3b34f9a..dc446112 100644 --- a/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx +++ b/apps/web/components/dashboard/preview/BookmarkHtmlHighlighter.tsx @@ -19,6 +19,7 @@ interface ColorPickerMenuProps { onDelete?: () => void; selectedHighlight: Highlight | null; onClose: () => void; + isMobile: boolean; } const ColorPickerMenu: React.FC<ColorPickerMenuProps> = ({ @@ -27,6 +28,7 @@ const ColorPickerMenu: React.FC<ColorPickerMenuProps> = ({ onDelete, selectedHighlight, onClose, + isMobile, }) => { return ( <Popover @@ -44,7 +46,10 @@ const ColorPickerMenu: React.FC<ColorPickerMenuProps> = ({ top: position?.y, }} /> - <PopoverContent side="top" className="flex w-fit items-center gap-1 p-2"> + <PopoverContent + side={isMobile ? "bottom" : "top"} + className="flex w-fit items-center gap-1 p-2" + > {SUPPORTED_HIGHLIGHT_COLORS.map((color) => ( <Button size="none" @@ -113,6 +118,11 @@ function BookmarkHTMLHighlighter({ const [selectedHighlight, setSelectedHighlight] = useState<Highlight | null>( null, ); + const isMobile = useState( + () => + typeof window !== "undefined" && + window.matchMedia("(pointer: coarse)").matches, + )[0]; // Apply existing highlights when component mounts or highlights change useEffect(() => { @@ -160,7 +170,7 @@ function BookmarkHTMLHighlighter({ window.getSelection()?.addRange(newRange); }, [pendingHighlight, contentRef]); - const handleMouseUp = (e: React.MouseEvent) => { + const handlePointerUp = (e: React.PointerEvent) => { const selection = window.getSelection(); // Check if we clicked on an existing highlight @@ -192,11 +202,11 @@ function BookmarkHTMLHighlighter({ return; } - // Position the menu above the selection + // Position the menu based on device type const rect = range.getBoundingClientRect(); setMenuPosition({ - x: rect.left + rect.width / 2, // Center the menu - y: rect.top, + x: rect.left + rect.width / 2, // Center the menu horizontally + y: isMobile ? rect.bottom : rect.top, // Position below on mobile, above otherwise }); // Store the highlight for later use @@ -333,7 +343,7 @@ function BookmarkHTMLHighlighter({ role="presentation" ref={contentRef} dangerouslySetInnerHTML={{ __html: htmlContent }} - onMouseUp={handleMouseUp} + onPointerUp={handlePointerUp} className={className} /> <ColorPickerMenu @@ -342,6 +352,7 @@ function BookmarkHTMLHighlighter({ onDelete={handleDelete} selectedHighlight={selectedHighlight} onClose={closeColorPicker} + isMobile={isMobile} /> </div> ); diff --git a/apps/web/components/dashboard/preview/BookmarkPreview.tsx b/apps/web/components/dashboard/preview/BookmarkPreview.tsx index df09f687..e213b9cb 100644 --- a/apps/web/components/dashboard/preview/BookmarkPreview.tsx +++ b/apps/web/components/dashboard/preview/BookmarkPreview.tsx @@ -1,11 +1,12 @@ "use client"; -import React from "react"; +import { useState } from "react"; import Link from "next/link"; import { BookmarkTagsEditor } from "@/components/dashboard/bookmarks/BookmarkTagsEditor"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, @@ -17,13 +18,13 @@ import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { CalendarDays, ExternalLink } from "lucide-react"; +import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; import { getBookmarkTitle, getSourceUrl, isBookmarkStillCrawling, isBookmarkStillLoading, -} from "@karakeep/shared-react/utils/bookmarkUtils"; -import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; +} from "@karakeep/shared/utils/bookmarkUtils"; import SummarizeBookmarkArea from "../bookmarks/SummarizeBookmarkArea"; import ActionBar from "./ActionBar"; @@ -68,6 +69,8 @@ export default function BookmarkPreview({ initialData?: ZBookmark; }) { const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState<string>("content"); + const { data: bookmark } = api.bookmarks.getBookmark.useQuery( { bookmarkId, @@ -111,45 +114,86 @@ export default function BookmarkPreview({ const sourceUrl = getSourceUrl(bookmark); const title = getBookmarkTitle(bookmark); - return ( - <div className="grid h-full grid-rows-3 gap-2 overflow-hidden bg-background lg:grid-cols-3 lg:grid-rows-none"> - <div className="row-span-2 h-full w-full overflow-auto p-2 md:col-span-2 lg:row-auto"> - {isBookmarkStillCrawling(bookmark) ? <ContentLoading /> : content} - </div> - <div className="row-span-1 flex flex-col gap-4 overflow-auto bg-accent p-4 md:col-span-2 lg:col-span-1 lg:row-auto"> - <div className="flex w-full flex-col items-center justify-center gap-y-2"> - <div className="flex w-full items-center justify-center gap-2"> - <p className="line-clamp-2 text-ellipsis break-words text-lg"> - {title === undefined || title === "" ? "Untitled" : title} - </p> - </div> - {sourceUrl && ( - <Link - href={sourceUrl} - target="_blank" - className="flex items-center gap-2 text-gray-400" - > - <span>{t("preview.view_original")}</span> - <ExternalLink /> - </Link> - )} - <Separator /> + // Common content for both layouts + const contentSection = isBookmarkStillCrawling(bookmark) ? ( + <ContentLoading /> + ) : ( + content + ); + + const detailsSection = ( + <div className="flex flex-col gap-4"> + <div className="flex w-full flex-col items-center justify-center gap-y-2"> + <div className="flex w-full items-center justify-center gap-2"> + <p className="line-clamp-2 text-ellipsis break-words text-lg"> + {title === undefined || title === "" ? "Untitled" : title} + </p> </div> + {sourceUrl && ( + <Link + href={sourceUrl} + target="_blank" + className="flex items-center gap-2 text-gray-400" + > + <span>{t("preview.view_original")}</span> + <ExternalLink /> + </Link> + )} + <Separator /> + </div> + <CreationTime createdAt={bookmark.createdAt} /> + <SummarizeBookmarkArea bookmark={bookmark} /> + <div className="flex items-center gap-4"> + <p className="text-sm text-gray-400">{t("common.tags")}</p> + <BookmarkTagsEditor bookmark={bookmark} /> + </div> + <div className="flex gap-4"> + <p className="pt-2 text-sm text-gray-400">{t("common.note")}</p> + <NoteEditor bookmark={bookmark} /> + </div> + <AttachmentBox bookmark={bookmark} /> + <HighlightsBox bookmarkId={bookmark.id} /> + <ActionBar bookmark={bookmark} /> + </div> + ); - <CreationTime createdAt={bookmark.createdAt} /> - <SummarizeBookmarkArea bookmark={bookmark} /> - <div className="flex items-center gap-4"> - <p className="text-sm text-gray-400">{t("common.tags")}</p> - <BookmarkTagsEditor bookmark={bookmark} /> + return ( + <> + {/* Render original layout for wide screens */} + <div className="hidden h-full grid-cols-3 overflow-hidden bg-background lg:grid"> + <div className="col-span-2 h-full w-full overflow-auto p-2"> + {contentSection} </div> - <div className="flex gap-4"> - <p className="pt-2 text-sm text-gray-400">{t("common.note")}</p> - <NoteEditor bookmark={bookmark} /> + <div className="flex flex-col gap-4 overflow-auto bg-accent p-4"> + {detailsSection} </div> - <AttachmentBox bookmark={bookmark} /> - <HighlightsBox bookmarkId={bookmark.id} /> - <ActionBar bookmark={bookmark} /> </div> - </div> + + {/* Render tabbed layout for narrow/vertical screens */} + <Tabs + value={activeTab} + onValueChange={setActiveTab} + className="flex h-full w-full flex-col overflow-hidden lg:hidden" + > + <TabsList + className={`sticky top-0 z-10 grid h-auto w-full grid-cols-2`} + > + <TabsTrigger value="content">{t("preview.tabs.content")}</TabsTrigger> + <TabsTrigger value="details">{t("preview.tabs.details")}</TabsTrigger> + </TabsList> + <TabsContent + value="content" + className="h-full flex-1 overflow-hidden overflow-y-auto bg-background p-2 data-[state=inactive]:hidden" + > + {contentSection} + </TabsContent> + <TabsContent + value="details" + className="h-full overflow-y-auto bg-accent p-4 data-[state=inactive]:hidden" + > + {detailsSection} + </TabsContent> + </Tabs> + </> ); } diff --git a/apps/web/components/dashboard/preview/TextContentSection.tsx b/apps/web/components/dashboard/preview/TextContentSection.tsx index 0c1aae67..4e33bb92 100644 --- a/apps/web/components/dashboard/preview/TextContentSection.tsx +++ b/apps/web/components/dashboard/preview/TextContentSection.tsx @@ -3,8 +3,8 @@ import { BookmarkMarkdownComponent } from "@/components/dashboard/bookmarks/Book import { ScrollArea } from "@radix-ui/react-scroll-area"; import type { ZBookmarkTypeText } from "@karakeep/shared/types/bookmarks"; -import { getAssetUrl } from "@karakeep/shared-react/utils/assetUtils"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; +import { getAssetUrl } from "@karakeep/shared/utils/assetUtils"; export function TextContentSection({ bookmark }: { bookmark: ZBookmark }) { if (bookmark.content.type != BookmarkTypes.TEXT) { |
