diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-12-30 13:30:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-30 11:30:35 +0000 |
| commit | 7a76216e5c971a300e9db32c93509b0376f6f47e (patch) | |
| tree | 2e383482c7dbfb127c2c3c725125ad253c6b4aa0 /apps/web/components/dashboard/BulkBookmarksAction.tsx | |
| parent | b20ba9cfccd22159bf8263165cca76aab3147d9c (diff) | |
| download | karakeep-7a76216e5c971a300e9db32c93509b0376f6f47e.tar.zst | |
feat: Add bulk remove from list (#2279)
* feat: Add bulk remove from list action in list context
- Add "Remove from List" button in bulk actions menu
- Only visible when in a manual list context with editor/owner role
- Includes confirmation dialog before removal
- Uses same concurrency pattern as bulk add (50 concurrent operations)
- Displays success count in toast notification
- Add translation key "actions.remove" for consistency
This complements the existing bulk add to list functionality and allows
users to efficiently remove multiple bookmarks from a list at once.
* fmt
* fix list context
* add remove from list
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'apps/web/components/dashboard/BulkBookmarksAction.tsx')
| -rw-r--r-- | apps/web/components/dashboard/BulkBookmarksAction.tsx | 75 |
1 files changed, 74 insertions, 1 deletions
diff --git a/apps/web/components/dashboard/BulkBookmarksAction.tsx b/apps/web/components/dashboard/BulkBookmarksAction.tsx index 9e248c03..bad76ff9 100644 --- a/apps/web/components/dashboard/BulkBookmarksAction.tsx +++ b/apps/web/components/dashboard/BulkBookmarksAction.tsx @@ -16,6 +16,7 @@ import { Hash, Link, List, + ListMinus, Pencil, RotateCw, Trash2, @@ -27,6 +28,7 @@ import { useRecrawlBookmark, useUpdateBookmark, } from "@karakeep/shared-react/hooks/bookmarks"; +import { useRemoveBookmarkFromList } from "@karakeep/shared-react/hooks/lists"; import { limitConcurrency } from "@karakeep/shared/concurrency"; import { BookmarkTypes } from "@karakeep/shared/types/bookmarks"; @@ -38,7 +40,11 @@ const MAX_CONCURRENT_BULK_ACTIONS = 50; export default function BulkBookmarksAction() { const { t } = useTranslation(); - const { selectedBookmarks, isBulkEditEnabled } = useBulkActionsStore(); + const { + selectedBookmarks, + isBulkEditEnabled, + listContext: withinListContext, + } = useBulkActionsStore(); const setIsBulkEditEnabled = useBulkActionsStore( (state) => state.setIsBulkEditEnabled, ); @@ -50,6 +56,8 @@ export default function BulkBookmarksAction() { (state) => state.isEverythingSelected, ); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [isRemoveFromListDialogOpen, setIsRemoveFromListDialogOpen] = + useState(false); const [manageListsModal, setManageListsModalOpen] = useState(false); const [bulkTagModal, setBulkTagModalOpen] = useState(false); const pathname = usePathname(); @@ -92,6 +100,13 @@ export default function BulkBookmarksAction() { onError, }); + const removeBookmarkFromListMutator = useRemoveBookmarkFromList({ + onSuccess: () => { + setIsBulkEditEnabled(false); + }, + onError, + }); + interface UpdateBookmarkProps { favourited?: boolean; archived?: boolean; @@ -184,6 +199,31 @@ export default function BulkBookmarksAction() { setIsDeleteDialogOpen(false); }; + const removeBookmarksFromList = async () => { + if (!withinListContext) return; + + const results = await Promise.allSettled( + limitConcurrency( + selectedBookmarks.map( + (item) => () => + removeBookmarkFromListMutator.mutateAsync({ + bookmarkId: item.id, + listId: withinListContext.id, + }), + ), + MAX_CONCURRENT_BULK_ACTIONS, + ), + ); + + const successes = results.filter((r) => r.status === "fulfilled").length; + if (successes > 0) { + toast({ + description: `${successes} bookmarks have been removed from the list!`, + }); + } + setIsRemoveFromListDialogOpen(false); + }; + const alreadyFavourited = selectedBookmarks.length && selectedBookmarks.every((item) => item.favourited === true); @@ -203,6 +243,18 @@ export default function BulkBookmarksAction() { hidden: !isBulkEditEnabled, }, { + name: t("actions.remove_from_list"), + icon: <ListMinus size={18} />, + action: () => setIsRemoveFromListDialogOpen(true), + isPending: removeBookmarkFromListMutator.isPending, + hidden: + !isBulkEditEnabled || + !withinListContext || + withinListContext.type !== "manual" || + (withinListContext.userRole !== "editor" && + withinListContext.userRole !== "owner"), + }, + { name: t("actions.add_to_list"), icon: <List size={18} />, action: () => setManageListsModalOpen(true), @@ -298,6 +350,27 @@ export default function BulkBookmarksAction() { </ActionButton> )} /> + <ActionConfirmingDialog + open={isRemoveFromListDialogOpen} + setOpen={setIsRemoveFromListDialogOpen} + title={"Remove Bookmarks from List"} + description={ + <p> + Are you sure you want to remove {selectedBookmarks.length} bookmarks + from this list? + </p> + } + actionButton={() => ( + <ActionButton + type="button" + variant="destructive" + loading={removeBookmarkFromListMutator.isPending} + onClick={() => removeBookmarksFromList()} + > + {t("actions.remove")} + </ActionButton> + )} + /> <BulkManageListsModal bookmarkIds={selectedBookmarks.map((b) => b.id)} open={manageListsModal} |
