feat(ext/vault): detail drawer — open/close state + core fields display

This commit is contained in:
adlee-was-taken
2026-05-03 21:21:52 -04:00
parent 37c20b28a6
commit 882a89bedd

View File

@@ -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<string, unknown>;
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 = `
<div class="vault-drawer__header">
<span class="vault-drawer__type-pill">${item.type.replace('_', ' ').toUpperCase()}</span>
<div class="vault-drawer__actions">
<button class="btn" id="drawer-edit-btn" style="font-size:11px;">edit</button>
<button class="vault-drawer__close" id="drawer-close-btn" title="Close (Esc)">✕</button>
</div>
</div>
<div class="vault-drawer__body">
<div class="vault-drawer__title">${escapeHtml(item.title)}</div>
${item.type === 'login' && (item.core as { url?: string }).url
? `<div class="vault-drawer__subtitle">${escapeHtml((item.core as { url?: string }).url ?? '')}</div>`
: ''}
<div class="vault-drawer__field-grid">
${coreFields.map(([label, value, full]) => `
<div class="vault-drawer__field${full ? ' vault-drawer__field--full' : ''}">
<div class="vault-drawer__field-label">${escapeHtml(label)}</div>
<div class="vault-drawer__field-value">${escapeHtml(value)}</div>
</div>
`).join('')}
</div>
</div>
`;
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<void> {
// 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();
}
});
}