import { Vault, TFile } from 'obsidian'; import { Logger } from './utils'; export type UndoOperation = | { type: 'create'; path: string } | { type: 'modify'; path: string; originalContent: string } | { type: 'rename'; originalPath: string; newPath: string } | { type: 'trash'; path: string; originalContent: string }; export interface UndoBatch { id: string; timestamp: number; operations: UndoOperation[]; } export class UndoManager { private batches: UndoBatch[] = []; private readonly maxBatches = 10; startBatch(): string { const id = crypto.randomUUID?.() ?? `undo-${Date.now()}-${Math.random()}`; this.batches.push({ id, timestamp: Date.now(), operations: [] }); if (this.batches.length > this.maxBatches) { this.batches = this.batches.slice(-this.maxBatches); } return id; } recordOperation(batchId: string, op: UndoOperation): void { const batch = this.batches.find((b) => b.id === batchId); batch?.operations.push(op); } getBatch(batchId: string): UndoBatch | undefined { return this.batches.find((b) => b.id === batchId); } hasBatch(batchId: string): boolean { const batch = this.getBatch(batchId); return !!(batch && batch.operations.length > 0); } async undo(batchId: string, vault: Vault): Promise<{ restored: number; failed: number }> { const batch = this.getBatch(batchId); if (!batch) return { restored: 0, failed: 0 }; let restored = 0; let failed = 0; for (const op of [...batch.operations].reverse()) { try { switch (op.type) { case 'create': { const file = vault.getAbstractFileByPath(op.path); if (file instanceof TFile) { await vault.trash(file, true); } break; } case 'modify': { const file = vault.getAbstractFileByPath(op.path); if (file instanceof TFile) { await vault.modify(file, op.originalContent); } break; } case 'rename': { const file = vault.getAbstractFileByPath(op.newPath); if (file instanceof TFile) { await vault.rename(file, op.originalPath); } break; } case 'trash': { const existing = vault.getAbstractFileByPath(op.path); if (existing instanceof TFile) { await vault.modify(existing, op.originalContent); } else { await vault.create(op.path, op.originalContent); } break; } } restored++; } catch (error) { const path = 'path' in op ? op.path : 'originalPath' in op ? op.originalPath : '?'; Logger.warn( `Undo failed for ${op.type} on ${path}: ${error instanceof Error ? error.message : String(error)}`, 'undo-manager' ); failed++; } } this.batches = this.batches.filter((b) => b.id !== batchId); return { restored, failed }; } clear(): void { this.batches = []; } }