feat: filtres et suppression massive dans admin/articles, profil auteur amélioré

- Admin articles : filtres auteur/catégorie/statut par GET, compteur de résultats
- Admin articles : suppression massive avec checkboxes, confirmation JS, contrôle d'ownership
- Profil auteur : bio tronquée à 3 lignes avec bouton 'plus', limite à 6 articles affichés
- Profil auteur : bouton CTA pill 'Mes liens' vers /liens/{slug}
- Page liens : boutons pill colorés (palette auto par index), fond gris clair, grand avatar
This commit is contained in:
Cedric Abonnel
2026-05-13 00:57:02 +02:00
parent f3584159c1
commit a926e1825d
11 changed files with 350 additions and 51 deletions
+27
View File
@@ -1479,3 +1479,30 @@ footer.mt-5 { margin-top: 0 !important; }
margin: 0;
line-height: 1.6;
}
/* Bouton flottant "+" — nouvel article */
.fab-new {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 1050;
width: 3.25rem;
height: 3.25rem;
border-radius: 50%;
background: var(--vl-accent);
color: #fff;
font-size: 1.75rem;
line-height: 3.25rem;
text-align: center;
text-decoration: none;
box-shadow: 0 4px 16px rgba(79,70,229,.45);
transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
user-select: none;
}
.fab-new:hover,
.fab-new:focus {
background: var(--vl-accent-dark);
color: #fff;
box-shadow: 0 6px 20px rgba(79,70,229,.55);
transform: scale(1.08);
}
+14
View File
@@ -0,0 +1,14 @@
(function(){
var bio = document.getElementById('author-bio');
var btn = document.getElementById('bio-toggle');
if (!bio || !btn) return;
requestAnimationFrame(function() {
if (bio.scrollHeight > bio.clientHeight + 2) { btn.hidden = false; }
});
btn.addEventListener('click', function() {
var exp = btn.getAttribute('aria-expanded') === 'true';
bio.classList.toggle('bio-clamped', exp);
btn.textContent = exp ? 'plus' : 'moins';
btn.setAttribute('aria-expanded', exp ? 'false' : 'true');
});
})();
+29
View File
@@ -0,0 +1,29 @@
(function() {
const list = document.getElementById('links-sortable');
if (!list) return;
let dragged = null;
list.querySelectorAll('li').forEach(li => {
li.setAttribute('draggable', true);
li.addEventListener('dragstart', () => { dragged = li; li.style.opacity = '.4'; });
li.addEventListener('dragend', () => { dragged = null; li.style.opacity = ''; saveOrder(); });
li.addEventListener('dragover', e => { e.preventDefault(); const after = getDragAfter(list, e.clientY); after ? list.insertBefore(dragged, after) : list.appendChild(dragged); });
});
function getDragAfter(container, y) {
return [...container.querySelectorAll('li:not([style*="opacity"])')].reduce((closest, el) => {
const box = el.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
return offset < 0 && offset > closest.offset ? { offset, element: el } : closest;
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
function saveOrder() {
const form = document.getElementById('reorder-form');
if (!form) return;
form.querySelectorAll('input').forEach(i => i.remove());
list.querySelectorAll('li[data-id]').forEach(li => {
const inp = document.createElement('input');
inp.type = 'hidden'; inp.name = 'order[]'; inp.value = li.dataset.id;
form.appendChild(inp);
});
form.submit();
}
})();