diff --git a/frontend/js/backups.js b/frontend/js/backups.js index 509e350..c51b26b 100644 --- a/frontend/js/backups.js +++ b/frontend/js/backups.js @@ -14,12 +14,6 @@ import { showToast } from './ui.js'; import { refreshSidebarTreePreservingState } from './sidebar.js'; import { safeCreateIcons } from './utils.js'; -// Global handler: same pattern as Graph View's initGraphView() -document.addEventListener('click', function(e) { - const btn = e.target.closest('#backup-close'); - if (btn && state.overlay) close(); -}); - // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- @@ -34,7 +28,7 @@ const state = { diffText: '', diffLeft: '', diffRight: '', - diffMode: 'unified', // 'unified' or 'sidebyside' + diffMode: 'unified', }; // --------------------------------------------------------------------------- @@ -48,20 +42,23 @@ export async function openBackupViewer(vault, path) { state.leftVersion = null; state.rightVersion = null; state.diffText = ''; + state.diffLeft = ''; + state.diffRight = ''; const overlay = createOverlay(); document.body.appendChild(overlay); state.overlay = overlay; - requestAnimationFrame(() => overlay.classList.add('active')); + requestAnimationFrame(function() { overlay.classList.add('active'); }); setBody(renderLoading()); try { - const data = await api(`/api/file/${encodeURIComponent(vault)}/backups?path=${encodeURIComponent(path)}`); + var url = '/api/file/' + encodeURIComponent(vault) + '/backups?path=' + encodeURIComponent(path); + var data = await api(url); state.backups = data.backups || []; } catch (err) { - const msg = err?.detail || err?.message || 'Erreur inconnue'; - setBody(renderError(`Erreur API: ${msg}`)); + var msg = err && err.detail || err && err.message || 'Erreur inconnue'; + setBody(renderError('Erreur API: ' + msg)); return; } @@ -80,33 +77,35 @@ export async function openBackupViewer(vault, path) { // DOM helpers // --------------------------------------------------------------------------- +var _escHandler = null; + function createOverlay() { - const overlay = document.createElement('div'); + var overlay = document.createElement('div'); overlay.className = 'obsigate-modal-overlay backup-viewer-overlay'; - overlay.addEventListener('click', (e) => { + overlay.addEventListener('click', function(e) { if (e.target === overlay) close(); }); - const panel = document.createElement('div'); + var panel = document.createElement('div'); panel.className = 'backup-viewer-panel'; panel.id = 'backup-viewer-panel'; overlay.appendChild(panel); - // Close button OUTSIDE the panel — never touched by innerHTML, always present - const closeBtn = document.createElement('button'); + // 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', (e) => { + closeBtn.addEventListener('click', function(e) { e.stopPropagation(); close(); }); overlay.appendChild(closeBtn); - // Keyboard: Escape closes the viewer - _escHandler = (e) => { + // Escape key + _escHandler = function(e) { if (e.key === 'Escape' && state.overlay) close(); }; document.addEventListener('keydown', _escHandler); @@ -114,14 +113,11 @@ function createOverlay() { return overlay; } -/** Replace panel content. Close button is static in the overlay, never replaced. */ function setBody(html) { - const panel = document.getElementById('backup-viewer-panel'); + var panel = document.getElementById('backup-viewer-panel'); if (panel) panel.innerHTML = html; } -let _escHandler = null; - function close() { if (!state.overlay) return; if (_escHandler) { @@ -129,102 +125,95 @@ function close() { _escHandler = null; } state.overlay.classList.remove('active'); - setTimeout(() => { + 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...

-
-
`; + return '

Backups

' + + '
' + + '
' + + '
' + + '

Chargement des backups...

' + + '
'; } function renderError(msg) { - return \` -
-

Backups

-
-
-

${escapeHtml(msg)}

-
`; + return '

Backups

' + + '
' + + '

' + escapeHtml(msg) + '

'; } function renderEmpty() { - return \` -
-

Backups — \${escapeHtml(state.path)}

-
-
-

Aucun backup disponible pour ce fichier.

-

Les backups sont créés automatiquement avant chaque modification.

