Obsidian plugin (manifest.json: id file-undo, name File Undo, v1.0.1, minAppVersion 0.15.0, isDesktopOnly false). Sole runtime is main.js exporting A1FileUndoPlugin. Records vault rename/move and delete (files and folders; listeners do not filter by type) into in-memory undoStack / redoStack and restores them from the command palette. The only persisted key is maxHistory (default 200) in data.json. onload always resets both stacks and ignoreEventsUntil; history does not survive reload or disable. No onunload.
| Component | Responsible For | MUST NOT Contain |
|---|---|---|
A1FileUndoPlugin (main.js) |
loadSettings / saveSettings; stacks and ignoreEventsUntil; vault listeners; commands; restore I/O |
Editor/content undo; Obsidian trash-mode policy; UI beyond Notice; persisting stacks |
A1FileUndoSettingTab (main.js) |
Tab "A1 File Undo Settings"; clamp maxHistory 10–999; persist; FIFO-trim undoStack |
Trimming redoStack; vault rename/delete; restore |
Vault rename / delete handlers (onload) |
Record payloads; skip .trash/ and an open ignore window; redoStack = []; FIFO-trim undo |
Performing restore |
undoLastAction / redoLastAction assign ignoreEventsUntil = Date.now() + 1000 in finally after awaited I/O, not before. A vault.rename restore can emit rename during the await; that event is skipped only if a previous window is still open. (Why chosen over wrapping I/O or source-tagged events: Obsidian vault events do not distinguish plugin vs user origin; this file sets the flag in finally.).trash/ (rename also checks oldPath). Delete restore uses adapter path .trash/ + action.name (basename), not the original full path. (Why chosen over treating trash as a first-class action or storing a unique trash location: .trash/ is the restore source, and the recorder never saves a trash path.)redoStack = []. Settings onChange trims undoStack only. (Why chosen over a branching history or trimming both stacks: linear undo/redo matches editor semantics; redo is not a FIFO log.)onload() → loadSettings() (Object.assign({}, DEFAULT_SETTINGS, await this.loadData())); undoStack = [], redoStack = [], ignoreEventsUntil = 0; addSettingTab(A1FileUndoSettingTab); registerEvent on app.vault rename / delete; addCommand ×3; console.log("A1 File Undo Plugin loaded!").(file, oldPath): return if Date.now() < ignoreEventsUntil or file.path / oldPath starts with .trash/; else push {type:'rename', newPath:file.path, oldPath}, redoStack = [], shift() while length > settings.maxHistory.(file): same ignore; skip if file.path starts with .trash/; push {type:'delete', path:file.path, name:file.name}; clear redo; FIFO-trim.finally still opens the 1s window.getAbstractFileByPath(action.newPath) then vault.rename(file, action.oldPath). Delete undo: if adapter.exists(".trash/"+action.name), mkdir missing parent of action.path, adapter.rename trash → original path. Success: redoStack.push(action) + Notice. catch / finally same as redo (step 6).getAbstractFileByPath(action.oldPath) then vault.rename(file, action.newPath). Delete redo: if adapter.exists(action.path), adapter.rename to .trash/+action.name (does not mkdir .trash). Success: undoStack.push(action) + Notice. catch: console.error + Notice. finally: ignoreEventsUntil = Date.now() + 1000.dump-commands: Node fs.writeFileSync of app.commands.commands to commands_dump.txt at adapter.getBasePath().| Method | Signature | Side-Effects |
|---|---|---|
onload |
async onload() |
Load settings; reset stacks/ignore; register events/commands/tab; console.log |
loadSettings |
async loadSettings() |
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) |
saveSettings |
async saveSettings() |
await this.saveData(this.settings) (settings only, never stacks) |
A1FileUndoSettingTab.display |
display() |
Build UI. Mutations in text onChange: parseInt (NaN→200), clamp 10–999, saveSettings(), undoStack.shift() while over limit |
| rename listener | (file, oldPath) => void |
Push undo; redoStack = []; FIFO trim |
| delete listener | (file) => void |
Push undo; redoStack = []; FIFO trim |
undoLastAction |
async undoLastAction() |
Pop undo; vault.rename or adapter mkdir/rename from .trash/<name>; push redo on success; Notices; finally ignore 1s |
redoLastAction |
async redoLastAction() |
Pop redo; vault.rename or adapter rename to .trash/<name>; push undo on success; Notices; finally ignore 1s |
| dump-commands | async () => void |
Node fs.writeFileSync vault-root commands_dump.txt; Notice "Commands dumped to commands_dump.txt" |
file-undo). Bind hotkeys in Settings → Hotkeys; addCommand registers none. History is empty until the next vault rename/delete in this session.id: undo-last-file-action) → A1FileUndoPlugin.undoLastAction in main.js (rename branch). Notices use prefixes A1: / A1 Error:..trash/<file.name> to action.path via adapter (creates parent dirs if missing). Requires Obsidian Deleted files = vault .trash folder, not system/OS trash. Same-basename entries in .trash/ collide.id: redo-last-file-action) → redoLastAction. Delete redo does not create .trash/ if missing.display() onChange clamps, saveSettings(), trims undoStack only. Reload/disable clears both stacks.dump-commands) writes commands_dump.txt at the vault base path (Node fs / Electron; isDesktopOnly is false). Marked TEMPORARY DIAGNOSTIC COMMAND in source; still registered.