fix: rewrite backup viewer close button — match editor-btn pattern
CI / lint (push) Failing after 2m24s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / security (push) Failing after 5s

- Close button: <button class="backup-close-btn"><i data-lucide="x"></i></button>
  (same HTML pattern as AdminPanel editor-btn)
- Handler: addEventListener via DOM query in setBody() after innerHTML
  (same JS pattern as AdminPanel)
- Removed all inline onclick, global capture handler, window._backupViewerClose
- CSS: .backup-close-btn now matches .editor-btn style (bordered icon button)
- Escape key + overlay click still work for closing
This commit is contained in:
2026-06-04 16:06:39 -04:00
parent bc8083fd6b
commit 05ac6cd756
2 changed files with 39 additions and 75 deletions
+29 -65
View File
@@ -23,8 +23,8 @@ const state = {
path: null, path: null,
backups: [], backups: [],
overlay: null, overlay: null,
leftVersion: null, // timestamp of backup shown on left leftVersion: null,
rightVersion: null, // timestamp of backup shown on right, or null for current rightVersion: null,
diffText: '', diffText: '',
}; };
@@ -32,11 +32,6 @@ const state = {
// Public API // Public API
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* Open the backup/diff viewer for a file.
* @param {string} vault - Vault name
* @param {string} path - Relative file path
*/
export async function openBackupViewer(vault, path) { export async function openBackupViewer(vault, path) {
state.vault = vault; state.vault = vault;
state.path = path; state.path = path;
@@ -45,16 +40,13 @@ export async function openBackupViewer(vault, path) {
state.rightVersion = null; state.rightVersion = null;
state.diffText = ''; state.diffText = '';
// Create and show overlay
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(() => overlay.classList.add('active'));
// Show loading state
setBody(renderLoading()); setBody(renderLoading());
// Fetch backups
try { try {
const data = await api(`/api/file/${encodeURIComponent(vault)}/backups?path=${encodeURIComponent(path)}`); const data = await api(`/api/file/${encodeURIComponent(vault)}/backups?path=${encodeURIComponent(path)}`);
state.backups = data.backups || []; state.backups = data.backups || [];
@@ -69,14 +61,9 @@ export async function openBackupViewer(vault, path) {
return; return;
} }
// Auto-select: left = most recent, right = current
state.leftVersion = state.backups[0].timestamp; state.leftVersion = state.backups[0].timestamp;
state.rightVersion = null; state.rightVersion = null;
// Render version selectors
renderMainView(); renderMainView();
// Load initial diff
await loadDiff(); await loadDiff();
} }
@@ -84,16 +71,6 @@ 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';
@@ -108,25 +85,33 @@ function createOverlay() {
// 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) close();
close();
}
}; };
document.addEventListener('keydown', _escHandler); document.addEventListener('keydown', _escHandler);
return overlay; return overlay;
} }
/** Replace panel content and re-bind the close button. */
function setBody(html) { function setBody(html) {
const panel = document.getElementById('backup-viewer-panel'); const panel = document.getElementById('backup-viewer-panel');
if (panel) panel.innerHTML = html; 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; let _escHandler = null;
function close() { function close() {
if (!state.overlay) return; if (!state.overlay) return;
// Remove keyboard listener
if (_escHandler) { if (_escHandler) {
document.removeEventListener('keydown', _escHandler); document.removeEventListener('keydown', _escHandler);
_escHandler = null; _escHandler = null;
@@ -138,18 +123,18 @@ function close() {
}, 200); }, 200);
} }
// Expose globally for inline onclick handlers
window._backupViewerClose = close;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Rendering // Rendering
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** Close button HTML — same pattern as AdminPanel editor-btn */
const CLOSE_BTN = '<button class="backup-close-btn" title="Fermer"><i data-lucide="x" style="width:16px;height:16px"></i></button>';
function renderLoading() { function renderLoading() {
return ` return `
<div class="backup-header"> <div class="backup-header">
<h3>Backups</h3> <h3>Backups</h3>
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">&times;</button> ${CLOSE_BTN}
</div> </div>
<div class="backup-body"> <div class="backup-body">
<div class="tree-loading" style="padding:2rem;text-align:center"> <div class="tree-loading" style="padding:2rem;text-align:center">
@@ -160,10 +145,10 @@ function renderLoading() {
} }
function renderError(msg) { function renderError(msg) {
return \` return `
<div class="backup-header"> <div class="backup-header">
<h3>Backups</h3> <h3>Backups</h3>
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">&times;</button> ${CLOSE_BTN}
</div> </div>
<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)"> <div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">
<p>${escapeHtml(msg)}</p> <p>${escapeHtml(msg)}</p>
@@ -171,10 +156,10 @@ function renderError(msg) {
} }
function renderEmpty() { function renderEmpty() {
return \` return `
<div class="backup-header"> <div class="backup-header">
<h3>Backups — \${escapeHtml(state.path)}</h3> <h3>Backups — ${escapeHtml(state.path)}</h3>
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">&times;</button> ${CLOSE_BTN}
</div> </div>
<div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)"> <div class="backup-body" style="text-align:center;padding:2rem;color:var(--text-muted)">
<p>Aucun backup disponible pour ce fichier.</p> <p>Aucun backup disponible pour ce fichier.</p>
@@ -205,7 +190,7 @@ function renderMainView() {
</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>
<button class="backup-close-btn" title="Fermer" onclick="window._backupViewerClose()">&times;</button> ${CLOSE_BTN}
</div> </div>
</div> </div>
@@ -234,10 +219,7 @@ function renderMainView() {
</div>`; </div>`;
setBody(html); setBody(html);
// Bind events
bindEvents(); bindEvents();
safeCreateIcons();
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -245,17 +227,12 @@ function renderMainView() {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function bindEvents() { function bindEvents() {
const panel = document.getElementById('backup-viewer-panel');
if (!panel) return;
// Left select
const leftSelect = document.getElementById('backup-left-select'); const leftSelect = document.getElementById('backup-left-select');
leftSelect?.addEventListener('change', async () => { leftSelect?.addEventListener('change', async () => {
state.leftVersion = parseInt(leftSelect.value); state.leftVersion = parseInt(leftSelect.value);
await loadDiff(); await loadDiff();
}); });
// Right select
const rightSelect = document.getElementById('backup-right-select'); const rightSelect = document.getElementById('backup-right-select');
rightSelect?.addEventListener('change', async () => { rightSelect?.addEventListener('change', async () => {
const val = rightSelect.value; const val = rightSelect.value;
@@ -263,7 +240,6 @@ function bindEvents() {
await loadDiff(); await loadDiff();
}); });
// Restore button
const restoreBtn = document.getElementById('backup-restore-btn'); const restoreBtn = document.getElementById('backup-restore-btn');
restoreBtn?.addEventListener('click', async () => { restoreBtn?.addEventListener('click', async () => {
await restoreBackup(); await restoreBackup();
@@ -324,10 +300,8 @@ async function restoreBackup() {
if (resp && resp.success) { if (resp && resp.success) {
showToast('Fichier restauré avec succès', 'success'); showToast('Fichier restauré avec succès', 'success');
// Refresh sidebar and close
await refreshSidebarTreePreservingState(); await refreshSidebarTreePreservingState();
close(); close();
// Reload current file if open
if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) { if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) {
TabManager.reloadCurrent(); TabManager.reloadCurrent();
} }
@@ -360,20 +334,10 @@ function renderDiff(diffText) {
for (const line of lines) { for (const line of lines) {
let cssClass = 'diff-context'; let cssClass = 'diff-context';
let prefix = ' '; let prefix = ' ';
if (line.startsWith('@@')) { cssClass = 'diff-hunk'; prefix = ''; }
if (line.startsWith('@@')) { else if (line.startsWith('+')) { cssClass = 'diff-add'; prefix = '+'; }
cssClass = 'diff-hunk'; else if (line.startsWith('-')) { cssClass = 'diff-del'; prefix = '-'; }
prefix = ''; else if (line.startsWith('---') || line.startsWith('+++')) { cssClass = 'diff-header-line'; 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 = '';
}
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>`;
} }
+10 -10
View File
@@ -7250,24 +7250,24 @@ body.popup-mode .content-area {
} }
.backup-close-btn { .backup-close-btn {
width: 32px; width: 36px;
height: 32px; height: 36px;
border: none; padding: 0;
border: 1px solid var(--border);
border-radius: 6px; border-radius: 6px;
background: transparent; background: transparent;
color: var(--text-muted); color: var(--text-secondary);
font-size: 1.4rem;
cursor: pointer; cursor: pointer;
display: flex; transition: all 150ms ease;
display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
line-height: 1; flex: 0 0 auto;
transition: color 0.15s, background 0.15s;
} }
.backup-close-btn:hover { .backup-close-btn:hover {
color: var(--text-primary); color: var(--accent);
background: var(--bg-tertiary); border-color: var(--accent);
} }
.backup-toolbar { .backup-toolbar {