-
`; + return '

Backups - ' + escapeHtml(state.path) + '

' + + '
' + + '

Aucun backup disponible pour ce fichier.

' + + '

Les backups sont crees automatiquement avant chaque modification.

' + + '
'; } function renderMainView() { - const b = state.backups; - const leftOpts = b.map(bk => { - const sel = bk.timestamp === state.leftVersion ? 'selected' : ''; - return ``; + var b = state.backups; + var leftOpts = b.map(function(bk) { + var sel = bk.timestamp === state.leftVersion ? ' selected' : ''; + return ''; }).join(''); - const rightOpts = [ - ``, - ...b.map(bk => { - const sel = bk.timestamp === state.rightVersion ? 'selected' : ''; - return ``; - }), - ].join(''); + var rightOpts = [''] + .concat(b.map(function(bk) { + var sel = bk.timestamp === state.rightVersion ? ' selected' : ''; + return ''; + })).join(''); - const html = ` -
-
-

Backups

- ${escapeHtml(state.path)} -
-
- \${b.length} backup\${b.length > 1 ? 's' : ''} -
-
+ var html = + '
' + + '
' + + '

Backups

' + + '' + escapeHtml(state.path) + '' + + '
' + + '
' + + '' + b.length + ' backup' + (b.length > 1 ? 's' : '') + '' + + '
' + + '
' + -
-
- - -
- vs -
- -
- - -
+ '
' + + '
' + + '' + + '' + + '
' + + 'vs' + + '
' + + '' + + '
' + + '' + + '' + + '
' + -
-
-
-
-
+ '
' + + '
' + + '
' + + '
' + + '
' + - `; + ''; setBody(html); bindEvents(); @@ -235,41 +224,51 @@ function renderMainView() { // --------------------------------------------------------------------------- function bindEvents() { - const leftSelect = document.getElementById('backup-left-select'); - leftSelect?.addEventListener('change', async () => { - state.leftVersion = parseInt(leftSelect.value); - await loadDiff(); - }); + var leftSelect = document.getElementById('backup-left-select'); + if (leftSelect) { + leftSelect.addEventListener('change', async function() { + state.leftVersion = parseInt(leftSelect.value); + await loadDiff(); + }); + } - const rightSelect = document.getElementById('backup-right-select'); - rightSelect?.addEventListener('change', async () => { - const val = rightSelect.value; - state.rightVersion = val === 'current' ? null : parseInt(val); - await loadDiff(); - }); + var rightSelect = document.getElementById('backup-right-select'); + if (rightSelect) { + rightSelect.addEventListener('change', async function() { + var val = rightSelect.value; + state.rightVersion = val === 'current' ? null : parseInt(val); + await loadDiff(); + }); + } - const restoreBtn = document.getElementById('backup-restore-btn'); - restoreBtn?.addEventListener('click', async () => { - await restoreBackup(); - }); + var restoreBtn = document.getElementById('backup-restore-btn'); + if (restoreBtn) { + restoreBtn.addEventListener('click', async function() { + await restoreBackup(); + }); + } - // Side-by-side toggle - const modeBtn = document.getElementById('backup-diff-mode-btn'); - modeBtn?.addEventListener('click', () => { - state.diffMode = state.diffMode === 'sidebyside' ? 'unified' : 'sidebyside'; - const container = document.getElementById('backup-diff-container'); - if (container) { - container.innerHTML = state.diffMode === 'sidebyside' - ? renderDiffSideBySide(state.diffLeft, state.diffRight) - : renderDiff(state.diffText); - } - // Update icon and text - const icon = modeBtn.querySelector('i'); - if (icon) icon.setAttribute('data-lucide', state.diffMode === 'sidebyside' ? 'columns-2' : 'align-justify'); - modeBtn.childNodes.forEach(n => { if (n.nodeType === 3) n.remove(); }); - modeBtn.appendChild(document.createTextNode(state.diffMode === 'sidebyside' ? ' Unifiee' : ' Cote-a-cote')); - safeCreateIcons(); - }); + var modeBtn = document.getElementById('backup-diff-mode-btn'); + if (modeBtn) { + modeBtn.addEventListener('click', function() { + state.diffMode = state.diffMode === 'sidebyside' ? 'unified' : 'sidebyside'; + var container = document.getElementById('backup-diff-container'); + if (container) { + container.innerHTML = state.diffMode === 'sidebyside' + ? renderDiffSideBySide(state.diffLeft, state.diffRight) + : renderDiff(state.diffText); + } + var icon = modeBtn.querySelector('i'); + if (icon) icon.setAttribute('data-lucide', state.diffMode === 'sidebyside' ? 'columns-2' : 'align-justify'); + var textNodes = []; + for (var i = 0; i < modeBtn.childNodes.length; i++) { + if (modeBtn.childNodes[i].nodeType === 3) textNodes.push(modeBtn.childNodes[i]); + } + textNodes.forEach(function(n) { n.remove(); }); + modeBtn.appendChild(document.createTextNode(state.diffMode === 'sidebyside' ? ' Unifiee' : ' Cote-a-cote')); + safeCreateIcons(); + }); + } } // --------------------------------------------------------------------------- @@ -277,21 +276,21 @@ function bindEvents() { // --------------------------------------------------------------------------- async function loadDiff() { - const container = document.getElementById('backup-diff-container'); + var container = document.getElementById('backup-diff-container'); if (!container) return; - container.innerHTML = ` -
-
-
`; + container.innerHTML = + '
' + + '
' + + '
'; - let url = `/api/file/${encodeURIComponent(state.vault)}/diff?path=${encodeURIComponent(state.path)}&version=${state.leftVersion}`; + var url = '/api/file/' + encodeURIComponent(state.vault) + '/diff?path=' + encodeURIComponent(state.path) + '&version=' + state.leftVersion; if (state.rightVersion !== null) { - url += `&compare_with=${state.rightVersion}`; + url += '&compare_with=' + state.rightVersion; } try { - const data = await api(url); + var data = await api(url); state.diffText = data.diff || ''; state.diffLeft = data.left_content || ''; state.diffRight = data.right_content || ''; @@ -299,28 +298,28 @@ async function loadDiff() { ? renderDiffSideBySide(state.diffLeft, state.diffRight) : renderDiff(state.diffText); } catch (err) { - container.innerHTML = `
Erreur: ${escapeHtml(err?.detail || err?.message || 'Unknown')}
`; + container.innerHTML = '
Erreur: ' + escapeHtml((err && err.detail) || (err && err.message) || 'Unknown') + '
'; } } async function restoreBackup() { if (!state.leftVersion) return; - const confirmed = confirm( - `Restaurer "${state.path}" à la version du ${formatTimestamp(state.leftVersion)} ?\n\n` + - `La version actuelle sera sauvegardée avant la restauration.` + var confirmed = confirm( + 'Restaurer "' + state.path + '" a la version du ' + formatTimestamp(state.leftVersion) + ' ?\n\n' + + 'La version actuelle sera sauvegardee avant la restauration.' ); if (!confirmed) return; - const restoreBtn = document.getElementById('backup-restore-btn'); + var restoreBtn = document.getElementById('backup-restore-btn'); if (restoreBtn) { restoreBtn.disabled = true; restoreBtn.textContent = 'Restauration...'; } try { - const resp = await api( - `/api/file/${encodeURIComponent(state.vault)}/restore?path=${encodeURIComponent(state.path)}`, + var resp = await api( + '/api/file/' + encodeURIComponent(state.vault) + '/restore?path=' + encodeURIComponent(state.path), { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -329,17 +328,17 @@ async function restoreBackup() { ); if (resp && resp.success) { - showToast('Fichier restauré avec succès', 'success'); + showToast('Fichier restaure avec succes', 'success'); await refreshSidebarTreePreservingState(); close(); if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) { TabManager.reloadCurrent(); } } else { - showToast('Échec de la restauration', 'error'); + showToast('Echec de la restauration', 'error'); } } catch (err) { - showToast(`Erreur: ${err?.detail || err?.message || 'Unknown'}`, 'error'); + showToast('Erreur: ' + ((err && err.detail) || (err && err.message) || 'Unknown'), 'error'); } finally { if (restoreBtn) { restoreBtn.disabled = false; @@ -355,21 +354,22 @@ async function restoreBackup() { function renderDiff(diffText) { if (!diffText) { - return '
Aucune différence — les versions sont identiques.
'; + return '
Aucune difference - les versions sont identiques.
'; } - let html = '
'; - const lines = diffText.split('\n'); + var html = '
'; + var lines = diffText.split('\n'); - for (const line of lines) { - let cssClass = 'diff-context'; - let prefix = ' '; - if (line.startsWith('@@')) { cssClass = 'diff-hunk'; prefix = ''; } - else if (line.startsWith('+')) { cssClass = 'diff-add'; prefix = '+'; } - else if (line.startsWith('-')) { cssClass = 'diff-del'; prefix = '-'; } - else if (line.startsWith('---') || line.startsWith('+++')) { cssClass = 'diff-header-line'; prefix = ''; } + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var cssClass = 'diff-context'; + var prefix = ' '; + if (line.indexOf('@@') === 0) { cssClass = 'diff-hunk'; prefix = ''; } + else if (line.indexOf('+') === 0) { cssClass = 'diff-add'; prefix = '+'; } + else if (line.indexOf('-') === 0) { cssClass = 'diff-del'; prefix = '-'; } + else if (line.indexOf('---') === 0 || line.indexOf('+++') === 0) { cssClass = 'diff-header-line'; prefix = ''; } - html += ``; + html += ''; } html += '
${prefix}
${escapeHtml(line)}
' + prefix + '
' + escapeHtml(line) + '
'; @@ -382,40 +382,40 @@ function renderDiff(diffText) { function renderDiffSideBySide(leftText, rightText) { if (!leftText && !rightText) { - return '
Aucune différence — les versions sont identiques.
'; + return '
Aucune difference - les versions sont identiques.
'; } - const leftLines = (leftText || '').split('\n'); - const rightLines = (rightText || '').split('\n'); + var leftLines = (leftText || '').split('\n'); + var rightLines = (rightText || '').split('\n'); + var sm = new DifflibSeqMatcher(leftLines, rightLines); + var ops = sm.getOpcodes(); - // Compute matching opcodes using difflib-style SequenceMatcher - // We build two arrays of line groups: equal, delete (left-only), insert (right-only), replace - const sm = new difflibSeqMatcher(leftLines, rightLines); - const ops = sm.getOpcodes(); + var html = '
'; - let 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]; - for (const [tag, i1, i2, j1, j2] of ops) { if (tag === 'equal') { - for (let k = 0; k < (i2 - i1); k++) { - html += ``; + for (var k = 0; k < (i2 - i1); k++) { + html += ''; } } else if (tag === 'delete') { - for (let k = i1; k < i2; k++) { - html += ``; + for (var k2 = i1; k2 < i2; k2++) { + html += ''; } } else if (tag === 'insert') { - for (let k = j1; k < j2; k++) { - html += ``; + for (var k3 = j1; k3 < j2; k3++) { + html += ''; } } else if (tag === 'replace') { - const maxLen = Math.max(i2 - i1, j2 - j1); - for (let k = 0; k < maxLen; k++) { - const leftLine = (i1 + k < i2) ? escapeHtml(leftLines[i1 + k]) : ''; - const rightLine = (j1 + k < j2) ? escapeHtml(rightLines[j1 + k]) : ''; - const leftClass = (i1 + k < i2) ? 'diff-del' : 'diff-empty-cell'; - const rightClass = (j1 + k < j2) ? 'diff-add' : 'diff-empty-cell'; - html += ``; + var maxLen = Math.max(i2 - i1, j2 - j1); + for (var k4 = 0; k4 < maxLen; k4++) { + var leftLine = (i1 + k4 < i2) ? escapeHtml(leftLines[i1 + k4]) : ''; + var rightLine = (j1 + k4 < j2) ? escapeHtml(rightLines[j1 + k4]) : ''; + var leftClass = (i1 + k4 < i2) ? 'diff-del' : 'diff-empty-cell'; + var rightClass = (j1 + k4 < j2) ? 'diff-add' : 'diff-empty-cell'; + html += ''; } } } @@ -424,66 +424,59 @@ function renderDiffSideBySide(leftText, rightText) { return html; } -// Minimal SequenceMatcher for line-based diff (no external deps) -class difflibSeqMatcher { - constructor(a, b) { - this.a = a; - this.b = b; - } - getOpcodes() { - // Simple LCS-based diff using dynamic programming - const m = this.a.length, n = this.b.length; - const dp = Array.from({length: m + 1}, () => new Array(n + 1).fill(0)); - for (let i = 1; i <= m; i++) { - for (let j = 1; j <= n; j++) { - if (this.a[i - 1] === this.b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; - else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); - } - } - // Backtrack to build opcodes - const ops = []; - let i = m, j = n; - const equalStart = (i2, j2) => { - let ei = i2, ej = j2; - while (ei > 0 && ej > 0 && this.a[ei - 1] === this.b[ej - 1]) { ei--; ej--; } - return [ei, ej]; - }; - while (i > 0 || j > 0) { - if (i > 0 && j > 0 && this.a[i - 1] === this.b[j - 1]) { - const [ei, ej] = equalStart(i, j); - if (ei < i) ops.unshift(['equal', ei, i, ej, j]); - i = ei; j = ej; - } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { - ops.unshift(['insert', i, i, j - 1, j]); - j--; - } else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) { - ops.unshift(['delete', i - 1, i, j, j]); - i--; - } - } - // Merge adjacent delete+insert into replace - const merged = []; - for (let k = 0; k < ops.length; k++) { - if (merged.length > 0 && merged[merged.length - 1][0] === 'delete' && ops[k][0] === 'insert' && - merged[merged.length - 1][4] === ops[k][3]) { - const del = merged.pop(); - merged.push(['replace', del[1], del[2], ops[k][3], ops[k][4]]); - } else { - merged.push(ops[k]); - } - } - return merged; - } +// Minimal SequenceMatcher for line-based diff +function DifflibSeqMatcher(a, b) { + this.a = a; + this.b = b; } +DifflibSeqMatcher.prototype.getOpcodes = function() { + var m = this.a.length, n = this.b.length; + var dp = []; + for (var i = 0; i <= m; i++) { dp[i] = []; for (var j = 0; j <= n; j++) dp[i][j] = 0; } + for (var i = 1; i <= m; i++) { + for (var j = 1; j <= n; j++) { + if (this.a[i - 1] === this.b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; + else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + var ops = []; + var i = m, j = n; + while (i > 0 || j > 0) { + if (i > 0 && j > 0 && this.a[i - 1] === this.b[j - 1]) { + var ei = i - 1, ej = j - 1; + while (ei > 0 && ej > 0 && this.a[ei - 1] === this.b[ej - 1]) { ei--; ej--; } + if (ei < i) ops.unshift(['equal', ei, i, ej, j]); + i = ei; j = ej; + } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { + ops.unshift(['insert', i, i, j - 1, j]); + j--; + } else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) { + ops.unshift(['delete', i - 1, i, j, j]); + i--; + } + } + var merged = []; + for (var k = 0; k < ops.length; k++) { + if (merged.length > 0 && merged[merged.length - 1][0] === 'delete' && ops[k][0] === 'insert' && + merged[merged.length - 1][4] === ops[k][3]) { + var del = merged.pop(); + merged.push(['replace', del[1], del[2], ops[k][3], ops[k][4]]); + } else { + merged.push(ops[k]); + } + } + return merged; +}; + // --------------------------------------------------------------------------- // Utility // --------------------------------------------------------------------------- function formatTimestamp(ts) { - const d = new Date(ts * 1000); - const pad = (n) => String(n).padStart(2, '0'); - return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + var d = new Date(ts * 1000); + var pad = function(n) { return String(n).padStart(2, '0'); }; + return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); } function formatSize(bytes) { @@ -493,7 +486,7 @@ function formatSize(bytes) { } function escapeHtml(str) { - const div = document.createElement('div'); + var div = document.createElement('div'); div.textContent = str; return div.innerHTML; }
${escapeHtml(leftLines[i1 + k])}
${escapeHtml(rightLines[j1 + k])}
' + escapeHtml(leftLines[i1 + k]) + '
' + escapeHtml(rightLines[j1 + k]) + '
${escapeHtml(leftLines[k])}
' + escapeHtml(leftLines[k2]) + '
${escapeHtml(rightLines[k])}
' + escapeHtml(rightLines[k3]) + '
${leftLine}
${rightLine}
' + leftLine + '
' + rightLine + '