/** * Backup & Diff viewer module for ObsiGate. * * Provides a modal UI to browse file backups, compare versions with * a unified diff view, and restore previous versions. * * Usage: * import { openBackupViewer } from './backups.js'; * openBackupViewer(vaultName, filePath); */ import { api } from './auth.js'; import { showToast } from './ui.js'; import { refreshSidebarTreePreservingState } from './sidebar.js'; import { safeCreateIcons } from './utils.js'; // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- const state = { vault: null, path: null, backups: [], overlay: null, leftVersion: null, rightVersion: null, diffText: '', }; // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- export async function openBackupViewer(vault, path) { state.vault = vault; state.path = path; state.backups = []; state.leftVersion = null; state.rightVersion = null; state.diffText = ''; const overlay = createOverlay(); document.body.appendChild(overlay); state.overlay = overlay; requestAnimationFrame(() => overlay.classList.add('active')); setBody(renderLoading()); try { const data = await api(`/api/file/${encodeURIComponent(vault)}/backups?path=${encodeURIComponent(path)}`); state.backups = data.backups || []; } catch (err) { const msg = err?.detail || err?.message || 'Erreur inconnue'; setBody(renderError(`Erreur API: ${msg}`)); return; } if (state.backups.length === 0) { setBody(renderEmpty()); return; } state.leftVersion = state.backups[0].timestamp; state.rightVersion = null; renderMainView(); await loadDiff(); } // --------------------------------------------------------------------------- // DOM helpers // --------------------------------------------------------------------------- function createOverlay() { const overlay = document.createElement('div'); overlay.className = 'obsigate-modal-overlay backup-viewer-overlay'; overlay.addEventListener('click', (e) => { 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 _escHandler = (e) => { if (e.key === 'Escape' && state.overlay) close(); }; document.addEventListener('keydown', _escHandler); return overlay; } /** Replace panel content and re-bind the close button. */ function setBody(html) { const panel = document.getElementById('backup-viewer-panel'); if (!panel) return; panel.innerHTML = html; safeCreateIcons(); // Bind close button — same pattern as AdminPanel const closeBtn = panel.querySelector('.backup-close-btn'); if (closeBtn) { closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); }); } } let _escHandler = null; function close() { if (!state.overlay) return; if (_escHandler) { document.removeEventListener('keydown', _escHandler); _escHandler = null; } state.overlay.classList.remove('active'); setTimeout(() => { if (state.overlay) state.overlay.remove(); state.overlay = null; }, 200); } // --------------------------------------------------------------------------- // Rendering // --------------------------------------------------------------------------- /** Close button HTML — same pattern as AdminPanel editor-btn */ const CLOSE_BTN = ''; function renderLoading() { return `
Chargement des backups...
${escapeHtml(msg)}
Aucun backup disponible pour ce fichier.
Les backups sont créés automatiquement avant chaque modification.
| ${prefix} | ${escapeHtml(line)} |