9134e7d216
CC12: renderer views/ (5 screens + Onboarding + settings cards) + lib/ (theme/thumb/datetime/id/qualityOptions/useClipboardLink/queueStats); main core/ (buildArgs/validation/indexerCore/ytdlpPolicy). git mv preserved history; all src+test imports updated. Build emits view chunks from views/, 344 tests + typecheck green, live probe rendered all screens. CC7: wc/onProgress is the leading arg everywhere — downloadAppUpdate(wc,url), indexSource(onProgress,url,signal), indexSourceCancelable(onProgress,url). CC10: closed by-design — jsonStore backs all records; settings stay in electron-store for DPAPI secret encryption (a deliberate two-store split). Also closes the UI24/W4 context-menu cross-reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
45 lines
1.9 KiB
TypeScript
45 lines
1.9 KiB
TypeScript
import type { CommandTemplate } from '@shared/ipc'
|
||
import { isTemplateLike } from './core/validation'
|
||
import { createJsonStore } from './jsonStore'
|
||
import { TEMPLATES_MAX } from './constants'
|
||
|
||
// Plain JSON in userData, same shape as history.ts. Atomic writes / corruption
|
||
// backup / caching come from the shared jsonStore (R1–R3).
|
||
// A persisted template entry must at least be an object carrying an id (see
|
||
// isTemplateLike in validation.ts); everything else is coerced by sanitize().
|
||
// Drop anything that isn't, so a hand-edited templates.json can't inject
|
||
// malformed entries. (audit S5)
|
||
const store = createJsonStore('templates.json', isTemplateLike, TEMPLATES_MAX)
|
||
|
||
export function listTemplates(): CommandTemplate[] {
|
||
// Normalise each surviving entry through sanitize() so name/args are always
|
||
// well-formed strings regardless of what was on disk.
|
||
return store.read().map(sanitize)
|
||
}
|
||
|
||
function sanitize(t: CommandTemplate): CommandTemplate {
|
||
const urlPattern = typeof t.urlPattern === 'string' ? t.urlPattern.trim() : ''
|
||
return {
|
||
id: String(t.id),
|
||
name: (t.name ?? '').trim() || 'Untitled command',
|
||
args: (t.args ?? '').trim(),
|
||
// Only persist a urlPattern when one was provided, keeping older files clean.
|
||
...(urlPattern ? { urlPattern } : {})
|
||
}
|
||
}
|
||
|
||
/** Add a new template, or update an existing one (matched by id). */
|
||
export function saveTemplate(template: CommandTemplate): CommandTemplate[] {
|
||
const clean = sanitize(template)
|
||
return store.write([clean, ...listTemplates().filter((t) => t.id !== clean.id)])
|
||
}
|
||
|
||
export function removeTemplate(id: string): CommandTemplate[] {
|
||
return store.write(listTemplates().filter((t) => t.id !== id))
|
||
}
|
||
|
||
/** Replace the entire template list (backup restore) rather than merge-by-id. */
|
||
export function replaceTemplates(templates: CommandTemplate[]): CommandTemplate[] {
|
||
return store.write(templates.map(sanitize))
|
||
}
|