fix: vault paths, TOTP caching, and keyboard nav on filtered list
- Fix .idfoto/ prefix for salt and params.json in vault.ts - Cache TOTP secrets by entry ID to avoid re-fetching every second - Fix keyboard navigation to use filtered entries, not unfiltered - Add window.close() on Escape from entry list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,26 +25,7 @@ function getGroups(entries: Array<[string, ManifestEntry]>): string[] {
|
||||
export function renderEntryList(app: HTMLElement): void {
|
||||
const state = getState();
|
||||
const groups = getGroups(state.entries);
|
||||
|
||||
// Filter entries by active group (already filtered if group was sent to service worker,
|
||||
// but we also support client-side filtering for instant response).
|
||||
let filtered = state.entries;
|
||||
if (state.activeGroup) {
|
||||
const g = state.activeGroup.toLowerCase();
|
||||
filtered = filtered.filter(([, e]) => e.group?.toLowerCase() === g);
|
||||
}
|
||||
if (state.searchQuery) {
|
||||
const q = state.searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(([, e]) => {
|
||||
if (e.name.toLowerCase().includes(q)) return true;
|
||||
if (e.url?.toLowerCase().includes(q)) return true;
|
||||
if (e.username?.toLowerCase().includes(q)) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by name.
|
||||
filtered.sort((a, b) => a[1].name.localeCompare(b[1].name));
|
||||
const filtered = getFilteredEntries();
|
||||
|
||||
const groupTabsHtml = groups.length > 0
|
||||
? `<div class="group-tabs">
|
||||
@@ -126,6 +107,27 @@ async function openEntry(id: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the visible (filtered) entry list from current state.
|
||||
function getFilteredEntries(): Array<[string, ManifestEntry]> {
|
||||
const state = getState();
|
||||
let filtered = state.entries;
|
||||
if (state.activeGroup) {
|
||||
const g = state.activeGroup.toLowerCase();
|
||||
filtered = filtered.filter(([, e]) => e.group?.toLowerCase() === g);
|
||||
}
|
||||
if (state.searchQuery) {
|
||||
const q = state.searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(([, e]) => {
|
||||
if (e.name.toLowerCase().includes(q)) return true;
|
||||
if (e.url?.toLowerCase().includes(q)) return true;
|
||||
if (e.username?.toLowerCase().includes(q)) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
filtered.sort((a, b) => a[1].name.localeCompare(b[1].name));
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function handleListKeydown(e: KeyboardEvent): void {
|
||||
const state = getState();
|
||||
const target = e.target as HTMLElement;
|
||||
@@ -143,9 +145,11 @@ function handleListKeydown(e: KeyboardEvent): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = getFilteredEntries();
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
const max = state.entries.length - 1;
|
||||
const max = Math.max(filtered.length - 1, 0);
|
||||
setState({ selectedIndex: Math.min(state.selectedIndex + 1, max) });
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +162,6 @@ function handleListKeydown(e: KeyboardEvent): void {
|
||||
|
||||
if (e.key === 'Enter' && !isSearch) {
|
||||
e.preventDefault();
|
||||
const filtered = state.entries;
|
||||
if (filtered[state.selectedIndex]) {
|
||||
openEntry(filtered[state.selectedIndex][0]);
|
||||
}
|
||||
@@ -166,8 +169,8 @@ function handleListKeydown(e: KeyboardEvent): void {
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
// Remove listener to avoid stacking.
|
||||
document.removeEventListener('keydown', handleListKeydown);
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user