import type { Issue } from '../gitea/types' export const KANBAN_SNAPSHOT_KEY = 'superpowers.kanbanSnapshot' export interface KanbanSnapshot { issues: Issue[] scope: 'mine' | 'all' me?: string /** Gitea login → 头像 data URI;团队视图下卡片按人贴头像。 */ avatars?: Record globalAutoReview: boolean youtrackConfigured: boolean } function isAvatarMap(raw: unknown): raw is Record { return !!raw && typeof raw === 'object' && Object.values(raw).every(v => typeof v === 'string') } export function parseKanbanSnapshot(raw: unknown): KanbanSnapshot | null { if (!raw || typeof raw !== 'object') return null const obj = raw as Record if (!Array.isArray(obj.issues)) return null if (obj.scope !== 'mine' && obj.scope !== 'all') return null return { issues: obj.issues as Issue[], scope: obj.scope, me: typeof obj.me === 'string' ? obj.me : undefined, avatars: isAvatarMap(obj.avatars) ? obj.avatars : undefined, globalAutoReview: obj.globalAutoReview === true, youtrackConfigured: obj.youtrackConfigured === true, } } /** 看板上现在摆着的工单,和它从哪来:面板这次会话里加载好的(loaded)、上次存下的快照(saved)。 */ export interface BoardIssues { issues: Issue[] from: 'loaded' | 'saved' } /** * 看板上现在摆着的工单:面板这次会话里加载好了就用它最近一次加载的,否则用上次存下的快照 * (重开窗口时看板先画的也是它);都没有时 undefined,看板还没加载过。 */ export function boardIssues(loaded: Issue[] | undefined, persisted: unknown): BoardIssues | undefined { if (loaded) return { issues: loaded, from: 'loaded' } const snapshot = parseKanbanSnapshot(persisted) return snapshot ? { issues: snapshot.issues, from: 'saved' } : undefined } /** 写入 HTML 的 JSON:小于号换成 \\u003c,避免 script 标签被提前关掉。 */ export function snapshotToScriptJson(snapshot: KanbanSnapshot): string { return JSON.stringify(snapshot).replace(/${snapshotToScriptJson(snapshot)}` }