diff --git a/extension/src/popup/components/item-form.ts b/extension/src/popup/components/item-form.ts index c9d8485..e0370a9 100644 --- a/extension/src/popup/components/item-form.ts +++ b/extension/src/popup/components/item-form.ts @@ -1,7 +1,7 @@ /// Typed-item add/edit form dispatcher. Each type's renderForm lives in /// its own module under ./types/. Document stays "coming soon" until γ. -import { navigate, getState, setState, escapeHtml } from '../popup'; +import { navigate, getState, setState, escapeHtml, popOutToTab } from '../popup'; import type { Item, ItemType } from '../../shared/types'; const TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: string }> = [ @@ -57,6 +57,8 @@ function renderTypeSelection(app: HTMLElement): void {

new item

+ +
${TYPE_OPTIONS.map((opt) => ` @@ -70,6 +72,7 @@ function renderTypeSelection(app: HTMLElement): void { `; document.getElementById('back-btn')?.addEventListener('click', () => navigate('list')); + document.getElementById('popout-btn')?.addEventListener('click', popOutToTab); document.querySelectorAll('[data-type]').forEach((btn) => { btn.addEventListener('click', () => { diff --git a/extension/src/popup/components/item-list.ts b/extension/src/popup/components/item-list.ts index 0d7cfb6..111b1ef 100644 --- a/extension/src/popup/components/item-list.ts +++ b/extension/src/popup/components/item-list.ts @@ -223,102 +223,6 @@ function handleListKeydown(e: KeyboardEvent): void { } // ---------------------------------------------------------------------- -// New-item type picker popover -// ---------------------------------------------------------------------- - -const NEW_TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: string; disabled?: boolean; tooltip?: string }> = [ - { type: 'login', icon: '🔑', label: 'login' }, - { type: 'secure_note', icon: '📝', label: 'secure note' }, - { type: 'identity', icon: '🪪', label: 'identity' }, - { type: 'card', icon: '💳', label: 'card' }, - { type: 'key', icon: '🗝', label: 'key' }, - { type: 'totp', icon: '⏱', label: 'totp' }, - { type: 'document', icon: '📄', label: 'document' }, -]; - -function showNewTypePicker(anchor: HTMLElement): void { - document.querySelectorAll('.new-type-picker').forEach((el) => el.remove()); - - const picker = document.createElement('div'); - picker.className = 'new-type-picker'; - Object.assign(picker.style, { - position: 'absolute', - background: '#161b22', - border: '1px solid #30363d', - borderRadius: '6px', - boxShadow: '0 4px 12px rgba(0,0,0,0.4)', - padding: '4px', - minWidth: '160px', - maxHeight: '280px', - overflowY: 'auto', - zIndex: '999999', - fontSize: '12px', - }); - - const rect = anchor.getBoundingClientRect(); - const dropdownHeight = 220; // approx height for 7 items - const spaceBelow = window.innerHeight - rect.bottom; - if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) { - picker.style.bottom = `${window.innerHeight - rect.top + 4}px`; - } else { - picker.style.top = `${rect.bottom + 4}px`; - } - picker.style.left = `${rect.left}px`; - - for (const opt of NEW_TYPE_OPTIONS) { - const row = document.createElement('div'); - Object.assign(row.style, { - padding: '6px 10px', - cursor: opt.disabled ? 'not-allowed' : 'pointer', - color: opt.disabled ? '#484f58' : '#c9d1d9', - borderRadius: '4px', - display: 'flex', alignItems: 'center', gap: '8px', - }); - if (opt.tooltip) row.title = opt.tooltip; - const iconSpan = document.createElement('span'); - Object.assign(iconSpan.style, { fontSize: '14px', width: '16px', display: 'inline-block', textAlign: 'center' }); - iconSpan.textContent = opt.icon; - const labelSpan = document.createElement('span'); - labelSpan.textContent = opt.label; - row.appendChild(iconSpan); - row.appendChild(labelSpan); - if (!opt.disabled) { - row.addEventListener('mouseenter', () => { row.style.background = '#21262d'; }); - row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; }); - row.addEventListener('click', (ev) => { - ev.stopPropagation(); - picker.remove(); - document.removeEventListener('click', closeOnOutside); - document.removeEventListener('keydown', closeOnEsc); - setState({ newType: opt.type }); - navigate('add'); - }); - } - picker.appendChild(row); - } - - document.body.appendChild(picker); - - const closeOnOutside = (ev: MouseEvent) => { - if (!picker.contains(ev.target as Node)) { - picker.remove(); - document.removeEventListener('click', closeOnOutside); - document.removeEventListener('keydown', closeOnEsc); - } - }; - const closeOnEsc = (ev: KeyboardEvent) => { - if (ev.key === 'Escape') { - picker.remove(); - document.removeEventListener('click', closeOnOutside); - document.removeEventListener('keydown', closeOnEsc); - } - }; - setTimeout(() => { - document.addEventListener('click', closeOnOutside); - document.addEventListener('keydown', closeOnEsc); - }, 0); -} - // ---------------------------------------------------------------------- // Settings picker popover (device vs vault) // ---------------------------------------------------------------------- diff --git a/extension/src/popup/components/types/card.ts b/extension/src/popup/components/types/card.ts index 68438ed..e36ffb8 100644 --- a/extension/src/popup/components/types/card.ts +++ b/extension/src/popup/components/types/card.ts @@ -1,7 +1,7 @@ /// Card: number / holder / expiry MonthYear / cvv / pin / kind. /// Detail view has a styled card-silhouette signature block. -import { getState, setState, sendMessage, navigate, escapeHtml } from '../../popup'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../popup'; import type { Item, ItemId, ManifestEntry, CardKind, Section, AttachmentRef } from '../../../shared/types'; import { renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections, @@ -173,7 +173,11 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite app.innerHTML = `
-
${mode === 'add' ? 'new card' : 'edit card'}
+
+
${mode === 'add' ? 'new card' : 'edit card'}
+ + +
${state.error ? `
${escapeHtml(state.error)}
` : ''}
@@ -235,6 +239,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite setState({ error: null }); navigate(mode === 'edit' ? 'detail' : 'list'); }); + document.getElementById('popout-btn')?.addEventListener('click', popOutToTab); document.getElementById('save-btn')?.addEventListener('click', async () => { await saveCard(mode, existing, sectionsDraft, attachmentsDraft); }); diff --git a/extension/src/popup/components/types/document.ts b/extension/src/popup/components/types/document.ts index df25aee..4eb891b 100644 --- a/extension/src/popup/components/types/document.ts +++ b/extension/src/popup/components/types/document.ts @@ -2,7 +2,7 @@ /// notes/tags + optional supplementary attachments. /// Primary attachment is referenced by ID from the item's attachments array. -import { getState, setState, sendMessage, navigate, escapeHtml } from '../../popup'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../popup'; import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types'; import { renderSectionsEditor, wireSectionsEditor, @@ -84,7 +84,11 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite app.innerHTML = `
-
${isEdit ? 'edit document' : 'new document'}
+
+
${isEdit ? 'edit document' : 'new document'}
+ + +
${state.error ? `
${escapeHtml(state.error)}
` : ''}
@@ -180,6 +184,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite setState({ error: null }); navigate(isEdit ? 'detail' : 'list'); }); + document.getElementById('popout-btn')?.addEventListener('click', popOutToTab); document.getElementById('save-btn')?.addEventListener('click', async () => { await saveDocument(mode, existing, primaryId, attachmentsDraft, sectionsDraft); diff --git a/extension/src/popup/components/types/identity.ts b/extension/src/popup/components/types/identity.ts index b3f0138..9f9c1a7 100644 --- a/extension/src/popup/components/types/identity.ts +++ b/extension/src/popup/components/types/identity.ts @@ -1,7 +1,7 @@ /// Identity: full_name, address (multiline), phone, email, date_of_birth. /// Detail view shows a "profile card" signature block + plain rows. -import { getState, setState, sendMessage, navigate, escapeHtml } from '../../popup'; +import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../popup'; import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types'; import { renderRow, renderSignatureBlock, wireFieldHandlers, renderSections, @@ -133,7 +133,11 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite app.innerHTML = `
-
${mode === 'add' ? 'new identity' : 'edit identity'}
+
+
${mode === 'add' ? 'new identity' : 'edit identity'}
+ + +
${state.error ? `
${escapeHtml(state.error)}
` : ''}
@@ -190,6 +194,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite setState({ error: null }); navigate(mode === 'edit' ? 'detail' : 'list'); }); + document.getElementById('popout-btn')?.addEventListener('click', popOutToTab); document.getElementById('save-btn')?.addEventListener('click', async () => { await saveIdentity(mode, existing, sectionsDraft, attachmentsDraft); }); diff --git a/extension/src/popup/components/types/key.ts b/extension/src/popup/components/types/key.ts index a6d7f59..3b4eee5 100644 --- a/extension/src/popup/components/types/key.ts +++ b/extension/src/popup/components/types/key.ts @@ -2,7 +2,7 @@ /// Form's key_material textarea uses CSS text-security to mask characters /// since