feat(ext/popup): ⚙ picker → device/vault settings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,7 +93,10 @@ export function renderItemList(app: HTMLElement): void {
|
|||||||
navigate('locked');
|
navigate('locked');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('settings-btn')?.addEventListener('click', () => navigate('settings'));
|
document.getElementById('settings-btn')?.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showSettingsPicker(e.currentTarget as HTMLElement);
|
||||||
|
});
|
||||||
|
|
||||||
// Item row clicks.
|
// Item row clicks.
|
||||||
const rows = app.querySelectorAll('.entry-row');
|
const rows = app.querySelectorAll('.entry-row');
|
||||||
@@ -307,3 +310,80 @@ function showNewTypePicker(anchor: HTMLElement): void {
|
|||||||
document.addEventListener('keydown', closeOnEsc);
|
document.addEventListener('keydown', closeOnEsc);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Settings picker popover (device vs vault)
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
const SETTINGS_OPTIONS: Array<{ view: 'settings' | 'settings-vault'; icon: string; label: string }> = [
|
||||||
|
{ view: 'settings', icon: '🖥', label: 'device settings' },
|
||||||
|
{ view: 'settings-vault', icon: '🔐', label: 'vault settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function showSettingsPicker(anchor: HTMLElement): void {
|
||||||
|
document.querySelectorAll('.settings-picker').forEach((el) => el.remove());
|
||||||
|
|
||||||
|
const picker = document.createElement('div');
|
||||||
|
picker.className = 'settings-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: '170px',
|
||||||
|
zIndex: '999999',
|
||||||
|
fontSize: '12px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rect = anchor.getBoundingClientRect();
|
||||||
|
picker.style.top = `${rect.bottom + 4}px`;
|
||||||
|
picker.style.left = `${rect.left}px`;
|
||||||
|
|
||||||
|
for (const opt of SETTINGS_OPTIONS) {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
Object.assign(row.style, {
|
||||||
|
padding: '6px 10px', cursor: 'pointer', color: '#c9d1d9',
|
||||||
|
borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '8px',
|
||||||
|
});
|
||||||
|
const iconSpan = document.createElement('span');
|
||||||
|
iconSpan.textContent = opt.icon;
|
||||||
|
Object.assign(iconSpan.style, { fontSize: '14px', width: '16px', textAlign: 'center' });
|
||||||
|
const labelSpan = document.createElement('span');
|
||||||
|
labelSpan.textContent = opt.label;
|
||||||
|
row.appendChild(iconSpan);
|
||||||
|
row.appendChild(labelSpan);
|
||||||
|
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);
|
||||||
|
navigate(opt.view);
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { renderItemList } from './components/item-list';
|
|||||||
import { renderItemDetail } from './components/item-detail';
|
import { renderItemDetail } from './components/item-detail';
|
||||||
import { renderItemForm } from './components/item-form';
|
import { renderItemForm } from './components/item-form';
|
||||||
import { renderSettings } from './components/settings';
|
import { renderSettings } from './components/settings';
|
||||||
|
import { renderVaultSettings } from './components/settings-vault';
|
||||||
|
|
||||||
// --- Escape HTML to prevent XSS ---
|
// --- Escape HTML to prevent XSS ---
|
||||||
export function escapeHtml(str: string): string {
|
export function escapeHtml(str: string): string {
|
||||||
@@ -146,6 +147,9 @@ function render(): void {
|
|||||||
case 'settings':
|
case 'settings':
|
||||||
renderSettings(app);
|
renderSettings(app);
|
||||||
break;
|
break;
|
||||||
|
case 'settings-vault':
|
||||||
|
renderVaultSettings(app);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user