aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx')
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx83
1 files changed, 80 insertions, 3 deletions
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx b/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx
index bf7fe2ad..f164b275 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx
@@ -2,10 +2,11 @@
import type { BookmarksLayoutTypes } from "@/lib/userLocalSettings/types";
import type { ReactNode } from "react";
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useSession } from "@/lib/auth/client";
+import { BOOKMARK_DRAG_MIME } from "@/lib/bookmark-drag";
import useBulkActionsStore from "@/lib/bulkActions";
import {
bookmarkLayoutSwitch,
@@ -14,14 +15,22 @@ import {
} from "@/lib/userLocalSettings/bookmarksLayout";
import { cn } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
-import { Check, Image as ImageIcon, NotebookPen } from "lucide-react";
+import {
+ Check,
+ GripVertical,
+ Image as ImageIcon,
+ NotebookPen,
+} from "lucide-react";
import { useTheme } from "next-themes";
import type { ZBookmark } from "@karakeep/shared/types/bookmarks";
import { useBookmarkListContext } from "@karakeep/shared-react/hooks/bookmark-list-context";
import { useTRPC } from "@karakeep/shared-react/trpc";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
-import { isBookmarkStillTagging } from "@karakeep/shared/utils/bookmarkUtils";
+import {
+ getBookmarkTitle,
+ isBookmarkStillTagging,
+} from "@karakeep/shared/utils/bookmarkUtils";
import { switchCase } from "@karakeep/shared/utils/switch";
import BookmarkActionBar from "./BookmarkActionBar";
@@ -155,6 +164,65 @@ function MultiBookmarkSelector({ bookmark }: { bookmark: ZBookmark }) {
);
}
+function DragHandle({
+ bookmark,
+ className,
+}: {
+ bookmark: ZBookmark;
+ className?: string;
+}) {
+ const { isBulkEditEnabled } = useBulkActionsStore();
+ const handleDragStart = useCallback(
+ (e: React.DragEvent) => {
+ e.stopPropagation();
+ e.dataTransfer.setData(BOOKMARK_DRAG_MIME, bookmark.id);
+ e.dataTransfer.effectAllowed = "copy";
+
+ // Create a small pill element as the drag preview
+ const pill = document.createElement("div");
+ const title = getBookmarkTitle(bookmark) ?? "Untitled";
+ pill.textContent =
+ title.length > 40 ? title.substring(0, 40) + "\u2026" : title;
+ Object.assign(pill.style, {
+ position: "fixed",
+ left: "-9999px",
+ top: "-9999px",
+ padding: "6px 12px",
+ borderRadius: "8px",
+ backgroundColor: "hsl(var(--card))",
+ border: "1px solid hsl(var(--border))",
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
+ fontSize: "13px",
+ fontFamily: "inherit",
+ color: "hsl(var(--foreground))",
+ maxWidth: "240px",
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ });
+ document.body.appendChild(pill);
+ e.dataTransfer.setDragImage(pill, 0, 0);
+ requestAnimationFrame(() => pill.remove());
+ },
+ [bookmark],
+ );
+
+ if (isBulkEditEnabled) return null;
+
+ return (
+ <div
+ draggable
+ onDragStart={handleDragStart}
+ className={cn(
+ "absolute z-40 cursor-grab rounded bg-background/70 p-0.5 opacity-0 shadow-sm transition-opacity duration-200 group-hover:opacity-100",
+ className,
+ )}
+ >
+ <GripVertical className="size-4 text-muted-foreground" />
+ </div>
+ );
+}
+
function ListView({
bookmark,
image,
@@ -180,6 +248,10 @@ function ListView({
>
<MultiBookmarkSelector bookmark={bookmark} />
<OwnerIndicator bookmark={bookmark} />
+ <DragHandle
+ bookmark={bookmark}
+ className="left-1 top-1/2 -translate-y-1/2"
+ />
<div className="flex size-32 items-center justify-center overflow-hidden">
{image("list", cn("size-32 rounded-lg", imgFitClass))}
</div>
@@ -240,6 +312,7 @@ function GridView({
>
<MultiBookmarkSelector bookmark={bookmark} />
<OwnerIndicator bookmark={bookmark} />
+ <DragHandle bookmark={bookmark} className="left-2 top-2" />
{img && <div className="h-56 w-full shrink-0 overflow-hidden">{img}</div>}
<div className="flex h-full flex-col justify-between gap-2 overflow-hidden p-2">
<div className="grow-1 flex flex-col gap-2 overflow-hidden">
@@ -278,6 +351,10 @@ function CompactView({ bookmark, title, footer, className }: Props) {
>
<MultiBookmarkSelector bookmark={bookmark} />
<OwnerIndicator bookmark={bookmark} />
+ <DragHandle
+ bookmark={bookmark}
+ className="left-0.5 top-1/2 -translate-y-1/2"
+ />
<div className="flex h-full justify-between gap-2 overflow-hidden p-2">
<div className="flex items-center gap-2">
{bookmark.content.type === BookmarkTypes.LINK &&