1
15
16import type { BlockDto, HistoryStep } from "./types.js";
17
18
19export const HISTORY_LIMIT = 100;
20
21export interface HistorySnapshot {
22 readonly label: string;
23 readonly blocks: readonly BlockDto[];
24}
25
26
30export class History {
31 private undoStack: HistoryStep[] = [];
32 private redoStack: HistoryStep[] = [];
33
34
39 private baseline: readonly BlockDto[];
40
41 constructor(initial?: readonly BlockDto[]) {
42 this.baseline = [...(initial ?? [])];
43 }
44
45 get canUndo(): boolean {
46 return this.undoStack.length > 0;
47 }
48
49 get canRedo(): boolean {
50 return this.redoStack.length > 0;
51 }
52
53
54 get depth(): number {
55 return this.undoStack.length;
56 }
57
58
64 record(before: readonly BlockDto[], after: readonly BlockDto[], label: string): void {
65 const last = this.undoStack[this.undoStack.length - 1];
66 if (last !== undefined && last.label === label && this.redoStack.length === 0) {
67
68 this.undoStack[this.undoStack.length - 1] = { label, before: last.before, after: [...after] };
69 this.baseline = [...after];
70 return;
71 }
72 this.undoStack.push({ label, before: [...before], after: [...after] });
73 if (this.undoStack.length > HISTORY_LIMIT) {
74 this.undoStack.shift();
75 }
76 this.redoStack = [];
77 this.baseline = [...after];
78 }
79
80
81 undo(): HistorySnapshot | null {
82 const step = this.undoStack.pop();
83 if (step === undefined) return null;
84 this.redoStack.push(step);
85 this.baseline = [...step.before];
86 return { label: step.label, blocks: step.before };
87 }
88
89
90 redo(): HistorySnapshot | null {
91 const step = this.redoStack.pop();
92 if (step === undefined) return null;
93 this.undoStack.push(step);
94 this.baseline = [...step.after];
95 return { label: step.label, blocks: step.after };
96 }
97
98
99 current(): readonly BlockDto[] {
100 return this.baseline;
101 }
102
103
104 reset(initial?: readonly BlockDto[]): void {
105 this.undoStack = [];
106 this.redoStack = [];
107 this.baseline = [...(initial ?? [])];
108 }
109}
110