/** 正文 [timeline]…[/timeline] 块解析与序列化 */ export type TimelineItem = { date: string; title: string; body: string; sourceUrl?: string; /** 导入勾选;手填项默认 true */ selected?: boolean; }; export type TimelineDayGroup = { date: string; items: TimelineItem[]; }; const GIT_COMMENT_RE = /^\s*$/i; const HEADING_RE = /^##\s+(\d{4}-\d{2}-\d{2})\s+(.+)$/; /** 去掉控制字符与 RTL 覆盖,截断长度 */ export function sanitizeTimelineText(raw: string, max: number): string { let s = raw .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "") .replace(/[\u202A-\u202E\u2066-\u2069]/g, ""); if ([...s].length > max) { s = [...s].slice(0, max).join(""); } return s; } function isDirectiveLine(t: string): boolean { const lower = t.toLowerCase(); return ( lower === "[/hide]" || lower === "[/timeline]" || lower === "[timeline]" || lower.startsWith("[hide") || t.startsWith("`); } return lines.join("\n"); }); return `[timeline]\n${parts.join("\n\n")}\n[/timeline]`; } export function groupTimelineByDate(items: TimelineItem[]): TimelineDayGroup[] { const map = new Map(); const order: string[] = []; for (const it of items) { const d = it.date || "未知日期"; if (!map.has(d)) { map.set(d, []); order.push(d); } map.get(d)!.push(it); } order.sort((a, b) => (a < b ? 1 : a > b ? -1 : 0)); return order.map((date) => ({ date, items: map.get(date)! })); } export function isLikelyMergeCommit(title: string): boolean { return /^merge\b/i.test(title.trim()) || /^合并\b/.test(title.trim()); }