fix: rewrite backups.js with pure ASCII, no template literals - avoids encoding corruption
This commit is contained in:
+224
-231
@@ -14,12 +14,6 @@ import { showToast } from './ui.js';
|
|||||||
import { refreshSidebarTreePreservingState } from './sidebar.js';
|
import { refreshSidebarTreePreservingState } from './sidebar.js';
|
||||||
import { safeCreateIcons } from './utils.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
|
// State
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -34,7 +28,7 @@ const state = {
|
|||||||
diffText: '',
|
diffText: '',
|
||||||
diffLeft: '',
|
diffLeft: '',
|
||||||
diffRight: '',
|
diffRight: '',
|
||||||
diffMode: 'unified', // 'unified' or 'sidebyside'
|
diffMode: 'unified',
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -48,20 +42,23 @@ export async function openBackupViewer(vault, path) {
|
|||||||
state.leftVersion = null;
|
state.leftVersion = null;
|
||||||
state.rightVersion = null;
|
state.rightVersion = null;
|
||||||
state.diffText = '';
|
state.diffText = '';
|
||||||
|
state.diffLeft = '';
|
||||||
|
state.diffRight = '';
|
||||||
|
|
||||||
const overlay = createOverlay();
|
const overlay = createOverlay();
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
state.overlay = overlay;
|
state.overlay = overlay;
|
||||||
requestAnimationFrame(() => overlay.classList.add('active'));
|
requestAnimationFrame(function() { overlay.classList.add('active'); });
|
||||||
|
|
||||||
setBody(renderLoading());
|
setBody(renderLoading());
|
||||||
|
|
||||||
try {
|
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 || [];
|
state.backups = data.backups || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err?.detail || err?.message || 'Erreur inconnue';
|
var msg = err && err.detail || err && err.message || 'Erreur inconnue';
|
||||||
setBody(renderError(`Erreur API: ${msg}`));
|
setBody(renderError('Erreur API: ' + msg));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,33 +77,35 @@ export async function openBackupViewer(vault, path) {
|
|||||||
// DOM helpers
|
// DOM helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _escHandler = null;
|
||||||
|
|
||||||
function createOverlay() {
|
function createOverlay() {
|
||||||
const overlay = document.createElement('div');
|
var overlay = document.createElement('div');
|
||||||
overlay.className = 'obsigate-modal-overlay backup-viewer-overlay';
|
overlay.className = 'obsigate-modal-overlay backup-viewer-overlay';
|
||||||
overlay.addEventListener('click', (e) => {
|
overlay.addEventListener('click', function(e) {
|
||||||
if (e.target === overlay) close();
|
if (e.target === overlay) close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const panel = document.createElement('div');
|
var panel = document.createElement('div');
|
||||||
panel.className = 'backup-viewer-panel';
|
panel.className = 'backup-viewer-panel';
|
||||||
panel.id = 'backup-viewer-panel';
|
panel.id = 'backup-viewer-panel';
|
||||||
overlay.appendChild(panel);
|
overlay.appendChild(panel);
|
||||||
|
|
||||||
// Close button OUTSIDE the panel — never touched by innerHTML, always present
|
// Close button -- static, never replaced by innerHTML
|
||||||
const closeBtn = document.createElement('button');
|
var closeBtn = document.createElement('button');
|
||||||
closeBtn.className = 'editor-btn backup-close-btn';
|
closeBtn.className = 'editor-btn backup-close-btn';
|
||||||
closeBtn.id = 'backup-close';
|
closeBtn.id = 'backup-close';
|
||||||
closeBtn.title = 'Fermer';
|
closeBtn.title = 'Fermer';
|
||||||
closeBtn.style.cssText = 'position:absolute;top:12px;right:12px;z-index:1002;flex-shrink:0';
|
closeBtn.style.cssText = 'position:absolute;top:12px;right:12px;z-index:1002;flex-shrink:0';
|
||||||
closeBtn.innerHTML = '<i data-lucide="x" style="width:15px;height:15px"></i>';
|
closeBtn.innerHTML = '<i data-lucide="x" style="width:15px;height:15px"></i>';
|
||||||
closeBtn.addEventListener('click', (e) => {
|
closeBtn.addEventListener('click', function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
overlay.appendChild(closeBtn);
|
overlay.appendChild(closeBtn);
|
||||||
|
|
||||||
// Keyboard: Escape closes the viewer
|
// Escape key
|
||||||
_escHandler = (e) => {
|
_escHandler = function(e) {
|
||||||
if (e.key === 'Escape' && state.overlay) close();
|
if (e.key === 'Escape' && state.overlay) close();
|
||||||
};
|
};
|
||||||
document.addEventListener('keydown', _escHandler);
|
document.addEventListener('keydown', _escHandler);
|
||||||
@@ -114,14 +113,11 @@ function createOverlay() {
|
|||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Replace panel content. Close button is static in the overlay, never replaced. */
|
|
||||||
function setBody(html) {
|
function setBody(html) {
|
||||||
const panel = document.getElementById('backup-viewer-panel');
|
var panel = document.getElementById('backup-viewer-panel');
|
||||||
if (panel) panel.innerHTML = html;
|
if (panel) panel.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _escHandler = null;
|
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
if (!state.overlay) return;
|
if (!state.overlay) return;
|
||||||
if (_escHandler) {
|
if (_escHandler) {
|
||||||
@@ -129,102 +125,95 @@ function close() {
|
|||||||
_escHandler = null;
|
_escHandler = null;
|
||||||
}
|
}
|
||||||
state.overlay.classList.remove('active');
|
state.overlay.classList.remove('active');
|
||||||
setTimeout(() => {
|
setTimeout(function() {
|
||||||
if (state.overlay) state.overlay.remove();
|
if (state.overlay) state.overlay.remove();
|
||||||
state.overlay = null;
|
state.overlay = null;
|
||||||
}, 200);
|
}, 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
|
// Rendering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function renderLoading() {
|
function renderLoading() {
|
||||||
return `
|
return '<div class="backup-header"><h3>Backups</h3></div>' +
|
||||||
<div class="backup-header">
|
'<div class="backup-body">' +
|
||||||
<h3>Backups</h3>
|
'<div class="tree-loading" style="padding:2rem;text-align:center">' +
|
||||||
</div>
|
'<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>' +
|
||||||
<div class="backup-body">
|
'<p style="margin-top:0.75rem;color:var(--text-muted);font-size:0.85rem">Chargement des backups...</p>' +
|
||||||
<div class="tree-loading" style="padding:2rem;text-align:center">
|
'</div></div>';
|
||||||
<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>
|
|
||||||
<p style="margin-top:0.75rem;color:var(--text-muted);font-size:0.85rem">Chargement des backups...</p>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderError(msg) {
|
function renderError(msg) {
|
||||||
return \`
|
return '<div class="backup-header"><h3>Backups</h3></div>' +
|
||||||
<div class="backup-header">
|
'<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">' +
|
||||||
<h3>Backups</h3>
|
'<p>' + escapeHtml(msg) + '</p></div>';
|
||||||
</div>
|
|
||||||
<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">
|
|
||||||
<p>${escapeHtml(msg)}</p>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEmpty() {
|
function renderEmpty() {
|
||||||
return \`
|
return '<div class="backup-header"><h3>Backups - ' + escapeHtml(state.path) + '</h3></div>' +
|
||||||
<div class="backup-header">
|
'<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">' +
|
||||||
<h3>Backups — \${escapeHtml(state.path)}</h3>
|
'<p>Aucun backup disponible pour ce fichier.</p>' +
|
||||||
</div>
|
'<p style="font-size:0.8rem;margin-top:0.5rem">Les backups sont crees automatiquement avant chaque modification.</p>' +
|
||||||
<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">
|
'</div>';
|
||||||
<p>Aucun backup disponible pour ce fichier.</p>
|
|
||||||
<p style="font-size:0.8rem;margin-top:0.5rem">Les backups sont créés automatiquement avant chaque modification.</p>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMainView() {
|
function renderMainView() {
|
||||||
const b = state.backups;
|
var b = state.backups;
|
||||||
const leftOpts = b.map(bk => {
|
var leftOpts = b.map(function(bk) {
|
||||||
const sel = bk.timestamp === state.leftVersion ? 'selected' : '';
|
var sel = bk.timestamp === state.leftVersion ? ' selected' : '';
|
||||||
return `<option value="${bk.timestamp}" ${sel}>${formatTimestamp(bk.timestamp)} (${formatSize(bk.size)})</option>`;
|
return '<option value="' + bk.timestamp + '"' + sel + '>' + formatTimestamp(bk.timestamp) + ' (' + formatSize(bk.size) + ')</option>';
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
const rightOpts = [
|
var rightOpts = ['<option value="current"' + (state.rightVersion === null ? ' selected' : '') + '>Version actuelle</option>']
|
||||||
`<option value="current" ${state.rightVersion === null ? 'selected' : ''}>Version actuelle</option>`,
|
.concat(b.map(function(bk) {
|
||||||
...b.map(bk => {
|
var sel = bk.timestamp === state.rightVersion ? ' selected' : '';
|
||||||
const sel = bk.timestamp === state.rightVersion ? 'selected' : '';
|
return '<option value="' + bk.timestamp + '"' + sel + '>' + formatTimestamp(bk.timestamp) + ' (' + formatSize(bk.size) + ')</option>';
|
||||||
return `<option value="${bk.timestamp}" ${sel}>${formatTimestamp(bk.timestamp)} (${formatSize(bk.size)})</option>`;
|
})).join('');
|
||||||
}),
|
|
||||||
].join('');
|
|
||||||
|
|
||||||
const html = `
|
var html =
|
||||||
<div class="backup-header">
|
'<div class="backup-header">' +
|
||||||
<div class="backup-header-left">
|
'<div class="backup-header-left">' +
|
||||||
<h3>Backups</h3>
|
'<h3>Backups</h3>' +
|
||||||
<span class="backup-file-path">${escapeHtml(state.path)}</span>
|
'<span class="backup-file-path">' + escapeHtml(state.path) + '</span>' +
|
||||||
</div>
|
'</div>' +
|
||||||
<div class="backup-header-right">
|
'<div class="backup-header-right">' +
|
||||||
<span class="backup-count">\${b.length} backup\${b.length > 1 ? 's' : ''}</span>
|
'<span class="backup-count">' + b.length + ' backup' + (b.length > 1 ? 's' : '') + '</span>' +
|
||||||
</div>
|
'</div>' +
|
||||||
</div>
|
'</div>' +
|
||||||
|
|
||||||
<div class="backup-toolbar">
|
'<div class="backup-toolbar">' +
|
||||||
<div class="backup-selector-group">
|
'<div class="backup-selector-group">' +
|
||||||
<label>Comparer :</label>
|
'<label>Comparer :</label>' +
|
||||||
<select class="backup-select" id="backup-left-select">${leftOpts}</select>
|
'<select class="backup-select" id="backup-left-select">' + leftOpts + '</select>' +
|
||||||
</div>
|
'</div>' +
|
||||||
<span class="backup-vs-label">vs</span>
|
'<span class="backup-vs-label">vs</span>' +
|
||||||
<div class="backup-selector-group">
|
'<div class="backup-selector-group">' +
|
||||||
<select class="backup-select" id="backup-right-select">${rightOpts}</select>
|
'<select class="backup-select" id="backup-right-select">' + rightOpts + '</select>' +
|
||||||
</div>
|
'</div>' +
|
||||||
<button class="backup-restore-btn" id="backup-restore-btn" title="Restaurer la version de gauche">
|
'<button class="backup-restore-btn" id="backup-restore-btn" title="Restaurer la version de gauche">' +
|
||||||
<i data-lucide="rotate-ccw" class="icon-sm"></i> Restaurer
|
'<i data-lucide="rotate-ccw" class="icon-sm"></i> Restaurer' +
|
||||||
</button>
|
'</button>' +
|
||||||
<button class="backup-diff-mode-btn" id="backup-diff-mode-btn" title="Vue cote a cote">
|
'<button class="backup-diff-mode-btn" id="backup-diff-mode-btn" title="Vue cote a cote">' +
|
||||||
<i data-lucide="columns-2" class="icon-sm"></i> Cote-a-cote
|
'<i data-lucide="columns-2" class="icon-sm"></i> Cote-a-cote' +
|
||||||
</button>
|
'</button>' +
|
||||||
</div>
|
'</div>' +
|
||||||
|
|
||||||
<div class="backup-diff-container" id="backup-diff-container">
|
'<div class="backup-diff-container" id="backup-diff-container">' +
|
||||||
<div class="backup-diff-loading">
|
'<div class="backup-diff-loading">' +
|
||||||
<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>
|
'<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>' +
|
||||||
</div>
|
'</div>' +
|
||||||
</div>
|
'</div>' +
|
||||||
|
|
||||||
<div class="backup-footer">
|
'<div class="backup-footer">' +
|
||||||
<span class="backup-footer-hint">Backups créés lors de la sauvegarde manuelle (Ctrl+S)</span>
|
'<span class="backup-footer-hint">Backups crees lors de la sauvegarde manuelle (Ctrl+S)</span>' +
|
||||||
</div>`;
|
'</div>';
|
||||||
|
|
||||||
setBody(html);
|
setBody(html);
|
||||||
bindEvents();
|
bindEvents();
|
||||||
@@ -235,41 +224,51 @@ function renderMainView() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
const leftSelect = document.getElementById('backup-left-select');
|
var leftSelect = document.getElementById('backup-left-select');
|
||||||
leftSelect?.addEventListener('change', async () => {
|
if (leftSelect) {
|
||||||
state.leftVersion = parseInt(leftSelect.value);
|
leftSelect.addEventListener('change', async function() {
|
||||||
await loadDiff();
|
state.leftVersion = parseInt(leftSelect.value);
|
||||||
});
|
await loadDiff();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const rightSelect = document.getElementById('backup-right-select');
|
var rightSelect = document.getElementById('backup-right-select');
|
||||||
rightSelect?.addEventListener('change', async () => {
|
if (rightSelect) {
|
||||||
const val = rightSelect.value;
|
rightSelect.addEventListener('change', async function() {
|
||||||
state.rightVersion = val === 'current' ? null : parseInt(val);
|
var val = rightSelect.value;
|
||||||
await loadDiff();
|
state.rightVersion = val === 'current' ? null : parseInt(val);
|
||||||
});
|
await loadDiff();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const restoreBtn = document.getElementById('backup-restore-btn');
|
var restoreBtn = document.getElementById('backup-restore-btn');
|
||||||
restoreBtn?.addEventListener('click', async () => {
|
if (restoreBtn) {
|
||||||
await restoreBackup();
|
restoreBtn.addEventListener('click', async function() {
|
||||||
});
|
await restoreBackup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Side-by-side toggle
|
var modeBtn = document.getElementById('backup-diff-mode-btn');
|
||||||
const modeBtn = document.getElementById('backup-diff-mode-btn');
|
if (modeBtn) {
|
||||||
modeBtn?.addEventListener('click', () => {
|
modeBtn.addEventListener('click', function() {
|
||||||
state.diffMode = state.diffMode === 'sidebyside' ? 'unified' : 'sidebyside';
|
state.diffMode = state.diffMode === 'sidebyside' ? 'unified' : 'sidebyside';
|
||||||
const container = document.getElementById('backup-diff-container');
|
var container = document.getElementById('backup-diff-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = state.diffMode === 'sidebyside'
|
container.innerHTML = state.diffMode === 'sidebyside'
|
||||||
? renderDiffSideBySide(state.diffLeft, state.diffRight)
|
? renderDiffSideBySide(state.diffLeft, state.diffRight)
|
||||||
: renderDiff(state.diffText);
|
: renderDiff(state.diffText);
|
||||||
}
|
}
|
||||||
// Update icon and text
|
var icon = modeBtn.querySelector('i');
|
||||||
const icon = modeBtn.querySelector('i');
|
if (icon) icon.setAttribute('data-lucide', state.diffMode === 'sidebyside' ? 'columns-2' : 'align-justify');
|
||||||
if (icon) icon.setAttribute('data-lucide', state.diffMode === 'sidebyside' ? 'columns-2' : 'align-justify');
|
var textNodes = [];
|
||||||
modeBtn.childNodes.forEach(n => { if (n.nodeType === 3) n.remove(); });
|
for (var i = 0; i < modeBtn.childNodes.length; i++) {
|
||||||
modeBtn.appendChild(document.createTextNode(state.diffMode === 'sidebyside' ? ' Unifiee' : ' Cote-a-cote'));
|
if (modeBtn.childNodes[i].nodeType === 3) textNodes.push(modeBtn.childNodes[i]);
|
||||||
safeCreateIcons();
|
}
|
||||||
});
|
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() {
|
async function loadDiff() {
|
||||||
const container = document.getElementById('backup-diff-container');
|
var container = document.getElementById('backup-diff-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML =
|
||||||
<div class="backup-diff-loading">
|
'<div class="backup-diff-loading">' +
|
||||||
<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>
|
'<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>' +
|
||||||
</div>`;
|
'</div>';
|
||||||
|
|
||||||
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) {
|
if (state.rightVersion !== null) {
|
||||||
url += `&compare_with=${state.rightVersion}`;
|
url += '&compare_with=' + state.rightVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await api(url);
|
var data = await api(url);
|
||||||
state.diffText = data.diff || '';
|
state.diffText = data.diff || '';
|
||||||
state.diffLeft = data.left_content || '';
|
state.diffLeft = data.left_content || '';
|
||||||
state.diffRight = data.right_content || '';
|
state.diffRight = data.right_content || '';
|
||||||
@@ -299,28 +298,28 @@ async function loadDiff() {
|
|||||||
? renderDiffSideBySide(state.diffLeft, state.diffRight)
|
? renderDiffSideBySide(state.diffLeft, state.diffRight)
|
||||||
: renderDiff(state.diffText);
|
: renderDiff(state.diffText);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
container.innerHTML = `<div class="backup-diff-error">Erreur: ${escapeHtml(err?.detail || err?.message || 'Unknown')}</div>`;
|
container.innerHTML = '<div class="backup-diff-error">Erreur: ' + escapeHtml((err && err.detail) || (err && err.message) || 'Unknown') + '</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restoreBackup() {
|
async function restoreBackup() {
|
||||||
if (!state.leftVersion) return;
|
if (!state.leftVersion) return;
|
||||||
|
|
||||||
const confirmed = confirm(
|
var confirmed = confirm(
|
||||||
`Restaurer "${state.path}" à la version du ${formatTimestamp(state.leftVersion)} ?\n\n` +
|
'Restaurer "' + state.path + '" a la version du ' + formatTimestamp(state.leftVersion) + ' ?\n\n' +
|
||||||
`La version actuelle sera sauvegardée avant la restauration.`
|
'La version actuelle sera sauvegardee avant la restauration.'
|
||||||
);
|
);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
const restoreBtn = document.getElementById('backup-restore-btn');
|
var restoreBtn = document.getElementById('backup-restore-btn');
|
||||||
if (restoreBtn) {
|
if (restoreBtn) {
|
||||||
restoreBtn.disabled = true;
|
restoreBtn.disabled = true;
|
||||||
restoreBtn.textContent = 'Restauration...';
|
restoreBtn.textContent = 'Restauration...';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await api(
|
var resp = await api(
|
||||||
`/api/file/${encodeURIComponent(state.vault)}/restore?path=${encodeURIComponent(state.path)}`,
|
'/api/file/' + encodeURIComponent(state.vault) + '/restore?path=' + encodeURIComponent(state.path),
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -329,17 +328,17 @@ async function restoreBackup() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (resp && resp.success) {
|
if (resp && resp.success) {
|
||||||
showToast('Fichier restauré avec succès', 'success');
|
showToast('Fichier restaure avec succes', 'success');
|
||||||
await refreshSidebarTreePreservingState();
|
await refreshSidebarTreePreservingState();
|
||||||
close();
|
close();
|
||||||
if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) {
|
if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) {
|
||||||
TabManager.reloadCurrent();
|
TabManager.reloadCurrent();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast('Échec de la restauration', 'error');
|
showToast('Echec de la restauration', 'error');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(`Erreur: ${err?.detail || err?.message || 'Unknown'}`, 'error');
|
showToast('Erreur: ' + ((err && err.detail) || (err && err.message) || 'Unknown'), 'error');
|
||||||
} finally {
|
} finally {
|
||||||
if (restoreBtn) {
|
if (restoreBtn) {
|
||||||
restoreBtn.disabled = false;
|
restoreBtn.disabled = false;
|
||||||
@@ -355,21 +354,22 @@ async function restoreBackup() {
|
|||||||
|
|
||||||
function renderDiff(diffText) {
|
function renderDiff(diffText) {
|
||||||
if (!diffText) {
|
if (!diffText) {
|
||||||
return '<div class="backup-diff-empty">Aucune différence — les versions sont identiques.</div>';
|
return '<div class="backup-diff-empty">Aucune difference - les versions sont identiques.</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = '<div class="backup-diff-output"><table class="backup-diff-table">';
|
var html = '<div class="backup-diff-output"><table class="backup-diff-table">';
|
||||||
const lines = diffText.split('\n');
|
var lines = diffText.split('\n');
|
||||||
|
|
||||||
for (const line of lines) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
let cssClass = 'diff-context';
|
var line = lines[i];
|
||||||
let prefix = ' ';
|
var cssClass = 'diff-context';
|
||||||
if (line.startsWith('@@')) { cssClass = 'diff-hunk'; prefix = ''; }
|
var prefix = ' ';
|
||||||
else if (line.startsWith('+')) { cssClass = 'diff-add'; prefix = '+'; }
|
if (line.indexOf('@@') === 0) { cssClass = 'diff-hunk'; prefix = ''; }
|
||||||
else if (line.startsWith('-')) { cssClass = 'diff-del'; prefix = '-'; }
|
else if (line.indexOf('+') === 0) { cssClass = 'diff-add'; prefix = '+'; }
|
||||||
else if (line.startsWith('---') || line.startsWith('+++')) { cssClass = 'diff-header-line'; prefix = ''; }
|
else if (line.indexOf('-') === 0) { cssClass = 'diff-del'; prefix = '-'; }
|
||||||
|
else if (line.indexOf('---') === 0 || line.indexOf('+++') === 0) { cssClass = 'diff-header-line'; prefix = ''; }
|
||||||
|
|
||||||
html += `<tr class="${cssClass}"><td class="diff-prefix">${prefix}</td><td class="diff-content"><pre>${escapeHtml(line)}</pre></td></tr>`;
|
html += '<tr class="' + cssClass + '"><td class="diff-prefix">' + prefix + '</td><td class="diff-content"><pre>' + escapeHtml(line) + '</pre></td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
html += '</table></div>';
|
html += '</table></div>';
|
||||||
@@ -382,40 +382,40 @@ function renderDiff(diffText) {
|
|||||||
|
|
||||||
function renderDiffSideBySide(leftText, rightText) {
|
function renderDiffSideBySide(leftText, rightText) {
|
||||||
if (!leftText && !rightText) {
|
if (!leftText && !rightText) {
|
||||||
return '<div class="backup-diff-empty">Aucune différence — les versions sont identiques.</div>';
|
return '<div class="backup-diff-empty">Aucune difference - les versions sont identiques.</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftLines = (leftText || '').split('\n');
|
var leftLines = (leftText || '').split('\n');
|
||||||
const rightLines = (rightText || '').split('\n');
|
var rightLines = (rightText || '').split('\n');
|
||||||
|
var sm = new DifflibSeqMatcher(leftLines, rightLines);
|
||||||
|
var ops = sm.getOpcodes();
|
||||||
|
|
||||||
// Compute matching opcodes using difflib-style SequenceMatcher
|
var html = '<div class="backup-diff-output backup-diff-sidebyside"><table class="backup-diff-table sidebyside"><colgroup><col style="width:50%"><col style="width:50%"></colgroup><tbody>';
|
||||||
// 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();
|
|
||||||
|
|
||||||
let html = '<div class="backup-diff-output backup-diff-sidebyside"><table class="backup-diff-table sidebyside"><colgroup><col style="width:50%"><col style="width:50%"></colgroup><tbody>';
|
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') {
|
if (tag === 'equal') {
|
||||||
for (let k = 0; k < (i2 - i1); k++) {
|
for (var k = 0; k < (i2 - i1); k++) {
|
||||||
html += `<tr class="diff-context"><td class="diff-content sidebyside-left"><pre>${escapeHtml(leftLines[i1 + k])}</pre></td><td class="diff-content sidebyside-right"><pre>${escapeHtml(rightLines[j1 + k])}</pre></td></tr>`;
|
html += '<tr class="diff-context"><td class="diff-content sidebyside-left"><pre>' + escapeHtml(leftLines[i1 + k]) + '</pre></td><td class="diff-content sidebyside-right"><pre>' + escapeHtml(rightLines[j1 + k]) + '</pre></td></tr>';
|
||||||
}
|
}
|
||||||
} else if (tag === 'delete') {
|
} else if (tag === 'delete') {
|
||||||
for (let k = i1; k < i2; k++) {
|
for (var k2 = i1; k2 < i2; k2++) {
|
||||||
html += `<tr class="diff-del"><td class="diff-content sidebyside-left"><pre>${escapeHtml(leftLines[k])}</pre></td><td class="diff-content sidebyside-right diff-empty-cell"></td></tr>`;
|
html += '<tr class="diff-del"><td class="diff-content sidebyside-left"><pre>' + escapeHtml(leftLines[k2]) + '</pre></td><td class="diff-content sidebyside-right diff-empty-cell"></td></tr>';
|
||||||
}
|
}
|
||||||
} else if (tag === 'insert') {
|
} else if (tag === 'insert') {
|
||||||
for (let k = j1; k < j2; k++) {
|
for (var k3 = j1; k3 < j2; k3++) {
|
||||||
html += `<tr class="diff-add"><td class="diff-content sidebyside-left diff-empty-cell"></td><td class="diff-content sidebyside-right"><pre>${escapeHtml(rightLines[k])}</pre></td></tr>`;
|
html += '<tr class="diff-add"><td class="diff-content sidebyside-left diff-empty-cell"></td><td class="diff-content sidebyside-right"><pre>' + escapeHtml(rightLines[k3]) + '</pre></td></tr>';
|
||||||
}
|
}
|
||||||
} else if (tag === 'replace') {
|
} else if (tag === 'replace') {
|
||||||
const maxLen = Math.max(i2 - i1, j2 - j1);
|
var maxLen = Math.max(i2 - i1, j2 - j1);
|
||||||
for (let k = 0; k < maxLen; k++) {
|
for (var k4 = 0; k4 < maxLen; k4++) {
|
||||||
const leftLine = (i1 + k < i2) ? escapeHtml(leftLines[i1 + k]) : '';
|
var leftLine = (i1 + k4 < i2) ? escapeHtml(leftLines[i1 + k4]) : '';
|
||||||
const rightLine = (j1 + k < j2) ? escapeHtml(rightLines[j1 + k]) : '';
|
var rightLine = (j1 + k4 < j2) ? escapeHtml(rightLines[j1 + k4]) : '';
|
||||||
const leftClass = (i1 + k < i2) ? 'diff-del' : 'diff-empty-cell';
|
var leftClass = (i1 + k4 < i2) ? 'diff-del' : 'diff-empty-cell';
|
||||||
const rightClass = (j1 + k < j2) ? 'diff-add' : 'diff-empty-cell';
|
var rightClass = (j1 + k4 < j2) ? 'diff-add' : 'diff-empty-cell';
|
||||||
html += `<tr><td class="diff-content sidebyside-left ${leftClass}"><pre>${leftLine}</pre></td><td class="diff-content sidebyside-right ${rightClass}"><pre>${rightLine}</pre></td></tr>`;
|
html += '<tr><td class="diff-content sidebyside-left ' + leftClass + '"><pre>' + leftLine + '</pre></td><td class="diff-content sidebyside-right ' + rightClass + '"><pre>' + rightLine + '</pre></td></tr>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,66 +424,59 @@ function renderDiffSideBySide(leftText, rightText) {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimal SequenceMatcher for line-based diff (no external deps)
|
// Minimal SequenceMatcher for line-based diff
|
||||||
class difflibSeqMatcher {
|
function DifflibSeqMatcher(a, b) {
|
||||||
constructor(a, b) {
|
this.a = a;
|
||||||
this.a = a;
|
this.b = b;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Utility
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function formatTimestamp(ts) {
|
function formatTimestamp(ts) {
|
||||||
const d = new Date(ts * 1000);
|
var d = new Date(ts * 1000);
|
||||||
const pad = (n) => String(n).padStart(2, '0');
|
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())}`;
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSize(bytes) {
|
function formatSize(bytes) {
|
||||||
@@ -493,7 +486,7 @@ function formatSize(bytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
const div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.textContent = str;
|
div.textContent = str;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user