/**
* 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: '',
diffLeft: '',
diffRight: '',
diffMode: 'unified',
};
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function openBackupViewer(vault, path) {
state.vault = vault;
state.path = path;
state.backups = [];
state.leftVersion = null;
state.rightVersion = null;
state.diffText = '';
state.diffLeft = '';
state.diffRight = '';
const overlay = createOverlay();
document.body.appendChild(overlay);
state.overlay = overlay;
requestAnimationFrame(function() { overlay.classList.add('active'); });
setBody(renderLoading());
try {
var url = '/api/file/' + encodeURIComponent(vault) + '/backups?path=' + encodeURIComponent(path);
var data = await api(url);
state.backups = data.backups || [];
} catch (err) {
var msg = err && err.detail || err && 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
// ---------------------------------------------------------------------------
var _escHandler = null;
function createOverlay() {
var overlay = document.createElement('div');
overlay.className = 'obsigate-modal-overlay backup-viewer-overlay';
overlay.addEventListener('click', function(e) {
if (e.target === overlay) close();
});
var panel = document.createElement('div');
panel.className = 'backup-viewer-panel';
panel.id = 'backup-viewer-panel';
overlay.appendChild(panel);
// Close button -- static, never replaced by innerHTML
var closeBtn = document.createElement('button');
closeBtn.className = 'editor-btn backup-close-btn';
closeBtn.id = 'backup-close';
closeBtn.title = 'Fermer';
closeBtn.style.cssText = 'position:absolute;top:12px;right:12px;z-index:1002;flex-shrink:0';
closeBtn.innerHTML = '';
closeBtn.addEventListener('click', function(e) {
e.stopPropagation();
close();
});
overlay.appendChild(closeBtn);
// Escape key
_escHandler = function(e) {
if (e.key === 'Escape' && state.overlay) close();
};
document.addEventListener('keydown', _escHandler);
return overlay;
}
function setBody(html) {
var panel = document.getElementById('backup-viewer-panel');
if (panel) panel.innerHTML = html;
}
function close() {
if (!state.overlay) return;
if (_escHandler) {
document.removeEventListener('keydown', _escHandler);
_escHandler = null;
}
state.overlay.classList.remove('active');
setTimeout(function() {
if (state.overlay) state.overlay.remove();
state.overlay = null;
}, 200);
}
// Global click handler for close button
document.addEventListener('click', function(e) {
var btn = e.target.closest ? e.target.closest('#backup-close') : null;
if (btn && state.overlay) close();
});
// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------
function renderLoading() {
return '
Backups
' +
'
' +
'
' +
'' +
'
Chargement des backups...
' +
'
';
}
function renderError(msg) {
return '
Backups
' +
'
' +
'
' + escapeHtml(msg) + '
';
}
function renderEmpty() {
return '
Backups - ' + escapeHtml(state.path) + '
' +
'
' +
'
Aucun backup disponible pour ce fichier.
' +
'
Les backups sont crees automatiquement avant chaque modification.
' +
'
';
}
function renderMainView() {
var b = state.backups;
var leftOpts = b.map(function(bk) {
var sel = bk.timestamp === state.leftVersion ? ' selected' : '';
return '';
}).join('');
var rightOpts = ['']
.concat(b.map(function(bk) {
var sel = bk.timestamp === state.rightVersion ? ' selected' : '';
return '';
})).join('');
var html =
'
';
}
var leftLines = (leftText || '').split('\n');
var rightLines = (rightText || '').split('\n');
var sm = new DifflibSeqMatcher(leftLines, rightLines);
var ops = sm.getOpcodes();
var html = '
';
for (var oi = 0; oi < ops.length; oi++) {
var op = ops[oi];
var tag = op[0], i1 = op[1], i2 = op[2], j1 = op[3], j2 = op[4];
if (tag === 'equal') {
for (var k = 0; k < (i2 - i1); k++) {
html += '
' + escapeHtml(leftLines[i1 + k]) + '
' + escapeHtml(rightLines[j1 + k]) + '
';
}
} else if (tag === 'delete') {
for (var k2 = i1; k2 < i2; k2++) {
html += '
' + escapeHtml(leftLines[k2]) + '
';
}
} else if (tag === 'insert') {
for (var k3 = j1; k3 < j2; k3++) {
html += '