Files
relicario/extension/src/popup/components/item-detail.ts
adlee-was-taken 673981379e feat(ext/popup): Totp view + form (countdown ring, Steam toggle)
Detail view renders a signature block with a large monospace rotating code and
a thin SVG countdown ring that sweeps via CSS transition. The ticker polls
get_totp every second and is stopped on teardown (back/edit/trash/Escape/e/d/t).

Form has a two-button kind toggle (TOTP / Steam Guard) that re-renders in place
while preserving entered values. TOTP uses digits=6 kind='totp'; Steam uses
digits=5 kind='steam'. Both default to algorithm='sha1' period_seconds=30.

Keyboard shortcuts on detail: Escape=back, e=edit, d=trash, t=copy-code.
Guarded against stealing keystrokes from editable targets.

Wires totp.renderDetail / totp.renderForm into both dispatchers and calls
totp.teardown() alongside the other types so tickers can't leak across views.

Closes T8 of the extension 1C-β1 plan (5/5 typed-item modules in place;
only T9 picker and T10 acceptance remain).
2026-04-23 22:54:49 -04:00

49 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// Typed-item detail view dispatcher. Each type's renderDetail lives in
/// its own module under ./types/. Document stays "coming soon" until γ.
import { navigate } from '../popup';
import type { Item } from '../../shared/types';
import { getState } from '../popup';
import * as login from './types/login';
import * as secureNote from './types/secure-note';
import * as identity from './types/identity';
import * as card from './types/card';
import * as key from './types/key';
import * as totp from './types/totp';
export async function renderItemDetail(app: HTMLElement): Promise<void> {
// Tear down any tickers/handlers from a previous detail render before
// the next one boots up. Each type module owns its own teardown; we
// call all of them since the dispatcher doesn't know which was active.
login.teardown();
secureNote.teardown();
identity.teardown();
card.teardown();
key.teardown();
totp.teardown();
const item = getState().selectedItem;
if (!item) { navigate('list'); return; }
switch (item.type) {
case 'login': return login.renderDetail(app, item);
case 'secure_note': return secureNote.renderDetail(app, item);
case 'identity': return identity.renderDetail(app, item);
case 'card': return card.renderDetail(app, item);
case 'key': return key.renderDetail(app, item);
case 'totp': return totp.renderDetail(app, item);
case 'document': return renderComingSoon(app, item);
}
}
function renderComingSoon(app: HTMLElement, item: Item): void {
app.innerHTML = `
<div class="pad">
<div class="detail-title" style="margin-bottom:16px;">${item.title}</div>
<p class="muted">The <strong>${item.type}</strong> item type is not editable in the extension yet.</p>
<div class="form-actions"><button class="btn" id="back-btn">back</button></div>
</div>
`;
document.getElementById('back-btn')?.addEventListener('click', () => navigate('list'));
}