securite: #87 T5a supprime 16 handlers inline au profit de listeners (CSP inchangee)
This commit is contained in:
+1
-31
@@ -1576,17 +1576,6 @@
|
||||
class="help-search-clear"
|
||||
id="config-search-clear"
|
||||
title="Effacer"
|
||||
onclick="
|
||||
var s =
|
||||
document.getElementById(
|
||||
'config-nav-search',
|
||||
);
|
||||
if (s) {
|
||||
s.value = '';
|
||||
s.dispatchEvent(new Event('input'));
|
||||
s.focus();
|
||||
}
|
||||
"
|
||||
>
|
||||
X
|
||||
</button>
|
||||
@@ -1698,7 +1687,7 @@
|
||||
<input type="text" id="profile-name" class="config-input" placeholder="Votre nom" maxlength="60">
|
||||
</div>
|
||||
<button class="config-save-btn" id="profile-save" data-i18n="config.save">Enregistrer</button>
|
||||
<button class="config-save-btn" id="profile-logout" style="background:var(--danger-bg);color:var(--danger);border-color:var(--danger);margin-left:8px" data-i18n="config.logout" onclick="if(window.handleLogout)window.handleLogout();else{doLogoutFallback()}">Déconnexion</button>
|
||||
<button class="config-save-btn" id="profile-logout" style="background:var(--danger-bg);color:var(--danger);border-color:var(--danger);margin-left:8px" data-i18n="config.logout">Déconnexion</button>
|
||||
<span class="profile-saved" id="profile-saved" style="display:none" data-i18n="config.saved">✓ Sauvegardé</span>
|
||||
</div>
|
||||
</section>
|
||||
@@ -3005,14 +2994,6 @@
|
||||
id="help-hamburger"
|
||||
title="Sommaire"
|
||||
aria-label="Afficher le sommaire"
|
||||
onclick="
|
||||
var n = document.getElementById('help-nav');
|
||||
if (n) {
|
||||
var d = n.style.display;
|
||||
n.style.display =
|
||||
d === 'none' || d === '' ? 'flex' : 'none';
|
||||
}
|
||||
"
|
||||
>
|
||||
<i
|
||||
data-lucide="menu"
|
||||
@@ -3077,17 +3058,6 @@
|
||||
id="help-search-clear"
|
||||
title="Effacer la recherche"
|
||||
aria-label="Effacer"
|
||||
onclick="
|
||||
var s =
|
||||
document.getElementById(
|
||||
'help-nav-search',
|
||||
);
|
||||
if (s) {
|
||||
s.value = '';
|
||||
s.dispatchEvent(new Event('input'));
|
||||
s.focus();
|
||||
}
|
||||
"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
+12
-2
@@ -1305,8 +1305,7 @@ async function _startMfaSetup() {
|
||||
// secret when the backend has no QR generator available.
|
||||
const qrImg = data.qr_data_url
|
||||
? `<img id="mfa-qr-img" alt="QR Code" class="mfa-qr-code-img"
|
||||
src="${data.qr_data_url}"
|
||||
onerror="this.style.display='none';document.getElementById('mfa-qr-fallback').style.display='block';">`
|
||||
src="${data.qr_data_url}">`
|
||||
: "";
|
||||
const fallbackStyle = data.qr_data_url ? "display:none" : "";
|
||||
flowArea.innerHTML = `
|
||||
@@ -1335,6 +1334,17 @@ async function _startMfaSetup() {
|
||||
codeInput.value = codeInput.value.replace(/[^0-9]/g, "");
|
||||
});
|
||||
|
||||
// QR fallback (#87, ex-onerror inline) : si l'image ne charge pas,
|
||||
// afficher la saisie manuelle du secret.
|
||||
const qrImgEl = document.getElementById("mfa-qr-img");
|
||||
if (qrImgEl) {
|
||||
qrImgEl.addEventListener("error", () => {
|
||||
qrImgEl.style.display = "none";
|
||||
const fallback = document.getElementById("mfa-qr-fallback");
|
||||
if (fallback) fallback.style.display = "block";
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("mfa-confirm-btn").addEventListener("click", async () => {
|
||||
const code = codeInput.value.trim();
|
||||
if (code.length !== 6) return;
|
||||
|
||||
+39
-3
@@ -363,6 +363,27 @@ function initHelpModal() {
|
||||
}
|
||||
});
|
||||
|
||||
// Help TOC hamburger + search clear (#87, ex-onclick inline in index.html).
|
||||
var helpHamburger = document.getElementById("help-hamburger");
|
||||
if (helpHamburger) {
|
||||
helpHamburger.addEventListener("click", function() {
|
||||
var n = document.getElementById("help-nav");
|
||||
if (n) {
|
||||
var d = n.style.display;
|
||||
n.style.display = d === "none" || d === "" ? "flex" : "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
var helpSearch = document.getElementById("help-nav-search");
|
||||
var helpSearchClear = document.getElementById("help-search-clear");
|
||||
if (helpSearchClear && helpSearch) {
|
||||
helpSearchClear.addEventListener("click", function() {
|
||||
helpSearch.value = "";
|
||||
helpSearch.dispatchEvent(new Event("input"));
|
||||
helpSearch.focus();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && modal.classList.contains("active")) {
|
||||
closeHelpModal();
|
||||
@@ -883,7 +904,7 @@ function initConfigModal() {
|
||||
});
|
||||
}
|
||||
|
||||
// Logout button — handled inline in index.html (onclick)
|
||||
// Logout button — wired here with addEventListener (#87, no inline onclick)
|
||||
|
||||
// Config nav search
|
||||
var cfgSearch = document.getElementById("config-nav-search");
|
||||
@@ -901,6 +922,16 @@ function initConfigModal() {
|
||||
});
|
||||
}
|
||||
|
||||
// Config search clear button (#87, ex-onclick inline).
|
||||
var cfgSearchClear = document.getElementById("config-search-clear");
|
||||
if (cfgSearchClear && cfgSearch) {
|
||||
cfgSearchClear.addEventListener("click", function() {
|
||||
cfgSearch.value = "";
|
||||
cfgSearch.dispatchEvent(new Event("input"));
|
||||
cfgSearch.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// BUG-071/#114: mobile table of contents. #config-nav shares the .help-nav
|
||||
// rule that hides it below 768px, but — unlike the help modal — the config
|
||||
// modal had no toggle to reveal it, leaving mobile users with no way to
|
||||
@@ -1575,13 +1606,15 @@ export async function openShareDialog(vault, path) {
|
||||
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:4px">${escapeHtml(vault)}/${escapeHtml(path)}</p>
|
||||
${expiresInfo}
|
||||
<p style="font-size:0.75rem;color:var(--text-muted);margin-bottom:8px">${existingShare.access_count} vue(s)</p>
|
||||
<input type="text" class="share-url-input" value="${url}" readonly onclick="this.select()">
|
||||
<input type="text" class="share-url-input" value="${url}" readonly>
|
||||
<div class="share-dialog-actions">
|
||||
<button class="share-copy-btn">📋 Copier le lien</button>
|
||||
<button class="share-revoke-btn">🗑 Révoquer</button>
|
||||
<button class="share-close-btn">Fermer</button>
|
||||
</div>
|
||||
</div>`;
|
||||
const shareUrlInput = div.querySelector(".share-url-input");
|
||||
if (shareUrlInput) shareUrlInput.addEventListener("click", function() { shareUrlInput.select(); });
|
||||
div.querySelector(".share-copy-btn").addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
@@ -2525,7 +2558,10 @@ function initProfile() {
|
||||
} catch(e) {}
|
||||
});
|
||||
|
||||
// Logout button — handled inline in index.html (onclick)
|
||||
// Logout button (#87, ex-onclick inline in index.html).
|
||||
if (logoutBtn && window.handleLogout) {
|
||||
logoutBtn.addEventListener('click', function() { window.handleLogout(); });
|
||||
}
|
||||
|
||||
// ── Avatar (#113) ────────────────────────────────────────────────
|
||||
var avatarField = document.getElementById('profile-avatar-field');
|
||||
|
||||
@@ -478,8 +478,8 @@ function openTemplateModal() {
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="modal-footer">' +
|
||||
'<button class="btn btn-primary" data-i18n="common.close" onclick="this.closest(\'.modal-overlay\').remove()">Close</button>' +
|
||||
'<button class="btn btn-secondary" onclick="copyTemplate()">Copy to Clipboard</button>' +
|
||||
'<button class="btn btn-primary btn-modal-close" data-i18n="common.close">Close</button>' +
|
||||
'<button class="btn btn-secondary btn-copy-template">Copy to Clipboard</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
@@ -487,11 +487,11 @@ function openTemplateModal() {
|
||||
|
||||
modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove());
|
||||
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
|
||||
|
||||
window.copyTemplate = () => {
|
||||
modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove());
|
||||
modal.querySelector('.btn-copy-template')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(JSON.stringify(template, null, 2));
|
||||
showToast('Template copied to clipboard', 'success');
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -508,12 +508,13 @@ function openCodeModal(name, code) {
|
||||
'<pre class="code-block" style="max-height: 500px; overflow: auto;">' + escapeHtml(code) + '</pre>' +
|
||||
'</div>' +
|
||||
'<div class="modal-footer">' +
|
||||
'<button class="btn btn-primary" data-i18n="common.close" onclick="this.closest(\'.modal-overlay\').remove()">Close</button>' +
|
||||
'<button class="btn btn-primary btn-modal-close" data-i18n="common.close">Close</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(modal);
|
||||
modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove());
|
||||
modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove());
|
||||
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -543,10 +543,16 @@ function showUpdateNotification() {
|
||||
message.innerHTML = `
|
||||
<div class="pwa-update-content">
|
||||
<span>Une nouvelle version d'ObsiGate est disponible !</span>
|
||||
<button class="pwa-update-btn" onclick="window.location.reload()">Mettre à jour</button>
|
||||
<button class="pwa-update-dismiss" onclick="this.parentElement.parentElement.remove()">×</button>
|
||||
<button class="pwa-update-btn">Mettre à jour</button>
|
||||
<button class="pwa-update-dismiss">×</button>
|
||||
</div>
|
||||
`;
|
||||
message.querySelector(".pwa-update-btn").addEventListener("click", function() {
|
||||
window.location.reload();
|
||||
});
|
||||
message.querySelector(".pwa-update-dismiss").addEventListener("click", function() {
|
||||
message.remove();
|
||||
});
|
||||
document.body.appendChild(message);
|
||||
|
||||
// Auto-dismiss after 30 seconds
|
||||
|
||||
@@ -1140,10 +1140,10 @@ export function renderFile(data) {
|
||||
<div class="pdf-viewer-container">
|
||||
<div class="pdf-toolbar">
|
||||
<span class="pdf-info">PDF — ${pages} pages</span>
|
||||
<button class="btn-action" onclick="window.open('${pdfUrl}', '_blank')">
|
||||
<button class="btn-action" data-pdf-url="${pdfUrl}">
|
||||
<i data-lucide="external-link" style="width:14px;height:14px"></i> Plein écran
|
||||
</button>
|
||||
<button class="btn-action" onclick="window.open('/api/file/${encodeURIComponent(data.vault)}/download?path=${encodeURIComponent(data.path)}', '_blank')">
|
||||
<button class="btn-action" data-download-url="/api/file/${encodeURIComponent(data.vault)}/download?path=${encodeURIComponent(data.path)}">
|
||||
<i data-lucide="download" style="width:14px;height:14px"></i> Télécharger
|
||||
</button>
|
||||
</div>
|
||||
@@ -1152,6 +1152,11 @@ export function renderFile(data) {
|
||||
<iframe src="${pdfUrl}" data-pdf-url="${pdfUrl}" class="pdf-iframe" title="${escapeHtml(data.title)}"></iframe>
|
||||
</div>
|
||||
</div>`;
|
||||
area.querySelectorAll('.pdf-toolbar .btn-action').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
window.open(btn.dataset.pdfUrl || btn.dataset.downloadUrl, '_blank');
|
||||
});
|
||||
});
|
||||
area.querySelectorAll('.pdf-toc a[data-page]').forEach((link) => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user