// 工具四(待辦事項)的純邏輯:解析、渲染、到點判斷。讀寫與指令在 register.tsx。 export const TODO_COMMAND = 'anima-todo' export const TODO_KEY = 'todos' export const TYPES_KEY = 'todoTypes' export const DEFAULT_TYPES = ['提醒', '開發', '研究', '維護'] export type Status = 'pending' | 'reminded' | 'done' export type Todo = { id: number; type: string; at: number; task: string; project: string; status: Status; result?: string } export const USAGE = '用法:\n' + '/jsc-anima:anima-todo add <類型> <任務> 建立\n' + '/jsc-anima:anima-todo update <編號> <類型> <任務> 更新\n' + '/jsc-anima:anima-todo list 列出全部\n' + '/jsc-anima:anima-todo type <名稱> 新增自訂類型\n' + '/jsc-anima:anima-todo run <編號> 確認後執行\n' + '/jsc-anima:anima-todo done <編號> 標記完成(不執行)' const pad = (n: number) => String(n).padStart(2, '0') export const fmtTime = (ms: number): string => { const d = new Date(ms) return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } // 排程時間以本機時區解讀。 export const parseTime = (date: string | undefined, time: string | undefined): number | undefined => { const d = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date ?? '') const t = /^(\d{1,2}):(\d{2})$/.exec(time ?? '') if (!d || !t) return undefined const [y, mo, da, h, mi] = [d[1], d[2], d[3], t[1], t[2]].map(Number) as [number, number, number, number, number] const out = new Date(y, mo - 1, da, h, mi) if (out.getMonth() !== mo - 1 || out.getDate() !== da || h > 23 || mi > 59) return undefined return out.getTime() } export type Parsed = | { op: 'add'; type: string; at: number; task: string } | { op: 'update'; id: number; type: string; at: number; task: string } | { op: 'list' } | { op: 'type'; name: string } | { op: 'run' | 'done'; id: number } const spec = (w: string[]): { type: string; at: number; task: string } | undefined => { const [type, date, time, ...rest] = w const at = parseTime(date, time) const task = rest.join(' ').trim() return type && at !== undefined && task ? { type, at, task } : undefined } export const parseArgs = (args: string): Parsed | undefined => { const w = args.trim().split(/\s+/).filter(Boolean) const [op, ...rest] = w const id = Number(rest[0]) if (op === 'list' && rest.length === 0) return { op } if (op === 'type' && rest.length === 1) return { op, name: rest[0]! } if ((op === 'run' || op === 'done') && rest.length === 1 && Number.isInteger(id) && id > 0) return { op, id } if (op === 'add') { const s = spec(rest) return s ? { op, ...s } : undefined } if (op === 'update' && Number.isInteger(id) && id > 0) { const s = spec(rest.slice(1)) return s ? { op, id, ...s } : undefined } return undefined } export const dueNow = (todos: Todo[], now: number): Todo[] => todos.filter(t => t.status === 'pending' && t.at <= now) export const nextId = (todos: Todo[]): number => todos.reduce((m, t) => Math.max(m, t.id), 0) + 1 const STATUS_TEXT: Record = { pending: '待辦', reminded: '已提醒', done: '完成' } export const renderList = (todos: Todo[]): string => todos.length === 0 ? '目前沒有待辦。' : todos .slice() .sort((a, b) => a.at - b.at) .map(t => `#${t.id} [${t.type}] ${fmtTime(t.at)} ${t.task}(${STATUS_TEXT[t.status]},專案:${t.project || '無'})`) .join('\n') export const reminderText = (t: Todo): string => `到點了:[${t.type}] ${t.task}(#${t.id},專案:${t.project || '無'})。要我執行的話請輸入 /jsc-anima:${TODO_COMMAND} run ${t.id},確認前我不會動手。` export const RUN_SYSTEM = '你要執行使用者確認過的待辦,簡短回報你做了什麼與結果;沒做到的事如實說明。'