refactor(ext/vault): extract vault-shell.ts + introduce VaultController ctx (Plan C Phase 4)
Introduces vault-context.ts (VaultView/HashRoute/VaultState types, the VaultController contract, and the pure helpers escapeHtml/typeIcon/typeLabel/ getFilteredEntries). Extracts the shell concerns — render entry, lock screen, 3-column shell scaffolding, type picker panel, color-scheme apply, and the session_expired listener — into vault-shell.ts. vault.ts now assembles the ctx object and delegates shell rendering through it. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
122
extension/src/vault/vault-context.ts
Normal file
122
extension/src/vault/vault-context.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// Shared contract for the vault-tab modules. vault.ts owns the state
|
||||
// singleton and assembles the VaultController; each vault-* module receives
|
||||
// it as `ctx`. This module sits at the bottom of the dependency graph —
|
||||
// it imports only from shared/, never from vault.ts or its sibling modules.
|
||||
|
||||
import type { Request, Response } from '../shared/messages';
|
||||
import type {
|
||||
ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest,
|
||||
} from '../shared/types';
|
||||
import {
|
||||
GLYPH_TYPE_LOGIN, GLYPH_TYPE_SECURE_NOTE, GLYPH_TYPE_TOTP,
|
||||
GLYPH_TYPE_CARD, GLYPH_TYPE_IDENTITY, GLYPH_TYPE_KEY, GLYPH_TYPE_DOCUMENT,
|
||||
} from '../shared/glyphs';
|
||||
|
||||
export type VaultView =
|
||||
| 'list' | 'detail' | 'add' | 'edit' | 'trash' | 'devices' | 'settings'
|
||||
| 'settings-vault' | 'field-history' | 'history' | 'backup' | 'import';
|
||||
|
||||
export interface HashRoute {
|
||||
view: VaultView;
|
||||
id?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface VaultState {
|
||||
unlocked: boolean;
|
||||
view: VaultView;
|
||||
entries: Array<[ItemId, ManifestEntry]>;
|
||||
selectedId: ItemId | null;
|
||||
selectedItem: Item | null;
|
||||
selectedIndex: number;
|
||||
searchQuery: string;
|
||||
activeGroup: string | null;
|
||||
drawerOpen: boolean;
|
||||
typePanelOpen: boolean;
|
||||
vaultSettings: VaultSettings | null;
|
||||
generatorDefaults: GeneratorRequest | null;
|
||||
error: string | null;
|
||||
loading: boolean;
|
||||
newType: ItemType | null;
|
||||
capturedTabId: number | null;
|
||||
capturedUrl: string;
|
||||
historyItemId: ItemId | null;
|
||||
}
|
||||
|
||||
// The controller passed to every vault-* module. vault.ts builds one instance
|
||||
// and wires each hook to the function that currently lives in vault.ts (later
|
||||
// Phase-4 tasks repoint individual hooks at the extracted module functions).
|
||||
export interface VaultController {
|
||||
readonly state: VaultState;
|
||||
sendMessage(request: Request): Promise<Response>;
|
||||
render(): void;
|
||||
renderPane(): void;
|
||||
renderListPane(): void;
|
||||
renderSidebarCategories(): void;
|
||||
renderDrawer(item: Item): void;
|
||||
applyShellViewClass(): void;
|
||||
setHash(view: VaultView, param?: string): void;
|
||||
openDrawer(): void;
|
||||
closeDrawer(): void;
|
||||
selectItemForDrawer(id: string): Promise<void>;
|
||||
openTypePanel(): void;
|
||||
closeTypePanel(): void;
|
||||
wireSidebar(): void;
|
||||
loadManifest(): Promise<void>;
|
||||
}
|
||||
|
||||
// --- pure helpers (no state, no DOM dependencies beyond the args) ---
|
||||
|
||||
export function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export function typeIcon(t: ItemType): string {
|
||||
switch (t) {
|
||||
case 'login': return GLYPH_TYPE_LOGIN;
|
||||
case 'secure_note': return GLYPH_TYPE_SECURE_NOTE;
|
||||
case 'identity': return GLYPH_TYPE_IDENTITY;
|
||||
case 'card': return GLYPH_TYPE_CARD;
|
||||
case 'key': return GLYPH_TYPE_KEY;
|
||||
case 'document': return GLYPH_TYPE_DOCUMENT;
|
||||
case 'totp': return GLYPH_TYPE_TOTP;
|
||||
}
|
||||
}
|
||||
|
||||
export function typeLabel(t: ItemType): string {
|
||||
const labels: Record<ItemType, string> = {
|
||||
login: 'Login',
|
||||
secure_note: 'Secure Note',
|
||||
identity: 'Identity',
|
||||
card: 'Card',
|
||||
key: 'SSH / API Key',
|
||||
document: 'Document',
|
||||
totp: 'TOTP',
|
||||
};
|
||||
return labels[t];
|
||||
}
|
||||
|
||||
export function getFilteredEntries(
|
||||
state: VaultState,
|
||||
): Array<[ItemId, ManifestEntry]> {
|
||||
let filtered = state.entries.filter(
|
||||
([, e]) => e.trashed_at === undefined || e.trashed_at === null,
|
||||
);
|
||||
if (state.searchQuery) {
|
||||
const q = state.searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(([, e]) => {
|
||||
if (e.title.toLowerCase().includes(q)) return true;
|
||||
if (e.icon_hint?.toLowerCase().includes(q)) return true;
|
||||
if (e.group?.toLowerCase().includes(q)) return true;
|
||||
if (e.tags.some((t) => t.toLowerCase().includes(q))) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
filtered.sort((a, b) => a[1].title.localeCompare(b[1].title));
|
||||
return filtered;
|
||||
}
|
||||
Reference in New Issue
Block a user