feat: implement archive and bulk archive functionality with confirmation dialogs

This commit is contained in:
shuaiplus
2026-03-23 08:22:08 +08:00
parent cb4632cd04
commit 96fc3ae485
3 changed files with 59 additions and 2 deletions
+25 -2
View File
@@ -79,7 +79,9 @@ export default function VaultPage(props: VaultPageProps) {
const [fieldLabel, setFieldLabel] = useState(''); const [fieldLabel, setFieldLabel] = useState('');
const [fieldValue, setFieldValue] = useState(''); const [fieldValue, setFieldValue] = useState('');
const [localError, setLocalError] = useState(''); const [localError, setLocalError] = useState('');
const [pendingArchive, setPendingArchive] = useState<Cipher | null>(null);
const [pendingDelete, setPendingDelete] = useState<Cipher | null>(null); const [pendingDelete, setPendingDelete] = useState<Cipher | null>(null);
const [bulkArchiveOpen, setBulkArchiveOpen] = useState(false);
const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false); const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
const [moveOpen, setMoveOpen] = useState(false); const [moveOpen, setMoveOpen] = useState(false);
const [moveFolderId, setMoveFolderId] = useState('__none__'); const [moveFolderId, setMoveFolderId] = useState('__none__');
@@ -684,6 +686,20 @@ function folderName(id: string | null | undefined): string {
} }
} }
async function confirmArchiveSelected(): Promise<void> {
if (!pendingArchive) return;
setBusy(true);
try {
await props.onArchive(pendingArchive);
setPendingArchive(null);
if (isMobileLayout && selectedCipherId === pendingArchive.id) {
setMobilePanel('list');
}
} finally {
setBusy(false);
}
}
async function confirmBulkArchive(): Promise<void> { async function confirmBulkArchive(): Promise<void> {
const ids = Object.entries(selectedMap) const ids = Object.entries(selectedMap)
.filter(([, selected]) => selected) .filter(([, selected]) => selected)
@@ -693,6 +709,7 @@ function folderName(id: string | null | undefined): string {
try { try {
await props.onBulkArchive(ids); await props.onBulkArchive(ids);
setSelectedMap({}); setSelectedMap({});
setBulkArchiveOpen(false);
} finally { } finally {
setBusy(false); setBusy(false);
} }
@@ -795,7 +812,7 @@ function folderName(id: string | null | undefined): string {
onToggleCreateMenu={() => setCreateMenuOpen((open) => !open)} onToggleCreateMenu={() => setCreateMenuOpen((open) => !open)}
onStartCreate={startCreate} onStartCreate={startCreate}
onBulkRestore={() => void confirmBulkRestore()} onBulkRestore={() => void confirmBulkRestore()}
onBulkArchive={() => void confirmBulkArchive()} onBulkArchive={() => setBulkArchiveOpen(true)}
onBulkUnarchive={() => void confirmBulkUnarchive()} onBulkUnarchive={() => void confirmBulkUnarchive()}
onOpenMove={() => { onOpenMove={() => {
setMoveFolderId('__none__'); setMoveFolderId('__none__');
@@ -888,7 +905,7 @@ function folderName(id: string | null | undefined): string {
attachmentDownloadPercent={props.attachmentDownloadPercent} attachmentDownloadPercent={props.attachmentDownloadPercent}
onStartEdit={startEdit} onStartEdit={startEdit}
onDelete={setPendingDelete} onDelete={setPendingDelete}
onArchive={props.onArchive} onArchive={(cipher) => setPendingArchive(cipher)}
onUnarchive={props.onUnarchive} onUnarchive={props.onUnarchive}
/> />
)} )}
@@ -902,6 +919,8 @@ function folderName(id: string | null | undefined): string {
fieldType={fieldType} fieldType={fieldType}
fieldLabel={fieldLabel} fieldLabel={fieldLabel}
fieldValue={fieldValue} fieldValue={fieldValue}
archiveConfirmOpen={!!pendingArchive}
bulkArchiveOpen={bulkArchiveOpen}
pendingDeleteOpen={!!pendingDelete} pendingDeleteOpen={!!pendingDelete}
bulkDeleteOpen={bulkDeleteOpen} bulkDeleteOpen={bulkDeleteOpen}
sidebarTrashMode={sidebarFilter.kind === 'trash'} sidebarTrashMode={sidebarFilter.kind === 'trash'}
@@ -944,6 +963,10 @@ function folderName(id: string | null | undefined): string {
onFieldTypeChange={setFieldType} onFieldTypeChange={setFieldType}
onFieldLabelChange={setFieldLabel} onFieldLabelChange={setFieldLabel}
onFieldValueChange={setFieldValue} onFieldValueChange={setFieldValue}
onConfirmArchive={() => void confirmArchiveSelected()}
onCancelArchive={() => setPendingArchive(null)}
onConfirmBulkArchive={() => void confirmBulkArchive()}
onCancelBulkArchive={() => setBulkArchiveOpen(false)}
onConfirmDelete={() => void deleteSelected()} onConfirmDelete={() => void deleteSelected()}
onCancelDelete={() => setPendingDelete(null)} onCancelDelete={() => setPendingDelete(null)}
onConfirmBulkDelete={() => void confirmBulkDelete()} onConfirmBulkDelete={() => void confirmBulkDelete()}
@@ -8,6 +8,8 @@ interface VaultDialogsProps {
fieldType: CustomFieldType; fieldType: CustomFieldType;
fieldLabel: string; fieldLabel: string;
fieldValue: string; fieldValue: string;
archiveConfirmOpen: boolean;
bulkArchiveOpen: boolean;
pendingDeleteOpen: boolean; pendingDeleteOpen: boolean;
bulkDeleteOpen: boolean; bulkDeleteOpen: boolean;
sidebarTrashMode: boolean; sidebarTrashMode: boolean;
@@ -26,6 +28,10 @@ interface VaultDialogsProps {
onFieldTypeChange: (value: CustomFieldType) => void; onFieldTypeChange: (value: CustomFieldType) => void;
onFieldLabelChange: (value: string) => void; onFieldLabelChange: (value: string) => void;
onFieldValueChange: (value: string) => void; onFieldValueChange: (value: string) => void;
onConfirmArchive: () => void;
onCancelArchive: () => void;
onConfirmBulkArchive: () => void;
onCancelBulkArchive: () => void;
onConfirmDelete: () => void; onConfirmDelete: () => void;
onCancelDelete: () => void; onCancelDelete: () => void;
onConfirmBulkDelete: () => void; onConfirmBulkDelete: () => void;
@@ -88,6 +94,26 @@ export default function VaultDialogs(props: VaultDialogsProps) {
)} )}
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog
open={props.archiveConfirmOpen}
title={t('txt_archive_item')}
message={t('txt_archive_item_message')}
confirmText={t('txt_archive')}
cancelText={t('txt_cancel')}
onConfirm={props.onConfirmArchive}
onCancel={props.onCancelArchive}
/>
<ConfirmDialog
open={props.bulkArchiveOpen}
title={t('txt_archive_selected_items')}
message={t('txt_archive_selected_items_message', { count: props.selectedCount })}
confirmText={t('txt_archive')}
cancelText={t('txt_cancel')}
onConfirm={props.onConfirmBulkArchive}
onCancel={props.onCancelBulkArchive}
/>
<ConfirmDialog open={props.pendingDeleteOpen} title={t('txt_delete_item')} message={t('txt_are_you_sure_you_want_to_delete_this_item')} danger onConfirm={props.onConfirmDelete} onCancel={props.onCancelDelete} /> <ConfirmDialog open={props.pendingDeleteOpen} title={t('txt_delete_item')} message={t('txt_are_you_sure_you_want_to_delete_this_item')} danger onConfirm={props.onConfirmDelete} onCancel={props.onCancelDelete} />
<ConfirmDialog <ConfirmDialog
+8
View File
@@ -281,6 +281,10 @@ const messages: Record<Locale, Record<string, string>> = {
txt_delete_item_failed: "Delete item failed", txt_delete_item_failed: "Delete item failed",
txt_delete_permanently: "Delete Permanently", txt_delete_permanently: "Delete Permanently",
txt_archive: "Archive", txt_archive: "Archive",
txt_archive_item: "Archive Item",
txt_archive_item_message: "After archiving, this item will be excluded from general search results and autofill suggestions.",
txt_archive_selected_items: "Archive Items",
txt_archive_selected_items_message: "After archiving, {count} selected items will be excluded from general search results and autofill suggestions.",
txt_archived: "Archived", txt_archived: "Archived",
txt_archive_selected: "Archive", txt_archive_selected: "Archive",
txt_item_archived: "Item archived", txt_item_archived: "Item archived",
@@ -1376,6 +1380,10 @@ zhCNOverrides.txt_import_export_title = '导入导出';
zhCNOverrides.txt_new_type_header = '新建{type}'; zhCNOverrides.txt_new_type_header = '新建{type}';
zhCNOverrides.txt_edit_type_header = '编辑{type}'; zhCNOverrides.txt_edit_type_header = '编辑{type}';
zhCNOverrides.txt_archive = '归档'; zhCNOverrides.txt_archive = '归档';
zhCNOverrides.txt_archive_item = '归档项目';
zhCNOverrides.txt_archive_item_message = '归档后,此项目将被排除在一般搜索结果和自动填充建议之外。';
zhCNOverrides.txt_archive_selected_items = '归档项目';
zhCNOverrides.txt_archive_selected_items_message = '归档后,所选的 {count} 个项目将被排除在一般搜索结果和自动填充建议之外。';
zhCNOverrides.txt_archived = '已归档'; zhCNOverrides.txt_archived = '已归档';
zhCNOverrides.txt_archive_selected = '归档'; zhCNOverrides.txt_archive_selected = '归档';
zhCNOverrides.txt_item_archived = '项目已归档'; zhCNOverrides.txt_item_archived = '项目已归档';