diff --git a/webapp/src/components/VaultPage.tsx b/webapp/src/components/VaultPage.tsx index e950311..b3e53df 100644 --- a/webapp/src/components/VaultPage.tsx +++ b/webapp/src/components/VaultPage.tsx @@ -1078,6 +1078,20 @@ const folderName = useCallback((id: string | null | undefined): string => { } setSelectedMap(map); }, [filteredCiphers, duplicateSignatureInfo, duplicateMode]); + const handleSelectUniqueFromDuplicates = useCallback(() => { + const map: Record = {}; + const seen = new Set(); + for (const cipher of filteredCiphers) { + const groupIndex = duplicateGroupIndexById.get(cipher.id); + if (groupIndex === undefined) continue; + if (seen.has(groupIndex)) { + map[cipher.id] = true; + } else { + seen.add(groupIndex); + } + } + setSelectedMap(map); + }, [filteredCiphers, duplicateGroupIndexById]); const handleSelectAll = useCallback(() => { const map: Record = {}; for (const cipher of filteredCiphers) map[cipher.id] = true; @@ -1192,6 +1206,7 @@ const folderName = useCallback((id: string | null | undefined): string => { onSyncVault={handleSyncVault} onOpenBulkDelete={handleOpenBulkDelete} onSelectDuplicates={handleSelectDuplicates} + onSelectUniqueFromDuplicates={handleSelectUniqueFromDuplicates} onSelectAll={handleSelectAll} onToggleCreateMenu={handleToggleCreateMenu} onStartCreate={startCreate} diff --git a/webapp/src/components/vault/VaultListPanel.tsx b/webapp/src/components/vault/VaultListPanel.tsx index d598924..e00b203 100644 --- a/webapp/src/components/vault/VaultListPanel.tsx +++ b/webapp/src/components/vault/VaultListPanel.tsx @@ -81,6 +81,7 @@ interface VaultListPanelProps { onSyncVault: () => void; onOpenBulkDelete: () => void; onSelectDuplicates: () => void; + onSelectUniqueFromDuplicates: () => void; onSelectAll: () => void; onToggleCreateMenu: () => void; onStartCreate: (type: number) => void; @@ -319,40 +320,43 @@ export default function VaultListPanel(props: VaultListPanelProps) { ) : ( <> -
- {props.sidebarFilter.kind === 'duplicates' && props.isMobileLayout ? ( -
+ {props.sidebarFilter.kind === 'duplicates' && props.isMobileLayout ? ( +
+
{renderMobileFilterMenu('duplicate', t('txt_duplicate_detection_mode'), duplicateModeSelected, , duplicateModeOptions)}
- ) : ( - <> - props.onSearchInput((e.currentTarget as HTMLInputElement).value)} - onCompositionStart={props.onSearchCompositionStart} - onCompositionEnd={(e) => props.onSearchCompositionEnd((e.currentTarget as HTMLInputElement).value)} - onKeyDown={(e) => { - if (e.key !== 'Escape' || !props.searchInput) return; - e.preventDefault(); - props.onClearSearch(); - }} - /> - {!!props.searchInput && ( - - )} - - )} -
+ +
+ ) : ( +
+ props.onSearchInput((e.currentTarget as HTMLInputElement).value)} + onCompositionStart={props.onSearchCompositionStart} + onCompositionEnd={(e) => props.onSearchCompositionEnd((e.currentTarget as HTMLInputElement).value)} + onKeyDown={(e) => { + if (e.key !== 'Escape' || !props.searchInput) return; + e.preventDefault(); + props.onClearSearch(); + }} + /> + {!!props.searchInput && ( + + )} +
+ )} {props.sidebarFilter.kind === 'duplicates' && !props.isMobileLayout && (
{renderMobileFilterMenu('duplicate', t('txt_duplicate_detection_mode'), duplicateModeSelected, , duplicateModeOptions)} @@ -387,7 +391,13 @@ export default function VaultListPanel(props: VaultListPanelProps) { - {!props.isMobileLayout && props.sidebarFilter !== undefined && createMenu} + {props.sidebarFilter.kind === 'duplicates' && !props.isMobileLayout ? ( + + ) : ( + !props.isMobileLayout && props.sidebarFilter !== undefined && createMenu + )} )}
diff --git a/webapp/src/styles/responsive.css b/webapp/src/styles/responsive.css index de4305d..2d730e1 100644 --- a/webapp/src/styles/responsive.css +++ b/webapp/src/styles/responsive.css @@ -427,6 +427,20 @@ min-width: max(100%, 190px); } + .list-head .mobile-duplicate-toolbar { + @apply flex min-w-0 items-center gap-1.5; + flex: none; + } + + .list-head .mobile-duplicate-mode-select-wrap { + max-width: 130px; + flex-shrink: 0; + } + + .list-head .mobile-duplicate-mode-select-wrap .mobile-vault-filter-trigger { + @apply w-full; + } + .toolbar.actions { @apply justify-end overflow-visible pb-0.5; flex-wrap: unset;