在 Loro 中使用时光旅行
在 Loro 中,可以调用 doc.checkout(frontiers) 跳转到指定 Frontiers 所代表的版本(了解 Frontiers)。
需要注意:调用 doc.checkout(frontiers) 后,文档会进入 detached 状态,此时无法继续编辑。详情参见 Attached/Detached 状态。若要继续编辑,需要调用 doc.attach() 回到最新版本。此设计是临时方案,未来会随着版本控制 API 的完善而调整。
只读时光旅行
下面演示如何实现一个简单的只读时光旅行功能,例如结合 UI 的滑块,让用户查看不同时间点的文档。
启用时间戳
在运行示例之前,需要为文档启用时间戳存储:
doc.setRecordTimestamp(true);这样每次变更都会带有时间戳,我们可以据此排序,符合用户直觉。
实现时光旅行
第一步,加载文档。假设你从数据库或 API 得到了一个快照:
// 从数据库 / API 获取文档快照
let snapshot = fetchSnapshot();
// 导入到新文档
const doc = new LoroDoc();
doc.import(snapshot);接下来收集并排序文档中每次变更的时间戳,供滑块选择:
// 收集文档的全部变更
const changes = doc.getAllChanges();
// 获取所有变更的时间戳
const timestamps = Array.from(
new Set(
[...changes.values()]
.flat() // 将不同 peer 的变更展平成一个列表
.map((x) => x.timestamp) // 提取时间戳
.filter((x) => !!x),
),
);
// 排序
timestamps.sort((a, b) => a - b);然后实现一个辅助函数,用于根据时间戳生成对应的 Frontiers。
对于每个参与编辑的 peer,都有一组按顺序排列的变更。每条变更包含 counter 与 length:counter 是起始版本号,length 表示此次变更占用了多少计数值。
Frontiers 是我们希望 checkout 的各 peer 计数列表。为了实现时间轴,需要找到每个 peer 在目标时间之前的最大计数值。
const getFrontiersForTimestamp = (
changes: Map<string, Change[]>,
ts: number,
): { peer: string; counter: number }[] => {
const frontiers = [] as { peer: string; counter: number }[];
// 记录每个 peer 在目标时间之前的最高计数
changes.forEach((changes, peer) => {
let counter = -1;
for (const change of changes) {
if (change.timestamp <= ts) {
counter = Math.max(counter, change.counter + change.length - 1);
}
}
if (counter > -1) {
frontiers.push({ counter, peer });
}
});
return frontiers;
};最后,根据滑块索引获取时间戳,计算 frontiers 并执行 checkout:
let sliderIdx = 3;
const timestamp = timestamps[sliderIdx - 1];
const frontiers = getFrontiersForTimestamp(changes, timestamp);
doc.checkout(frontiers);可编辑的时光旅行
下方展示了结合节点编辑器的完整时光旅行示例。