"use client";
import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import {
ActionButton,
ActionButtonWithTooltip,
} from "@/components/ui/action-button";
import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
import { useToast } from "@/components/ui/use-toast";
import useBulkActionsStore from "@/lib/bulkActions";
import { useTranslation } from "@/lib/i18n/client";
import {
CheckCheck,
FileDown,
Hash,
Link,
List,
Pencil,
RotateCw,
Trash2,
X,
} from "lucide-react";
import {
useDeleteBookmark,
useRecrawlBookmark,
useUpdateBookmark,
} from "@karakeep/shared-react/hooks/bookmarks";
import { limitConcurrency } from "@karakeep/shared/concurrency";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
import BulkManageListsModal from "./bookmarks/BulkManageListsModal";
import BulkTagModal from "./bookmarks/BulkTagModal";
import { ArchivedActionIcon, FavouritedActionIcon } from "./bookmarks/icons";
const MAX_CONCURRENT_BULK_ACTIONS = 50;
export default function BulkBookmarksAction() {
const { t } = useTranslation();
const { selectedBookmarks, isBulkEditEnabled } = useBulkActionsStore();
const setIsBulkEditEnabled = useBulkActionsStore(
(state) => state.setIsBulkEditEnabled,
);
const selectAllBookmarks = useBulkActionsStore((state) => state.selectAll);
const unSelectAllBookmarks = useBulkActionsStore(
(state) => state.unSelectAll,
);
const isEverythingSelected = useBulkActionsStore(
(state) => state.isEverythingSelected,
);
const { toast } = useToast();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [manageListsModal, setManageListsModalOpen] = useState(false);
const [bulkTagModal, setBulkTagModalOpen] = useState(false);
const pathname = usePathname();
const [currentPathname, setCurrentPathname] = useState("");
// Reset bulk edit state when the route changes
useEffect(() => {
if (pathname !== currentPathname) {
setCurrentPathname(pathname);
setIsBulkEditEnabled(false);
}
}, [pathname, currentPathname]);
const onError = () => {
toast({
variant: "destructive",
title: "Something went wrong",
description: "There was a problem with your request.",
});
};
const deleteBookmarkMutator = useDeleteBookmark({
onSuccess: () => {
setIsBulkEditEnabled(false);
},
onError,
});
const updateBookmarkMutator = useUpdateBookmark({
onSuccess: () => {
setIsBulkEditEnabled(false);
},
onError,
});
const recrawlBookmarkMutator = useRecrawlBookmark({
onSuccess: () => {
setIsBulkEditEnabled(false);
},
onError,
});
interface UpdateBookmarkProps {
favourited?: boolean;
archived?: boolean;
}
const recrawlBookmarks = async (archiveFullPage: boolean) => {
const links = selectedBookmarks.filter(
(item) => item.content.type === BookmarkTypes.LINK,
);
await Promise.all(
limitConcurrency(
links.map(
(item) => () =>
recrawlBookmarkMutator.mutateAsync({
bookmarkId: item.id,
archiveFullPage,
}),
),
MAX_CONCURRENT_BULK_ACTIONS,
),
);
toast({
description: `${links.length} bookmarks will be ${archiveFullPage ? "re-crawled and archived!" : "refreshed!"}`,
});
};
function isClipboardAvailable() {
if (typeof window === "undefined") {
return false;
}
return window && window.navigator && window.navigator.clipboard;
}
const copyLinks = async () => {
if (!isClipboardAvailable()) {
toast({
description: `Copying is only available over https`,
});
return;
}
const copyString = selectedBookmarks
.map((item) => {
return item.content.type === BookmarkTypes.LINK && item.content.url;
})
.filter(Boolean)
.join("\n");
await navigator.clipboard.writeText(copyString);
toast({
description: `Added ${selectedBookmarks.length} bookmark links into the clipboard!`,
});
};
const updateBookmarks = async ({
favourited,
archived,
}: UpdateBookmarkProps) => {
await Promise.all(
limitConcurrency(
selectedBookmarks.map(
(item) => () =>
updateBookmarkMutator.mutateAsync({
bookmarkId: item.id,
favourited,
archived,
}),
),
MAX_CONCURRENT_BULK_ACTIONS,
),
);
toast({
description: `${selectedBookmarks.length} bookmarks have been updated!`,
});
};
const deleteBookmarks = async () => {
await Promise.all(
limitConcurrency(
selectedBookmarks.map(
(item) => () =>
deleteBookmarkMutator.mutateAsync({ bookmarkId: item.id }),
),
MAX_CONCURRENT_BULK_ACTIONS,
),
);
toast({
description: `${selectedBookmarks.length} bookmarks have been deleted!`,
});
setIsDeleteDialogOpen(false);
};
const alreadyFavourited =
selectedBookmarks.length &&
selectedBookmarks.every((item) => item.favourited === true);
const alreadyArchived =
selectedBookmarks.length &&
selectedBookmarks.every((item) => item.archived === true);
const actionList = [
{
name: isClipboardAvailable()
? t("actions.copy_link")
: "Copying is only available over https",
icon: ,
action: () => copyLinks(),
isPending: false,
hidden: !isBulkEditEnabled,
},
{
name: t("actions.add_to_list"),
icon: ,
action: () => setManageListsModalOpen(true),
isPending: false,
hidden: !isBulkEditEnabled,
},
{
name: t("actions.edit_tags"),
icon:
(