mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add restore functionality for deleted items with corresponding UI updates
This commit is contained in:
@@ -81,6 +81,7 @@ export interface AppMainRoutesProps {
|
||||
onDeleteVaultItem: (cipher: Cipher) => Promise<void>;
|
||||
onArchiveVaultItem: (cipher: Cipher) => Promise<void>;
|
||||
onUnarchiveVaultItem: (cipher: Cipher) => Promise<void>;
|
||||
onRestoreVaultItems: (ids: string[]) => Promise<void>;
|
||||
onBulkDeleteVaultItems: (ids: string[]) => Promise<void>;
|
||||
onBulkPermanentDeleteVaultItems: (ids: string[]) => Promise<void>;
|
||||
onBulkRestoreVaultItems: (ids: string[]) => Promise<void>;
|
||||
@@ -214,6 +215,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
|
||||
onDelete={props.onDeleteVaultItem}
|
||||
onArchive={props.onArchiveVaultItem}
|
||||
onUnarchive={props.onUnarchiveVaultItem}
|
||||
onRestore={props.onRestoreVaultItems}
|
||||
onBulkDelete={props.onBulkDeleteVaultItems}
|
||||
onBulkPermanentDelete={props.onBulkPermanentDeleteVaultItems}
|
||||
onBulkRestore={props.onBulkRestoreVaultItems}
|
||||
|
||||
@@ -45,6 +45,7 @@ interface VaultPageProps {
|
||||
onDelete: (cipher: Cipher) => Promise<void>;
|
||||
onArchive: (cipher: Cipher) => Promise<void>;
|
||||
onUnarchive: (cipher: Cipher) => Promise<void>;
|
||||
onRestore: (ids: string[]) => Promise<void>;
|
||||
onBulkDelete: (ids: string[]) => Promise<void>;
|
||||
onBulkPermanentDelete: (ids: string[]) => Promise<void>;
|
||||
onBulkRestore: (ids: string[]) => Promise<void>;
|
||||
@@ -732,6 +733,18 @@ const folderName = useCallback((id: string | null | undefined): string => {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRestoreSelected(cipher: Cipher): Promise<void> {
|
||||
setBusy(true);
|
||||
try {
|
||||
await props.onRestore([cipher.id]);
|
||||
if (isMobileLayout && selectedCipherId === cipher.id) {
|
||||
setMobilePanel('list');
|
||||
}
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmBulkDelete(): Promise<void> {
|
||||
const ids = Object.entries(selectedMap)
|
||||
.filter(([, selected]) => selected)
|
||||
@@ -1148,6 +1161,7 @@ const folderName = useCallback((id: string | null | undefined): string => {
|
||||
attachmentDownloadPercent={props.attachmentDownloadPercent}
|
||||
onStartEdit={startEdit}
|
||||
onDelete={setPendingDelete}
|
||||
onRestore={(cipher) => void handleRestoreSelected(cipher)}
|
||||
onArchive={(cipher) => setPendingArchive(cipher)}
|
||||
onUnarchive={(cipher) => void handleUnarchiveSelected(cipher)}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
formatAttachmentSize,
|
||||
formatHistoryTime,
|
||||
formatTotp,
|
||||
isCipherDeleted,
|
||||
maskSecret,
|
||||
openUri,
|
||||
parseFieldType,
|
||||
@@ -36,6 +37,7 @@ interface VaultDetailViewProps {
|
||||
onDownloadAttachment: (cipher: Cipher, attachmentId: string) => void;
|
||||
onStartEdit: () => void;
|
||||
onDelete: (cipher: Cipher) => void;
|
||||
onRestore: (cipher: Cipher) => void | Promise<void>;
|
||||
onArchive: (cipher: Cipher) => void | Promise<void>;
|
||||
onUnarchive: (cipher: Cipher) => void | Promise<void>;
|
||||
}
|
||||
@@ -84,6 +86,7 @@ export default function VaultDetailView(props: VaultDetailViewProps) {
|
||||
const [showSshPrivateKey, setShowSshPrivateKey] = useState(false);
|
||||
const [passwordHistoryOpen, setPasswordHistoryOpen] = useState(false);
|
||||
const isArchived = !!(props.selectedCipher.archivedDate || (props.selectedCipher as { archivedAt?: string | null }).archivedAt);
|
||||
const isDeleted = isCipherDeleted(props.selectedCipher);
|
||||
const passwordHistoryEntries = useMemo(
|
||||
() =>
|
||||
(props.selectedCipher.passwordHistory || [])
|
||||
@@ -446,21 +449,29 @@ export default function VaultDetailView(props: VaultDetailViewProps) {
|
||||
|
||||
<div className="detail-actions">
|
||||
<div className="actions">
|
||||
<button type="button" className="btn btn-secondary" onClick={props.onStartEdit}>
|
||||
<Pencil size={14} className="btn-icon" /> {t('txt_edit')}
|
||||
</button>
|
||||
{isArchived ? (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => void props.onUnarchive(props.selectedCipher)}>
|
||||
<RotateCcw size={14} className="btn-icon" /> {t('txt_unarchive')}
|
||||
{isDeleted ? (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => void props.onRestore(props.selectedCipher)}>
|
||||
<RotateCcw size={14} className="btn-icon" /> {t('txt_restore')}
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => void props.onArchive(props.selectedCipher)}>
|
||||
<Archive size={14} className="btn-icon" /> {t('txt_archive')}
|
||||
</button>
|
||||
<>
|
||||
<button type="button" className="btn btn-secondary" onClick={props.onStartEdit}>
|
||||
<Pencil size={14} className="btn-icon" /> {t('txt_edit')}
|
||||
</button>
|
||||
{isArchived ? (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => void props.onUnarchive(props.selectedCipher)}>
|
||||
<RotateCcw size={14} className="btn-icon" /> {t('txt_unarchive')}
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => void props.onArchive(props.selectedCipher)}>
|
||||
<Archive size={14} className="btn-icon" /> {t('txt_archive')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button type="button" className="btn btn-danger" onClick={() => props.onDelete(props.selectedCipher)}>
|
||||
<Trash2 size={14} className="btn-icon" /> {t('txt_delete')}
|
||||
<Trash2 size={14} className="btn-icon" /> {isDeleted ? t('txt_delete_permanently') : t('txt_delete')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user