mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add duplicate handling features and UI elements for cipher management
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user