File Undo

by Jeff1024
5
4
3
2
1
Score: 50/100

Description

Undo recent file deletions and moves (renames) in Obsidian.

Reviews

No reviews yet.

Stats

0
stars
174
downloads
0
forks
55
days
9
days
55
days
0
total PRs
0
open PRs
0
closed PRs
0
merged PRs
0
total issues
0
open issues
0
closed issues
5
commits

Latest Version

2 months ago

Changelog

Update plugin ID to comply with Obsidian community guidelines.

README file from

Github

A1 File Undo

Overview & Purpose

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.

Scope Boundaries

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

Key Invariants

  1. 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.)
  2. Record never pushes paths under .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.)
  3. Any recorded user rename/delete sets 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.)

Numbered Data Flow

  1. 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!").
  2. Rename listener (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.
  3. Delete listener (file): same ignore; skip if file.path starts with .trash/; push {type:'delete', path:file.path, name:file.name}; clear redo; FIFO-trim.
  4. Empty undo/redo: Notice and return (no ignore window). Otherwise pop first, then I/O. Missing file/trash: Notice; the popped action is not re-queued; finally still opens the 1s window.
  5. Rename undo: 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).
  6. Rename redo: 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.
  7. dump-commands: Node fs.writeFileSync of app.commands.commands to commands_dump.txt at adapter.getBasePath().

Side-effects API

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"

Recipes

  1. Enable — Community plugins → File Undo (file-undo). Bind hotkeys in Settings → Hotkeys; addCommand registers none. History is empty until the next vault rename/delete in this session.
  2. Undo a move — Command palette "Undo last file move or deletion" (id: undo-last-file-action) → A1FileUndoPlugin.undoLastAction in main.js (rename branch). Notices use prefixes A1: / A1 Error:.
  3. Undo a deletion — Same command, delete branch: restores .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.
  4. Redo — "Redo last undone file move or deletion" (id: redo-last-file-action) → redoLastAction. Delete redo does not create .trash/ if missing.
  5. Cap history — Settings → A1 File Undo Settings → Max History Length (placeholder 200, min 10, max 999). display() onChange clamps, saveSettings(), trims undoStack only. Reload/disable clears both stacks.
  6. Dump command list — "A1 Debug: Dump all commands" (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.