备份存的是「改动前」,所以一条记录的 diff 两端是它自己和更新一份的 状态(最新一条对比当前文件)。行级 diff 先掐公共前后缀再做 LCS,一次 改动通常只碰几行,表撑得住;大面积不同时退化成整段删增。 合并窗做在服务端而不是前端防抖:普通保存(opts.merge)发现最新备份 还在窗内就不新建,连点删除共享一条历史、一步撤回。窗锚定在这一串的 第一下,不滚雪球;还原、导入、撤回重做不合并,各占一条。合并时没有 备份文件,重载失败的还原改用刚读出的原文写回。顺带消除了备份名只精 确到秒、同秒保存互相覆盖的隐患。
30 lines
920 B
TypeScript
30 lines
920 B
TypeScript
/**
|
|
* 备份文件名里的时间戳。
|
|
*
|
|
* 名字由服务端生成(server/config-store.ts 的 backupName),形如
|
|
* `config.yaml.bak-20260811-031000`,时间戳是服务端的本地时间。
|
|
*/
|
|
|
|
const STAMP = /\.bak-(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/
|
|
|
|
/** 把备份文件名排成可读时间。名字不合预期时返回 null,调用方退回显示原名。 */
|
|
export function backupTime(name: string): string | null {
|
|
const m = STAMP.exec(name)
|
|
if (!m) return null
|
|
return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}:${m[6]}`
|
|
}
|
|
|
|
/** 备份文件名里的时间戳解析成 Date(服务端本地时间)。名字不合预期时返回 null。 */
|
|
export function backupDate(name: string): Date | null {
|
|
const m = STAMP.exec(name)
|
|
if (!m) return null
|
|
return new Date(
|
|
Number(m[1]),
|
|
Number(m[2]) - 1,
|
|
Number(m[3]),
|
|
Number(m[4]),
|
|
Number(m[5]),
|
|
Number(m[6]),
|
|
)
|
|
}
|