fix(ext/popup): fix reversed search, remove auto-focus, Enter opens items

- Search no longer auto-focuses; use "/" to focus it
- Typing in search no longer re-renders the entire view, just the
  item list — fixes backwards text caused by cursor reset to pos 0
- Arrow keys also update list without full re-render
- Enter opens the selected item even when search is focused

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-27 02:10:23 -04:00
parent 8f603ec069
commit 9488670b1b

View File

@@ -25,11 +25,10 @@ function typeIcon(t: ItemType): string {
} }
} }
export function renderItemList(app: HTMLElement): void { function buildRowsHtml(): string {
const state = getState(); const state = getState();
const filtered = getFilteredEntries(); const filtered = getFilteredEntries();
return filtered.length > 0
const rowsHtml = filtered.length > 0
? filtered.map(([id, e], i) => ` ? filtered.map(([id, e], i) => `
<div class="entry-row ${i === state.selectedIndex ? 'selected' : ''}" data-id="${escapeHtml(id)}" data-index="${i}"> <div class="entry-row ${i === state.selectedIndex ? 'selected' : ''}" data-id="${escapeHtml(id)}" data-index="${i}">
<span class="entry-name"><span class="type-icon" aria-hidden="true">${typeIcon(e.type)}</span> ${escapeHtml(e.title)}${e.attachment_summaries.length > 0 ? ' <span class="entry-row__attach-indicator" title="has attachments">📎</span>' : ''}</span> <span class="entry-name"><span class="type-icon" aria-hidden="true">${typeIcon(e.type)}</span> ${escapeHtml(e.title)}${e.attachment_summaries.length > 0 ? ' <span class="entry-row__attach-indicator" title="has attachments">📎</span>' : ''}</span>
@@ -37,7 +36,28 @@ export function renderItemList(app: HTMLElement): void {
</div> </div>
`).join('') `).join('')
: '<div class="empty">no items</div>'; : '<div class="empty">no items</div>';
}
function updateItemList(): void {
const list = document.getElementById('item-list');
if (list) {
list.innerHTML = buildRowsHtml();
wireRowClicks();
}
}
function wireRowClicks(): void {
document.querySelectorAll('.entry-row').forEach(row => {
row.addEventListener('click', async () => {
const id = (row as HTMLElement).dataset.id!;
document.removeEventListener('keydown', handleListKeydown);
await openItem(id);
});
});
}
export function renderItemList(app: HTMLElement): void {
const state = getState();
app.innerHTML = ` app.innerHTML = `
<div class="search-bar"> <div class="search-bar">
<input type="text" id="search-input" placeholder="/ search..." value="${escapeHtml(state.searchQuery)}"> <input type="text" id="search-input" placeholder="/ search..." value="${escapeHtml(state.searchQuery)}">
@@ -50,7 +70,7 @@ export function renderItemList(app: HTMLElement): void {
<button class="btn" id="lock-btn" style="font-size:11px;">lock</button> <button class="btn" id="lock-btn" style="font-size:11px;">lock</button>
</div> </div>
<div class="entry-list" id="item-list"> <div class="entry-list" id="item-list">
${rowsHtml} ${buildRowsHtml()}
</div> </div>
<div class="keyhints"> <div class="keyhints">
<span><kbd>/</kbd> search</span> <span><kbd>/</kbd> search</span>
@@ -64,7 +84,12 @@ export function renderItemList(app: HTMLElement): void {
const searchInput = document.getElementById('search-input') as HTMLInputElement | null; const searchInput = document.getElementById('search-input') as HTMLInputElement | null;
searchInput?.addEventListener('input', () => { searchInput?.addEventListener('input', () => {
setState({ searchQuery: searchInput.value, selectedIndex: 0 }); const state2 = getState();
state2.searchQuery = searchInput.value;
state2.selectedIndex = 0;
const list = document.getElementById('item-list');
if (list) list.innerHTML = buildRowsHtml();
wireRowClicks();
}); });
document.getElementById('new-btn')?.addEventListener('click', () => { document.getElementById('new-btn')?.addEventListener('click', () => {
@@ -98,21 +123,9 @@ export function renderItemList(app: HTMLElement): void {
showSettingsPicker(e.currentTarget as HTMLElement); showSettingsPicker(e.currentTarget as HTMLElement);
}); });
// Item row clicks. wireRowClicks();
const rows = app.querySelectorAll('.entry-row');
rows.forEach(row => {
row.addEventListener('click', async () => {
const id = (row as HTMLElement).dataset.id!;
document.removeEventListener('keydown', handleListKeydown);
await openItem(id);
});
});
// Keyboard navigation.
document.addEventListener('keydown', handleListKeydown); document.addEventListener('keydown', handleListKeydown);
// Focus search on open.
searchInput?.focus();
} }
async function openItem(id: ItemId): Promise<void> { async function openItem(id: ItemId): Promise<void> {
@@ -195,17 +208,19 @@ function handleListKeydown(e: KeyboardEvent): void {
if (e.key === 'ArrowDown') { if (e.key === 'ArrowDown') {
e.preventDefault(); e.preventDefault();
const max = Math.max(filtered.length - 1, 0); const max = Math.max(filtered.length - 1, 0);
setState({ selectedIndex: Math.min(state.selectedIndex + 1, max) }); state.selectedIndex = Math.min(state.selectedIndex + 1, max);
updateItemList();
return; return;
} }
if (e.key === 'ArrowUp') { if (e.key === 'ArrowUp') {
e.preventDefault(); e.preventDefault();
setState({ selectedIndex: Math.max(state.selectedIndex - 1, 0) }); state.selectedIndex = Math.max(state.selectedIndex - 1, 0);
updateItemList();
return; return;
} }
if (e.key === 'Enter' && !isSearch) { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
const selected = filtered[state.selectedIndex]; const selected = filtered[state.selectedIndex];
if (selected) { if (selected) {