diff --git a/extension/src/vault/vault-list.ts b/extension/src/vault/vault-list.ts new file mode 100644 index 0000000..7e6b39c --- /dev/null +++ b/extension/src/vault/vault-list.ts @@ -0,0 +1,52 @@ +// Vault-tab list column: renders the middle list pane (row markup, empty +// state, and the row-click → drawer selection). Receives the VaultController +// (`ctx`) and reaches sibling concerns through it; pure helpers come from +// vault-context. Imports only from shared/ and vault-context. + +import type { ItemId, ManifestEntry, ItemType } from '../shared/types'; +import { relativeTime } from '../shared/relative-time'; +import { + type VaultController, escapeHtml, typeIcon, getFilteredEntries, +} from './vault-context'; + +export function renderListPane(ctx: VaultController): void { + const pane = document.getElementById('vault-list-pane'); + if (!pane) return; + + const group = ctx.state.activeGroup as ItemType | null; + let items = getFilteredEntries(ctx.state); + if (group) items = items.filter(([, e]) => e.type === group); + + if (items.length === 0) { + pane.innerHTML = ` +
+ +
${ctx.state.searchQuery ? `No results for "${escapeHtml(ctx.state.searchQuery)}"` : 'No items yet'}
+
${ctx.state.searchQuery ? 'Try a shorter search term.' : 'Click + new item to get started.'}
+
+ `; + return; + } + + pane.innerHTML = items.map(([id, e]: [ItemId, ManifestEntry]) => { + const sel = id === ctx.state.selectedId ? ' vault-list-row--selected' : ''; + const subtitle = (e as any).icon_hint ?? (e.tags?.length > 0 ? e.tags.join(', ') : ''); + const modifiedAgo = e.modified ? relativeTime(e.modified) : ''; + return ` +
+ +
+
${escapeHtml(e.title)}
+ ${subtitle ? `
${escapeHtml(subtitle)}
` : ''} +
+ ${modifiedAgo ? `
${escapeHtml(modifiedAgo)}
` : ''} +
+ `; + }).join(''); + + pane.querySelectorAll('.vault-list-row').forEach((row) => { + row.addEventListener('click', async () => { + await ctx.selectItemForDrawer(row.dataset.id!); + }); + }); +} diff --git a/extension/src/vault/vault.ts b/extension/src/vault/vault.ts index 1521dec..825ef9d 100644 --- a/extension/src/vault/vault.ts +++ b/extension/src/vault/vault.ts @@ -10,7 +10,6 @@ import type { } from '../shared/types'; import { registerHost } from '../shared/state'; import { type ErrorCta } from '../shared/error-copy'; -import { relativeTime } from '../shared/relative-time'; import { renderItemDetail } from '../popup/components/item-detail'; import { renderItemForm } from '../popup/components/item-form'; import { renderTrash, teardown as teardownTrash } from '../popup/components/trash'; @@ -23,7 +22,7 @@ import { renderBackupPanel, teardown as teardownBackup } from './components/back import { renderImportPanel, teardown as teardownImport } from './components/import-panel'; import { type VaultController, type VaultState, type VaultView, type HashRoute, - escapeHtml, typeIcon, getFilteredEntries, + escapeHtml, } from './vault-context'; import { render, applyShellViewClass, @@ -31,6 +30,7 @@ import { wireSessionExpiredListener, } from './vault-shell'; import { wireSidebar, renderSidebarCategories } from './vault-sidebar'; +import { renderListPane } from './vault-list'; // --------------------------------------------------------------------------- // Helpers @@ -144,7 +144,7 @@ const ctx: VaultController = { sendMessage, render: () => render(ctx), renderPane: () => renderPane(), - renderListPane: () => renderListPane(), + renderListPane: () => renderListPane(ctx), renderSidebarCategories: () => renderSidebarCategories(ctx), renderDrawer: (item) => renderDrawer(item), applyShellViewClass: () => applyShellViewClass(ctx), @@ -173,7 +173,7 @@ registerHost({ setHash(view as VaultView); applyShellViewClass(ctx); renderSidebarCategories(ctx); - if (state.view === 'list') renderListPane(); + if (state.view === 'list') renderListPane(ctx); renderPane(); }, sendMessage, @@ -281,7 +281,7 @@ function renderDrawer(item: Item): void { document.getElementById('drawer-close-btn')?.addEventListener('click', () => { closeDrawer(); - renderListPane(); + renderListPane(ctx); }); document.getElementById('drawer-edit-btn')?.addEventListener('click', () => { @@ -304,57 +304,11 @@ async function selectItemForDrawer(id: string): Promise { state.selectedItem = data.item; state.drawerOpen = true; renderSidebarCategories(ctx); - renderListPane(); + renderListPane(ctx); renderDrawer(data.item); openDrawer(); } -// --------------------------------------------------------------------------- -// List pane -// --------------------------------------------------------------------------- - -function renderListPane(): void { - const pane = document.getElementById('vault-list-pane'); - if (!pane) return; - - const group = state.activeGroup as ItemType | null; - let items = getFilteredEntries(state); - if (group) items = items.filter(([, e]) => e.type === group); - - if (items.length === 0) { - pane.innerHTML = ` -
- -
${state.searchQuery ? `No results for "${escapeHtml(state.searchQuery)}"` : 'No items yet'}
-
${state.searchQuery ? 'Try a shorter search term.' : 'Click + new item to get started.'}
-
- `; - return; - } - - pane.innerHTML = items.map(([id, e]) => { - const sel = id === state.selectedId ? ' vault-list-row--selected' : ''; - const subtitle = (e as any).icon_hint ?? (e.tags?.length > 0 ? e.tags.join(', ') : ''); - const modifiedAgo = e.modified ? relativeTime(e.modified) : ''; - return ` -
- -
-
${escapeHtml(e.title)}
- ${subtitle ? `
${escapeHtml(subtitle)}
` : ''} -
- ${modifiedAgo ? `
${escapeHtml(modifiedAgo)}
` : ''} -
- `; - }).join(''); - - pane.querySelectorAll('.vault-list-row').forEach((row) => { - row.addEventListener('click', async () => { - await selectItemForDrawer(row.dataset.id!); - }); - }); -} - // --------------------------------------------------------------------------- // Platform-aware save hint // --------------------------------------------------------------------------- @@ -589,7 +543,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (state.selectedId === route.id && state.selectedItem) { renderPane(); renderSidebarCategories(ctx); - if (state.view === 'list') renderListPane(); + if (state.view === 'list') renderListPane(ctx); return; } // Need to fetch the item @@ -601,7 +555,7 @@ document.addEventListener('DOMContentLoaded', async () => { state.selectedId = null; state.selectedItem = null; renderSidebarCategories(ctx); - if (state.view === 'list') renderListPane(); + if (state.view === 'list') renderListPane(ctx); renderPane(); }); }); @@ -620,7 +574,7 @@ async function selectItem(id: ItemId): Promise { state.loading = false; setHash('detail', id); renderSidebarCategories(ctx); - renderListPane(); + renderListPane(ctx); renderPane(); } else { state.loading = false;