fix: backup storage relative to vault parent + global capture click handler for close button
- _backup_file: resolve relative backup path against vault parent (survives CWD changes) - _get_backup_root: same resolution logic for listing - backups.js: global document-level capture-phase click handler for .backup-close-btn (works regardless of innerHTML replacements, text node targets, or event bubbling)
This commit is contained in:
+30
-5
@@ -715,22 +715,29 @@ def _resolve_safe_path(vault_root: Path, relative_path: str) -> Path:
|
|||||||
def _backup_file(file_path: Path, vault_name: str, relative_path: str):
|
def _backup_file(file_path: Path, vault_name: str, relative_path: str):
|
||||||
"""Create a timestamped backup of a file before modification.
|
"""Create a timestamped backup of a file before modification.
|
||||||
|
|
||||||
Backups are stored in .obsigate-backup/{vault}/{relative_path}.{timestamp}.bak
|
Backups are stored in {backup_root}/{vault}/{relative_path}.{timestamp}.bak
|
||||||
Silently skips if the file doesn't exist or can't be read.
|
Silently skips if the file doesn't exist or can't be read.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not file_path.exists() or not file_path.is_file():
|
if not file_path.exists() or not file_path.is_file():
|
||||||
|
logger.debug(f"Backup skipped: file not found {file_path}")
|
||||||
return
|
return
|
||||||
backup_root = Path(os.environ.get("OBSIGATE_BACKUP_DIR", ".obsigate-backup"))
|
backup_root = Path(os.environ.get("OBSIGATE_BACKUP_DIR", ".obsigate-backup"))
|
||||||
|
# Resolve relative to vault root for consistency
|
||||||
|
if not backup_root.is_absolute():
|
||||||
|
vault_data = get_vault_data(vault_name)
|
||||||
|
if vault_data:
|
||||||
|
vault_root_path = Path(vault_data["path"])
|
||||||
|
backup_root = vault_root_path.parent / backup_root
|
||||||
backup_dir = backup_root / vault_name / Path(relative_path).parent
|
backup_dir = backup_root / vault_name / Path(relative_path).parent
|
||||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
backup_name = f"{file_path.name}.{timestamp}.bak"
|
backup_name = f"{file_path.name}.{timestamp}.bak"
|
||||||
backup_path = backup_dir / backup_name
|
backup_path = backup_dir / backup_name
|
||||||
shutil.copy2(file_path, backup_path)
|
shutil.copy2(file_path, backup_path)
|
||||||
logger.debug(f"Backed up {relative_path} to {backup_path}")
|
logger.info(f"Backup created: {relative_path} -> {backup_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to backup {relative_path}: {e}")
|
logger.warning(f"Failed to backup {relative_path} (vault={vault_name}): {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
def _check_vault_writable(vault_root: Path) -> bool:
|
def _check_vault_writable(vault_root: Path) -> bool:
|
||||||
@@ -1928,8 +1935,26 @@ async def api_file_move(
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _get_backup_root() -> Path:
|
def _get_backup_root() -> Path:
|
||||||
"""Return the backup root directory as configured."""
|
"""Return the backup root directory as configured.
|
||||||
return Path(os.environ.get("OBSIGATE_BACKUP_DIR", ".obsigate-backup"))
|
|
||||||
|
If the configured path is relative, it is resolved relative to the
|
||||||
|
first vault's parent directory for consistency with _backup_file.
|
||||||
|
"""
|
||||||
|
root = Path(os.environ.get("OBSIGATE_BACKUP_DIR", ".obsigate-backup"))
|
||||||
|
if not root.is_absolute():
|
||||||
|
# Resolve relative to the first vault's parent so backups survive CWD changes
|
||||||
|
try:
|
||||||
|
from backend.indexer import get_vault_data as _get_vd
|
||||||
|
vaults = index # use the module-level index
|
||||||
|
if vaults:
|
||||||
|
first_name = next(iter(vaults.keys()))
|
||||||
|
vd = _get_vd(first_name)
|
||||||
|
if vd:
|
||||||
|
vault_parent = Path(vd["path"]).parent
|
||||||
|
root = vault_parent / root
|
||||||
|
except Exception:
|
||||||
|
pass # fall back to relative
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
def _get_backup_dir(vault_name: str, relative_path: str) -> Path:
|
def _get_backup_dir(vault_name: str, relative_path: str) -> Path:
|
||||||
|
|||||||
+16
-26
@@ -84,6 +84,16 @@ export async function openBackupViewer(vault, path) {
|
|||||||
// DOM helpers
|
// DOM helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Global click handler: catches close button clicks anywhere in the document
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest ? e.target.closest('.backup-close-btn') : null;
|
||||||
|
if (btn && state.overlay) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}, true); // capture phase — fires before other handlers
|
||||||
|
|
||||||
function createOverlay() {
|
function createOverlay() {
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'obsigate-modal-overlay backup-viewer-overlay';
|
overlay.className = 'obsigate-modal-overlay backup-viewer-overlay';
|
||||||
@@ -91,6 +101,11 @@ function createOverlay() {
|
|||||||
if (e.target === overlay) close();
|
if (e.target === overlay) close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const panel = document.createElement('div');
|
||||||
|
panel.className = 'backup-viewer-panel';
|
||||||
|
panel.id = 'backup-viewer-panel';
|
||||||
|
overlay.appendChild(panel);
|
||||||
|
|
||||||
// Keyboard: Escape closes the viewer
|
// Keyboard: Escape closes the viewer
|
||||||
_escHandler = (e) => {
|
_escHandler = (e) => {
|
||||||
if (e.key === 'Escape' && state.overlay) {
|
if (e.key === 'Escape' && state.overlay) {
|
||||||
@@ -99,37 +114,12 @@ function createOverlay() {
|
|||||||
};
|
};
|
||||||
document.addEventListener('keydown', _escHandler);
|
document.addEventListener('keydown', _escHandler);
|
||||||
|
|
||||||
const panel = document.createElement('div');
|
|
||||||
panel.className = 'backup-viewer-panel';
|
|
||||||
panel.id = 'backup-viewer-panel';
|
|
||||||
overlay.appendChild(panel);
|
|
||||||
|
|
||||||
// Event delegation fallback for close button (in case inline onclick fails)
|
|
||||||
panel.addEventListener('click', (e) => {
|
|
||||||
// e.target might be a text node (e.g. "×") — use parentElement as fallback
|
|
||||||
const target = e.target.nodeType === Node.TEXT_NODE ? e.target.parentElement : e.target;
|
|
||||||
const closeBtn = target && target.closest ? target.closest('.backup-close-btn') : null;
|
|
||||||
if (closeBtn) {
|
|
||||||
e.stopPropagation();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBody(html) {
|
function setBody(html) {
|
||||||
const panel = document.getElementById('backup-viewer-panel');
|
const panel = document.getElementById('backup-viewer-panel');
|
||||||
if (!panel) return;
|
if (panel) panel.innerHTML = html;
|
||||||
panel.innerHTML = html;
|
|
||||||
// Re-bind close button after innerHTML replacement
|
|
||||||
const closeBtn = panel.querySelector('.backup-close-btn');
|
|
||||||
if (closeBtn) {
|
|
||||||
closeBtn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _escHandler = null;
|
let _escHandler = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user