diff --git a/apps/browser-extension/src/entrypoints/popup/pages/items/ItemsList.tsx b/apps/browser-extension/src/entrypoints/popup/pages/items/ItemsList.tsx index d25cec83b..33d7f59dd 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/items/ItemsList.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/items/ItemsList.tsx @@ -22,6 +22,7 @@ import { PopoutUtility } from '@/entrypoints/popup/utils/PopoutUtility'; import type { CredentialSortOrder } from '@/utils/db/repositories/SettingsRepository'; import type { Item, ItemType } from '@/utils/dist/core/models/vault'; import { ItemTypes } from '@/utils/dist/core/models/vault'; +import { LocalPreferencesService } from '@/utils/LocalPreferencesService'; import { useMinDurationLoading } from '@/hooks/useMinDurationLoading'; @@ -125,8 +126,14 @@ const ItemsList: React.FC = () => { const [folderRefreshKey, setFolderRefreshKey] = useState(0); const [sortOrder, setSortOrder] = useState('OldestFirst'); const [showSortMenu, setShowSortMenu] = useState(false); + const [showFolders, setShowFolders] = useState(true); const { setIsInitialLoading } = useLoading(); + // Load showFolders preference from storage on mount + useEffect(() => { + LocalPreferencesService.getShowFolders().then(setShowFolders); + }, []); + // Derive current folder from URL params const currentFolderId = folderIdParam ?? null; @@ -442,8 +449,11 @@ const ItemsList: React.FC = () => { if (item.FolderId !== currentFolderId) { return false; } - } else if (!searchTerm) { - // In root view without search, exclude items that are in folders + } else if (!searchTerm && showFolders) { + /* + * When showing folders (checkbox ON): only show root items (exclude items in folders) + * When not showing folders (checkbox OFF): show all items flat + */ if (item.FolderId) { return false; } @@ -587,22 +597,49 @@ const ItemsList: React.FC = () => { setShowFilterMenu(false); }} /> -
+
- {/* All items filter */} - + {/* All items filter with show folders toggle (only show toggle on root view) */} +
+ + {!currentFolderId && ( + + )} +
{/* Item type filters - dynamically generated from ItemTypes */} {ITEM_TYPE_OPTIONS.map((option) => ( @@ -851,8 +888,8 @@ const ItemsList: React.FC = () => { ) : ( <> - {/* Folders as inline pills (only show at root level when not searching) */} - {!currentFolderId && !searchTerm && ( + {/* Folders as inline pills (only show at root level when not searching and showFolders is enabled) */} + {!currentFolderId && !searchTerm && showFolders && (
{folders.map(folder => ( { + const value = await storage.getItem(KEYS.SHOW_FOLDERS) as boolean | null; + return value ?? true; + }, + + /** + * Set the show folders preference. + */ + async setShowFolders(showFolders: boolean): Promise { + await storage.setItem(KEYS.SHOW_FOLDERS, showFolders); + }, + + /* + * ============================================ + * Autofill Settings + * ============================================ + */ + + /** + * Get whether the global autofill popup is enabled. + * @returns Whether autofill popup is globally enabled. Defaults to true. + */ + async getGlobalAutofillPopupEnabled(): Promise { + const value = await storage.getItem(KEYS.GLOBAL_AUTOFILL_POPUP_ENABLED) as boolean | null; + return value !== false; + }, + + /** + * Set whether the global autofill popup is enabled. + */ + async setGlobalAutofillPopupEnabled(enabled: boolean): Promise { + await storage.setItem(KEYS.GLOBAL_AUTOFILL_POPUP_ENABLED, enabled); + }, + + /** + * Get the autofill matching mode. + * @returns The matching mode. Defaults to DEFAULT. + */ + async getAutofillMatchingMode(): Promise { + const value = await storage.getItem(KEYS.AUTOFILL_MATCHING_MODE) as AutofillMatchingMode | null; + return value ?? AutofillMatchingMode.DEFAULT; + }, + + /** + * Set the autofill matching mode. + */ + async setAutofillMatchingMode(mode: AutofillMatchingMode): Promise { + await storage.setItem(KEYS.AUTOFILL_MATCHING_MODE, mode); + }, + + /** + * Get the list of permanently disabled sites. + * @returns Array of disabled site URLs. Defaults to empty array. + */ + async getDisabledSites(): Promise { + const value = await storage.getItem(KEYS.DISABLED_SITES) as string[] | null; + return value ?? []; + }, + + /** + * Set the list of permanently disabled sites. + */ + async setDisabledSites(sites: string[]): Promise { + await storage.setItem(KEYS.DISABLED_SITES, sites); + }, + + /** + * Get the map of temporarily disabled sites with their expiry timestamps. + * @returns Record of site URL to expiry timestamp. Defaults to empty object. + */ + async getTemporaryDisabledSites(): Promise> { + const value = await storage.getItem(KEYS.TEMPORARY_DISABLED_SITES) as Record | null; + return value ?? {}; + }, + + /** + * Set the map of temporarily disabled sites. + */ + async setTemporaryDisabledSites(sites: Record): Promise { + await storage.setItem(KEYS.TEMPORARY_DISABLED_SITES, sites); + }, + + /* + * ============================================ + * Context Menu Settings + * ============================================ + */ + + /** + * Get whether the global context menu is enabled. + * @returns Whether context menu is globally enabled. Defaults to true. + */ + async getGlobalContextMenuEnabled(): Promise { + const value = await storage.getItem(KEYS.GLOBAL_CONTEXT_MENU_ENABLED) as boolean | null; + return value !== false; + }, + + /** + * Set whether the global context menu is enabled. + */ + async setGlobalContextMenuEnabled(enabled: boolean): Promise { + await storage.setItem(KEYS.GLOBAL_CONTEXT_MENU_ENABLED, enabled); + }, + + /* + * ============================================ + * Passkey Settings + * ============================================ + */ + + /** + * Get whether the passkey provider is globally enabled. + * @returns Whether passkey provider is enabled. Defaults to true. + */ + async getPasskeyProviderEnabled(): Promise { + const value = await storage.getItem(KEYS.PASSKEY_PROVIDER_ENABLED) as boolean | null; + return value !== false; + }, + + /** + * Set whether the passkey provider is globally enabled. + */ + async setPasskeyProviderEnabled(enabled: boolean): Promise { + await storage.setItem(KEYS.PASSKEY_PROVIDER_ENABLED, enabled); + }, + + /** + * Get the list of sites where passkey provider is disabled. + * @returns Array of disabled site URLs. Defaults to empty array. + */ + async getPasskeyDisabledSites(): Promise { + const value = await storage.getItem(KEYS.PASSKEY_DISABLED_SITES) as string[] | null; + return value ?? []; + }, + + /** + * Set the list of sites where passkey provider is disabled. + */ + async setPasskeyDisabledSites(sites: string[]): Promise { + await storage.setItem(KEYS.PASSKEY_DISABLED_SITES, sites); + }, + + /* + * ============================================ + * Timeout Settings + * ============================================ + */ + + /** + * Get the clipboard clear timeout in seconds. + * @returns Timeout in seconds. Defaults to 10. + */ + async getClipboardClearTimeout(): Promise { + const value = await storage.getItem(KEYS.CLIPBOARD_CLEAR_TIMEOUT) as number | null; + return value ?? 10; + }, + + /** + * Set the clipboard clear timeout in seconds. + */ + async setClipboardClearTimeout(timeout: number): Promise { + await storage.setItem(KEYS.CLIPBOARD_CLEAR_TIMEOUT, timeout); + }, + + /** + * Get the auto-lock timeout in seconds. + * @returns Timeout in seconds. Defaults to 0 (never). + */ + async getAutoLockTimeout(): Promise { + const value = await storage.getItem(KEYS.AUTO_LOCK_TIMEOUT) as number | null; + return value ?? 0; + }, + + /** + * Set the auto-lock timeout in seconds. + */ + async setAutoLockTimeout(timeout: number): Promise { + await storage.setItem(KEYS.AUTO_LOCK_TIMEOUT, timeout); + }, + + /** + * Get the vault locked dismiss until timestamp. + * @returns Timestamp until which the vault locked message is dismissed. Defaults to 0. + */ + async getVaultLockedDismissUntil(): Promise { + const value = await storage.getItem(KEYS.VAULT_LOCKED_DISMISS_UNTIL) as number | null; + return value ?? 0; + }, + + /** + * Set the vault locked dismiss until timestamp. + */ + async setVaultLockedDismissUntil(timestamp: number): Promise { + await storage.setItem(KEYS.VAULT_LOCKED_DISMISS_UNTIL, timestamp); + }, + + /* + * ============================================ + * History Settings (for custom email/username) + * ============================================ + */ + + /** + * Get the custom email history. + * @returns Array of previously used custom emails. Defaults to empty array. + */ + async getCustomEmailHistory(): Promise { + const value = await storage.getItem(KEYS.CUSTOM_EMAIL_HISTORY) as string[] | null; + return value ?? []; + }, + + /** + * Set the custom email history. + */ + async setCustomEmailHistory(history: string[]): Promise { + await storage.setItem(KEYS.CUSTOM_EMAIL_HISTORY, history); + }, + + /** + * Get the custom username history. + * @returns Array of previously used custom usernames. Defaults to empty array. + */ + async getCustomUsernameHistory(): Promise { + const value = await storage.getItem(KEYS.CUSTOM_USERNAME_HISTORY) as string[] | null; + return value ?? []; + }, + + /** + * Set the custom username history. + */ + async setCustomUsernameHistory(history: string[]): Promise { + await storage.setItem(KEYS.CUSTOM_USERNAME_HISTORY, history); + }, + + /* + * ============================================ + * Utility Methods + * ============================================ + */ + + /** + * Clear all UI preferences. Can be called on logout. + * Note: This only clears UI preferences, not security-related settings. + */ + async clearUiPreferences(): Promise { + await storage.removeItem(KEYS.SHOW_FOLDERS); + }, + + /** + * Reset all site-specific settings (disabled sites, temporary disabled sites). + */ + async resetAllSiteSettings(): Promise { + await storage.setItem(KEYS.DISABLED_SITES, []); + await storage.setItem(KEYS.TEMPORARY_DISABLED_SITES, {}); + await storage.setItem(KEYS.PASSKEY_DISABLED_SITES, []); + }, + + /** + * Clear all preferences. Called on logout to reset everything. + */ + async clearAll(): Promise { + await Promise.all([ + storage.removeItem(KEYS.SHOW_FOLDERS), + storage.removeItem(KEYS.DISABLED_SITES), + storage.removeItem(KEYS.TEMPORARY_DISABLED_SITES), + storage.removeItem(KEYS.PASSKEY_DISABLED_SITES), + storage.removeItem(KEYS.VAULT_LOCKED_DISMISS_UNTIL), + storage.removeItem(KEYS.CUSTOM_EMAIL_HISTORY), + storage.removeItem(KEYS.CUSTOM_USERNAME_HISTORY), + /* + * Note: We don't clear global settings like autofill enabled, clipboard timeout, etc. + * as those are user preferences that should persist across logins. + */ + ]); + }, +};