feat: add duplicate handling features and UI elements for cipher management

This commit is contained in:
shuaiplus
2026-03-18 01:39:35 +08:00
parent 9280f6916e
commit 3204eeb9ab
5 changed files with 131 additions and 2 deletions
+35 -1
View File
@@ -14,6 +14,7 @@ import {
createEmptyDraft,
creationTimeValue,
draftFromCipher,
buildCipherDuplicateSignature,
firstCipherUri,
firstPasskeyCreationTime,
sortTimeValue,
@@ -223,6 +224,17 @@ export default function VaultPage(props: VaultPageProps) {
void recalculateSshFingerprint(draft.sshPublicKey);
}, [isEditing, draft?.id, draft?.type]);
const duplicateSignatureCounts = useMemo(() => {
const counts = new Map<string, number>();
for (const cipher of props.ciphers) {
const isDeleted = !!(cipher.deletedDate || (cipher as { deletedAt?: string | null }).deletedAt);
if (isDeleted) continue;
const signature = buildCipherDuplicateSignature(cipher);
counts.set(signature, (counts.get(signature) || 0) + 1);
}
return counts;
}, [props.ciphers]);
const filteredCiphers = useMemo(() => {
const next = props.ciphers.filter((cipher) => {
const isDeleted = !!(cipher.deletedDate || (cipher as any).deletedAt);
@@ -230,6 +242,9 @@ export default function VaultPage(props: VaultPageProps) {
if (!isDeleted) return false;
} else {
if (isDeleted) return false;
if (sidebarFilter.kind === 'duplicates' && (duplicateSignatureCounts.get(buildCipherDuplicateSignature(cipher)) || 0) < 2) {
return false;
}
if (sidebarFilter.kind === 'favorite' && !cipher.favorite) return false;
if (sidebarFilter.kind === 'type' && cipherTypeKey(Number(cipher.type || 1)) !== sidebarFilter.value) return false;
if (sidebarFilter.kind === 'folder') {
@@ -266,7 +281,7 @@ export default function VaultPage(props: VaultPageProps) {
});
return next;
}, [props.ciphers, sidebarFilter, searchQuery, sortMode]);
}, [props.ciphers, sidebarFilter, searchQuery, sortMode, duplicateSignatureCounts]);
const sidebarFilterKey = useMemo(() => {
if (sidebarFilter.kind === 'folder') return `folder:${sidebarFilter.folderId ?? 'none'}`;
@@ -279,6 +294,12 @@ export default function VaultPage(props: VaultPageProps) {
listPanelRef.current?.scrollTo({ top: 0 });
}, [searchQuery, sortMode, sidebarFilterKey]);
useEffect(() => {
if (sidebarFilter.kind === 'duplicates' && sortMode !== 'name') {
setSortMode('name');
}
}, [sidebarFilter.kind, sortMode]);
useEffect(() => {
if (isCreating) return;
if (!filteredCiphers.length) {
@@ -716,6 +737,19 @@ function folderName(id: string | null | undefined): string {
}}
onSyncVault={() => void syncVault()}
onOpenBulkDelete={() => setBulkDeleteOpen(true)}
onSelectDuplicates={() => {
const map: Record<string, boolean> = {};
const seen = new Set<string>();
for (const cipher of filteredCiphers) {
const signature = buildCipherDuplicateSignature(cipher);
if (seen.has(signature)) {
map[cipher.id] = true;
continue;
}
seen.add(signature);
}
setSelectedMap(map);
}}
onSelectAll={() => {
const map: Record<string, boolean> = {};
for (const cipher of filteredCiphers) map[cipher.id] = true;