fix : externaliser tous les scripts inline (CSP script-src 'self')

Tous les <script> inline et event handlers inline bloqués par la CSP sont
déplacés vers des fichiers JS statiques servis par 'self' :
- density-fouc.js  : anti-FOUC densité (chargé en <head>)
- density.js       : widget L/M/S
- trending-home.js : AJAX "Meilleures audiences" (RSS XML)
- admin-stats.js   : groupes AS + pages trending (RSS XML)
- admin.js         : bookAddArticle + bulk-delete (onclick/onchange → listeners)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 21:00:26 +02:00
parent 58a110d5b9
commit 3e856dc476
9 changed files with 199 additions and 157 deletions
+68
View File
@@ -0,0 +1,68 @@
/* Admin stats : groupes AS + chargement pages via flux RSS XML /trending?period=14d */
// ── Groupes de réseaux ────────────────────────────────────────────────────────
(function () {
var addBtn = document.getElementById('as-group-add');
if (!addBtn) { return; }
addBtn.addEventListener('click', function () {
var tpl = document.getElementById('as-group-tpl').content.cloneNode(true);
document.getElementById('as-groups-list').appendChild(tpl);
});
document.getElementById('as-groups-list').addEventListener('click', function (e) {
if (e.target.classList.contains('as-group-delete')) {
e.target.closest('.as-group-row').remove();
}
});
}());
// ── Pages les plus visitées (RSS XML) ────────────────────────────────────────
(function () {
var container = document.getElementById('stats-pages-container');
var badge = document.getElementById('stats-pages-count');
if (!container) { return; }
function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
fetch('/trending?period=14d')
.then(function (r) { return r.ok ? r.text() : Promise.reject(); })
.then(function (xml) {
var doc = new DOMParser().parseFromString(xml, 'application/xml');
var items = Array.from(doc.querySelectorAll('item'));
if (!items.length) {
container.innerHTML = '<p class="text-muted p-3 mb-0">Aucune donnée.</p>';
return;
}
var rows = items.map(function (item) {
var raw = (item.querySelector('title') || { textContent: '' }).textContent;
var link = ((item.querySelector('link') || {}).textContent || '').trim();
var m = raw.match(/\((\d+)\s+visiteurs?\)$/);
var vis = m ? parseInt(m[1], 10) : 0;
var title = raw.replace(/\s*\(\d+\s+visiteurs?\)$/, '');
var slug = decodeURIComponent(link.replace(/.*\/post\//, ''));
return { title: title, link: link, slug: slug, vis: vis };
});
var maxV = Math.max.apply(null, rows.map(function (r) { return r.vis; })) || 1;
var html = '<div class="table-responsive"><table class="table table-sm table-hover mb-0 small"><tbody>';
rows.forEach(function (row, i) {
var pct = Math.round(row.vis / maxV * 100);
var vis = row.vis.toLocaleString('fr-FR');
html += '<tr>'
+ '<td class="text-muted ps-3" style="width:2rem">' + (i + 1) + '</td>'
+ '<td><a href="' + esc(row.link) + '" target="_blank" class="text-decoration-none text-truncate d-block" style="max-width:260px" title="' + esc(row.slug) + '">'
+ esc(row.title || row.slug) + '</a>'
+ '<div class="progress mt-1" style="height:3px"><div class="progress-bar" style="width:' + pct + '%"></div></div></td>'
+ '<td class="text-end fw-semibold pe-3">' + vis + ' <span class="text-muted fw-normal">vis.</span></td>'
+ '</tr>';
});
html += '</tbody></table></div>';
if (badge) { badge.textContent = rows.length + ' URLs'; }
container.innerHTML = html;
})
.catch(function () {
container.innerHTML = '<p class="text-muted p-3 mb-0">Impossible de charger le flux.</p>';
});
}());
+24
View File
@@ -45,4 +45,28 @@ document.addEventListener('DOMContentLoaded', function () {
}
});
}
// Suppression groupée avec confirmation (remplace onclick inline)
var bulkDeleteBtn = document.getElementById('bulk-delete-btn');
if (bulkDeleteBtn) {
bulkDeleteBtn.addEventListener('click', function (e) {
var checked = document.querySelectorAll('.bulk-check:checked').length;
if (checked === 0) { e.preventDefault(); return; }
var msg = bulkDeleteBtn.getAttribute('data-confirm-bulk') || 'Confirmer ?';
if (!window.confirm(msg)) { e.preventDefault(); }
});
}
// Ajout d'un article à un livre (remplace onchange="bookAddArticle(this)")
var bookArticleSel = document.getElementById('book-article-select');
if (bookArticleSel) {
bookArticleSel.addEventListener('change', function () {
var slug = bookArticleSel.value;
if (!slug) { return; }
var ta = document.getElementById('book-articles-ta');
var lines = ta.value.split('\n').map(function (s) { return s.trim(); }).filter(Boolean);
if (lines.indexOf(slug) === -1) { lines.push(slug); ta.value = lines.join('\n'); }
bookArticleSel.value = '';
});
}
});
+11
View File
@@ -0,0 +1,11 @@
/* Anti-FOUC densité — chargé tôt dans <head> pour appliquer max-width avant rendu de <main> */
(function () {
var d = localStorage.getItem('folio_density');
if (d && d !== 'l') {
var mw = d === 'm' ? '980px' : '660px';
var s = document.createElement('style');
s.id = 'density-fouc';
s.textContent = 'main[role="main"]{max-width:' + mw + '!important;margin-left:auto!important;margin-right:auto!important}';
document.head.appendChild(s);
}
}());
+38
View File
@@ -0,0 +1,38 @@
/* Sélecteur de densité L/M/S — persisté dans localStorage */
(function () {
var KEY = 'folio_density';
var cur = localStorage.getItem(KEY) || 'l';
function applyDensity(d) {
var fouc = document.getElementById('density-fouc');
if (d !== 'l') {
var mw = d === 'm' ? '980px' : '660px';
if (!fouc) {
fouc = document.createElement('style');
fouc.id = 'density-fouc';
document.head.appendChild(fouc);
}
fouc.textContent = 'main[role="main"]{max-width:' + mw + '!important;margin-left:auto!important;margin-right:auto!important}';
} else {
if (fouc) { fouc.parentNode.removeChild(fouc); }
}
document.querySelectorAll('.density-btn').forEach(function (btn) {
btn.classList.toggle('active', btn.getAttribute('data-d') === d);
});
}
applyDensity(cur);
document.addEventListener('click', function (e) {
var el = e.target;
while (el && el !== document) {
if (el.classList && el.classList.contains('density-btn')) {
cur = el.getAttribute('data-d') || 'l';
try { localStorage.setItem(KEY, cur); } catch (ignore) {}
applyDensity(cur);
return;
}
el = el.parentNode;
}
});
}());
+51
View File
@@ -0,0 +1,51 @@
/* Chargement AJAX de la section "Meilleures audiences" via le flux RSS XML /trending?period=1h */
(function () {
var grid = document.getElementById('home-audiences-grid');
if (!grid) { return; }
var gradients = [
'linear-gradient(135deg,#667eea 0%,#764ba2 100%)',
'linear-gradient(135deg,#f093fb 0%,#f5576c 100%)',
'linear-gradient(135deg,#4facfe 0%,#00f2fe 100%)',
'linear-gradient(135deg,#43e97b 0%,#38f9d7 100%)',
'linear-gradient(135deg,#fa709a 0%,#fee140 100%)',
'linear-gradient(135deg,#a18cd1 0%,#fbc2eb 100%)'
];
function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
fetch('/trending?period=1h')
.then(function (r) { return r.ok ? r.text() : Promise.reject(); })
.then(function (xml) {
var doc = new DOMParser().parseFromString(xml, 'application/xml');
var items = Array.from(doc.querySelectorAll('item')).slice(0, 6);
if (!items.length) { return; }
grid.innerHTML = items.map(function (item, i) {
var raw = (item.querySelector('title') || { textContent: '' }).textContent;
var title = raw.replace(/\s*\(\d+\s+visiteurs?\)$/, '');
var link = ((item.querySelector('link') || {}).textContent || '#').trim();
var pd = (item.querySelector('pubDate') || { textContent: '' }).textContent;
var date = '';
try { if (pd) { date = new Date(pd).toLocaleDateString('fr-FR'); } } catch (err) {}
var grad = gradients[i % gradients.length];
return '<article class="card">'
+ '<div class="card-cover" style="background:' + grad + '"></div>'
+ '<div class="card-body d-flex flex-column">'
+ '<h2 class="card-title"><a href="' + esc(link) + '">' + esc(title) + '</a></h2>'
+ '<div class="post-entry-meta mt-auto">'
+ (date ? '<span>' + esc(date) + '</span>' : '')
+ '<a href="' + esc(link) + '" class="post-entry-read">→ lire</a>'
+ '</div></div>'
+ '<a href="' + esc(link) + '" class="stretched-link"></a>'
+ '</article>';
}).join('');
var section = document.getElementById('home-audiences-section');
if (section) { section.hidden = false; }
})
.catch(function () {});
}());