From 882a89beddac1028c911631620135ba71bcaa84d Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 3 May 2026 21:21:52 -0400 Subject: [PATCH] =?UTF-8?q?feat(ext/vault):=20detail=20drawer=20=E2=80=94?= =?UTF-8?q?=20open/close=20state=20+=20core=20fields=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extension/src/vault/vault.ts | 121 +++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 6 deletions(-) diff --git a/extension/src/vault/vault.ts b/extension/src/vault/vault.ts index b41829c..5851cd6 100644 --- a/extension/src/vault/vault.ts +++ b/extension/src/vault/vault.ts @@ -316,25 +316,129 @@ function wireBottomSheet(): void { } // --------------------------------------------------------------------------- -// Drawer stubs (implemented in Task 10) +// Drawer (implemented in Task 10) // --------------------------------------------------------------------------- +function openDrawer(): void { + document.getElementById('vault-drawer')?.classList.add('vault-drawer--open'); +} + function closeDrawer(): void { state.drawerOpen = false; + state.selectedId = null; + state.selectedItem = null; document.getElementById('vault-drawer')?.classList.remove('vault-drawer--open'); } -function renderDrawer(_item: unknown): void { - // implemented in Task 10 +function getDrawerCoreFields(item: Item): Array<[string, string, boolean]> { + const core = item.core as Record; + if (!core) return []; + const fields: Array<[string, string, boolean]> = []; + + switch (item.type) { + case 'login': + if ('username' in core) fields.push(['username', String(core.username ?? ''), false]); + if ('password' in core) fields.push(['password', '••••••••', false]); + if ('url' in core) fields.push(['url', String(core.url ?? ''), true]); + break; + case 'card': { + if ('number' in core) fields.push(['number', String(core.number ?? ''), false]); + if ('holder' in core) fields.push(['holder', String(core.holder ?? ''), false]); + if ('expiry' in core && core.expiry) { + const exp = core.expiry as { month: number; year: number }; + fields.push(['expiry', `${String(exp.month).padStart(2, '0')}/${exp.year}`, false]); + } + if ('cvv' in core) fields.push(['cvv', '•••', false]); + if ('pin' in core) fields.push(['pin', '••••', false]); + break; + } + case 'identity': + if ('full_name' in core) fields.push(['full name', String(core.full_name ?? ''), true]); + if ('email' in core) fields.push(['email', String(core.email ?? ''), true]); + if ('phone' in core) fields.push(['phone', String(core.phone ?? ''), false]); + if ('address' in core) fields.push(['address', String(core.address ?? ''), true]); + if ('date_of_birth' in core) fields.push(['date of birth', String(core.date_of_birth ?? ''), false]); + break; + case 'key': + if ('label' in core) fields.push(['label', String(core.label ?? ''), true]); + if ('algorithm' in core) fields.push(['algorithm', String(core.algorithm ?? ''), false]); + if ('public_key' in core) fields.push(['public key', String(core.public_key ?? ''), true]); + break; + case 'secure_note': + if ('body' in core) fields.push(['body', String(core.body ?? ''), true]); + break; + case 'totp': + if ('issuer' in core) fields.push(['issuer', String(core.issuer ?? ''), false]); + if ('label' in core) fields.push(['label', String(core.label ?? ''), false]); + break; + case 'document': + if ('filename' in core) fields.push(['filename', String(core.filename ?? ''), true]); + if ('mime_type' in core) fields.push(['type', String(core.mime_type ?? ''), false]); + break; + } + + if (item.notes) fields.push(['notes', item.notes, true]); + return fields; +} + +function renderDrawer(item: Item): void { + const drawer = document.getElementById('vault-drawer'); + if (!drawer) return; + + const coreFields = getDrawerCoreFields(item); + + drawer.innerHTML = ` +
+ ${item.type.replace('_', ' ').toUpperCase()} +
+ + +
+
+
+
${escapeHtml(item.title)}
+ ${item.type === 'login' && (item.core as { url?: string }).url + ? `
${escapeHtml((item.core as { url?: string }).url ?? '')}
` + : ''} +
+ ${coreFields.map(([label, value, full]) => ` +
+
${escapeHtml(label)}
+
${escapeHtml(value)}
+
+ `).join('')} +
+
+ `; + + document.getElementById('drawer-close-btn')?.addEventListener('click', () => { + closeDrawer(); + renderListPane(); + }); + + document.getElementById('drawer-edit-btn')?.addEventListener('click', () => { + if (state.selectedId) { + setHash('edit', state.selectedId); + renderPane(); + } + }); } // --------------------------------------------------------------------------- -// Item selection stub (implemented in Task 10) +// Item selection (implemented in Task 10) // --------------------------------------------------------------------------- async function selectItemForDrawer(id: string): Promise { - // implemented in Task 10 + const resp = await sendMessage({ type: 'get_item', id }); + if (!resp.ok) return; + const data = resp.data as { item: Item }; state.selectedId = id; + state.selectedItem = data.item; + state.drawerOpen = true; + renderSidebarCategories(); + renderListPane(); + renderDrawer(data.item); + openDrawer(); } // --------------------------------------------------------------------------- @@ -382,11 +486,16 @@ function wireSidebar(): void { }); }); - // Global "/" shortcut to focus search + // Global "/" shortcut to focus search; Esc to close drawer document.addEventListener('keydown', (e) => { if (e.key === '/' && !isEditableTarget(e.target)) { e.preventDefault(); searchInput?.focus(); + return; + } + if (e.key === 'Escape' && state.drawerOpen) { + closeDrawer(); + renderListPane(); } }); }