mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add duplicate detection modes and UI enhancements for managing duplicates
This commit is contained in:
@@ -10,10 +10,13 @@ import {
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { t } from '@/lib/i18n';
|
||||
import type { Cipher, CipherAttachment, CustomFieldType, VaultDraft, VaultDraftField, VaultDraftLoginUri } from '@/lib/types';
|
||||
import { firstCipherUri, hostFromUri, websiteIconUrl } from '@/lib/website-utils';
|
||||
import { normalizeEquivalentDomain } from '@shared/domain-normalize';
|
||||
import WebsiteIcon from './WebsiteIcon';
|
||||
|
||||
export type TypeFilter = 'login' | 'card' | 'identity' | 'note' | 'ssh';
|
||||
export type VaultSortMode = 'edited' | 'created' | 'name';
|
||||
export type DuplicateDetectionMode = 'exact' | 'login-site' | 'login-credentials' | 'password';
|
||||
export type SidebarFilter =
|
||||
| { kind: 'all' }
|
||||
| { kind: 'favorite' }
|
||||
@@ -126,6 +129,16 @@ export const FOLDER_SORT_STORAGE_KEY = 'nodewarden.folder-sort.v1';
|
||||
export const MOBILE_LAYOUT_QUERY = '(max-width: 1180px)';
|
||||
export const VAULT_LIST_ROW_HEIGHT = 74;
|
||||
export const VAULT_LIST_OVERSCAN = 10;
|
||||
|
||||
export function getDuplicateDetectionOptions(): Array<{ value: DuplicateDetectionMode; label: string }> {
|
||||
return [
|
||||
{ value: 'exact', label: t('txt_duplicate_mode_exact') },
|
||||
{ value: 'login-site', label: t('txt_duplicate_mode_login_site') },
|
||||
{ value: 'login-credentials', label: t('txt_duplicate_mode_login_credentials') },
|
||||
{ value: 'password', label: t('txt_duplicate_mode_password') },
|
||||
];
|
||||
}
|
||||
|
||||
export function getVaultSortOptions(): Array<{ value: VaultSortMode; label: string }> {
|
||||
return [
|
||||
{ value: 'edited', label: t('txt_sort_last_edited') },
|
||||
@@ -242,7 +255,7 @@ export function toBooleanFieldValue(raw: string): boolean {
|
||||
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
||||
}
|
||||
|
||||
export { firstCipherUri, hostFromUri, websiteIconUrl } from '@/lib/website-utils';
|
||||
export { firstCipherUri, hostFromUri, websiteIconUrl };
|
||||
|
||||
export function createEmptyLoginUri(): VaultDraftLoginUri {
|
||||
return { uri: '', match: null, originalUri: '', extra: {} };
|
||||
@@ -257,6 +270,30 @@ function valueOrFallback(value: string | null | undefined): string {
|
||||
return String(value || '');
|
||||
}
|
||||
|
||||
function duplicateLoginUsername(cipher: Cipher): string {
|
||||
return valueOrFallback(cipher.login?.decUsername ?? cipher.login?.username).trim().toLowerCase();
|
||||
}
|
||||
|
||||
function duplicateLoginPassword(cipher: Cipher): string {
|
||||
return valueOrFallback(cipher.login?.decPassword ?? cipher.login?.password);
|
||||
}
|
||||
|
||||
function duplicateLoginSites(cipher: Cipher): string[] {
|
||||
const sites = new Set<string>();
|
||||
for (const uri of cipher.login?.uris || []) {
|
||||
const raw = valueOrFallback(uri.decUri ?? uri.uri).trim();
|
||||
if (!raw) continue;
|
||||
const host = hostFromUri(raw).trim().toLowerCase().replace(/^www\./, '');
|
||||
const site = normalizeEquivalentDomain(raw) || host;
|
||||
if (site) sites.add(site);
|
||||
}
|
||||
return Array.from(sites).sort();
|
||||
}
|
||||
|
||||
function duplicateSignature(parts: string[]): string {
|
||||
return JSON.stringify(parts);
|
||||
}
|
||||
|
||||
export function buildCipherDuplicateSignature(cipher: Cipher): string {
|
||||
const normalized = {
|
||||
type: Number(cipher.type || 1),
|
||||
@@ -333,6 +370,23 @@ export function buildCipherDuplicateSignature(cipher: Cipher): string {
|
||||
return JSON.stringify(normalized);
|
||||
}
|
||||
|
||||
export function buildCipherDuplicateSignatures(cipher: Cipher, mode: DuplicateDetectionMode): string[] {
|
||||
if (mode === 'exact') return [buildCipherDuplicateSignature(cipher)];
|
||||
if (Number(cipher.type || 1) !== 1 || !cipher.login) return [];
|
||||
|
||||
const username = duplicateLoginUsername(cipher);
|
||||
const password = duplicateLoginPassword(cipher);
|
||||
if (mode === 'password') {
|
||||
return password ? [duplicateSignature(['password', password])] : [];
|
||||
}
|
||||
if (!username || !password) return [];
|
||||
if (mode === 'login-credentials') {
|
||||
return [duplicateSignature(['login-credentials', username, password])];
|
||||
}
|
||||
|
||||
return duplicateLoginSites(cipher).map((site) => duplicateSignature(['login-site', site, username, password]));
|
||||
}
|
||||
|
||||
export function createEmptyDraft(type: number): VaultDraft {
|
||||
return {
|
||||
type,
|
||||
|
||||
Reference in New Issue
Block a user