feat: add settings view with capture toggle and blacklist management
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
extension/src/popup/components/settings.ts
Normal file
98
extension/src/popup/components/settings.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/// Settings view — capture toggle, prompt style, and blacklist management.
|
||||||
|
|
||||||
|
import { sendMessage, navigate, escapeHtml } from '../popup';
|
||||||
|
import type { IdfotoSettings } from '../../shared/types';
|
||||||
|
|
||||||
|
export async function renderSettings(app: HTMLElement): Promise<void> {
|
||||||
|
app.innerHTML = '<div class="pad" style="text-align:center; padding-top:20px;"><span class="spinner"></span></div>';
|
||||||
|
|
||||||
|
// Load settings and blacklist in parallel
|
||||||
|
const [settingsResp, blacklistResp] = await Promise.all([
|
||||||
|
sendMessage({ type: 'get_settings' }),
|
||||||
|
sendMessage({ type: 'get_blacklist' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const settings: IdfotoSettings = settingsResp.ok
|
||||||
|
? (settingsResp.data as { settings: IdfotoSettings }).settings
|
||||||
|
: { captureEnabled: false, captureStyle: 'bar' };
|
||||||
|
|
||||||
|
const blacklist: string[] = blacklistResp.ok
|
||||||
|
? (blacklistResp.data as { blacklist: string[] }).blacklist
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const blacklistHtml = blacklist.length > 0
|
||||||
|
? blacklist.map((h) => `
|
||||||
|
<div style="display:flex; align-items:center; justify-content:space-between; padding:4px 0; border-bottom:1px solid #21262d;">
|
||||||
|
<span style="font-size:12px; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(h)}</span>
|
||||||
|
<button class="idfoto-remove-bl" data-hostname="${escapeHtml(h)}" style="
|
||||||
|
background:transparent; color:#f85149; border:none; cursor:pointer;
|
||||||
|
font-size:11px; padding:2px 6px;
|
||||||
|
">remove</button>
|
||||||
|
</div>
|
||||||
|
`).join('')
|
||||||
|
: '<p class="muted" style="font-size:12px;">no blacklisted sites</p>';
|
||||||
|
|
||||||
|
app.innerHTML = `
|
||||||
|
<div class="pad" style="padding-top:12px;">
|
||||||
|
<div style="display:flex; align-items:center; margin-bottom:16px;">
|
||||||
|
<button id="settings-back" class="btn" style="font-size:11px; margin-right:8px;">←</button>
|
||||||
|
<span style="font-size:14px; font-weight:600;">settings</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom:16px;">
|
||||||
|
<label style="display:flex; align-items:center; gap:8px; cursor:pointer; font-size:13px;">
|
||||||
|
<input type="checkbox" id="capture-enabled" ${settings.captureEnabled ? 'checked' : ''}>
|
||||||
|
auto-detect logins
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom:16px;">
|
||||||
|
<div style="font-size:12px; color:#8b949e; margin-bottom:6px;">prompt style</div>
|
||||||
|
<div style="display:flex; gap:8px;">
|
||||||
|
<button id="style-bar" class="btn" style="font-size:11px; ${settings.captureStyle === 'bar' ? 'background:#1f6feb; color:#fff;' : ''}">bar</button>
|
||||||
|
<button id="style-toast" class="btn" style="font-size:11px; ${settings.captureStyle === 'toast' ? 'background:#1f6feb; color:#fff;' : ''}">toast</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="font-size:12px; color:#8b949e; margin-bottom:6px;">blacklisted sites</div>
|
||||||
|
<div id="blacklist-container">
|
||||||
|
${blacklistHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
document.getElementById('settings-back')?.addEventListener('click', () => {
|
||||||
|
navigate('locked');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Capture enabled toggle
|
||||||
|
document.getElementById('capture-enabled')?.addEventListener('change', async (e) => {
|
||||||
|
const checked = (e.target as HTMLInputElement).checked;
|
||||||
|
await sendMessage({ type: 'update_settings', settings: { captureEnabled: checked } });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Style buttons
|
||||||
|
document.getElementById('style-bar')?.addEventListener('click', async () => {
|
||||||
|
await sendMessage({ type: 'update_settings', settings: { captureStyle: 'bar' } });
|
||||||
|
renderSettings(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('style-toast')?.addEventListener('click', async () => {
|
||||||
|
await sendMessage({ type: 'update_settings', settings: { captureStyle: 'toast' } });
|
||||||
|
renderSettings(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Blacklist remove buttons
|
||||||
|
document.querySelectorAll('.idfoto-remove-bl').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const hostname = (btn as HTMLElement).dataset.hostname;
|
||||||
|
if (hostname) {
|
||||||
|
await sendMessage({ type: 'remove_blacklist', hostname });
|
||||||
|
renderSettings(app);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -52,5 +52,5 @@ export function renderUnlock(app: HTMLElement): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settingsBtn = document.getElementById('settings-btn');
|
const settingsBtn = document.getElementById('settings-btn');
|
||||||
settingsBtn?.addEventListener('click', () => navigate('setup'));
|
settingsBtn?.addEventListener('click', () => navigate('settings'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { renderEntryList } from './components/entry-list';
|
|||||||
import { renderEntryDetail } from './components/entry-detail';
|
import { renderEntryDetail } from './components/entry-detail';
|
||||||
import { renderEntryForm } from './components/entry-form';
|
import { renderEntryForm } from './components/entry-form';
|
||||||
import { renderSetupWizard } from './components/setup-wizard';
|
import { renderSetupWizard } from './components/setup-wizard';
|
||||||
|
import { renderSettings } from './components/settings';
|
||||||
|
|
||||||
// --- Escape HTML to prevent XSS ---
|
// --- Escape HTML to prevent XSS ---
|
||||||
export function escapeHtml(str: string): string {
|
export function escapeHtml(str: string): string {
|
||||||
@@ -20,7 +21,7 @@ export function escapeHtml(str: string): string {
|
|||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
|
|
||||||
export type View = 'setup' | 'locked' | 'list' | 'detail' | 'add' | 'edit';
|
export type View = 'setup' | 'locked' | 'list' | 'detail' | 'add' | 'edit' | 'settings';
|
||||||
|
|
||||||
export interface PopupState {
|
export interface PopupState {
|
||||||
view: View;
|
view: View;
|
||||||
@@ -96,6 +97,9 @@ function render(): void {
|
|||||||
case 'edit':
|
case 'edit':
|
||||||
renderEntryForm(app, 'edit');
|
renderEntryForm(app, 'edit');
|
||||||
break;
|
break;
|
||||||
|
case 'settings':
|
||||||
|
renderSettings(app);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user