Le select « Ajouter une page » ne fonctionnait pas car admin.js
n'était pas chargé dans la section books du template.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Un seul appel API retourne l'analyse critique ET la proposition d'article
via le séparateur ===CRITIQUE===/===REWRITE===. Le panneau affiche les deux
sections avec un bouton « Appliquer la proposition ».
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
post_form.php n'était jamais inclus — les boutons IA sont ajoutés dans la vraie
page d'édition (wizard/step1.php). ai-editor.js cherche #wz-content en priorité
et extrait le titre depuis la première ligne # du Markdown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Les <form> admin_toggle_featured et duplicate étaient imbriqués dans
#bulk-form — HTML invalide, le navigateur soumettait le form parent
(suppression) au lieu du bon. Fix : attribut form="id" HTML5 + forms
cachés placés après le bulk-form. Ajoute note CSP + nested forms dans
consignes.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- post_view.php : section Historique dans la sidebar pour les connectés
liste les 10 dernières révisions avec date + commentaire → lien diff (#82)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- post_view.php : widget ★ 1-5 étoiles pour les connectés, moyenne + nb votes pour tous (#13)
- admin : onglet /admin/flux liste tous les flux rss_feeds avec suppression (#87)
- case 'admin_delete_feed' : suppression admin d'un flux sans contrainte email (#87)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- buildAutoSeoDesc() : entités HTML décodées + titre supprimé en tête (#91)
- post_confirm.js : guard null sur #confirm-slug absent (#91)
- feed.php : <media:thumbnail> avec image de couverture RSS (#90)
- admin livres : slug auto depuis le titre + filtre articles (#89)
- BookManager::sanitizeSlug() passé public
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- post_view.php : barre de partage (mail, X, LinkedIn, Mastodon, copier, Web Share) sur articles publiés (#47)
- share.js : logique clipboard + navigator.share sans script tiers, compatible CSP (#47)
- addFile() : hardlink vers fichier identique si même hash16-size.ext dans un autre article (#35)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getAll() : cache fichier articles_list.json, invalidé à chaque écriture (#16)
- layout.php : fingerprinting ?v=<hash> sur CSS/JS pour invalidation navigateur (#18)
- case 'view' : Last-Modified + 304 Not Modified pour les articles publiés (#18)
- case 'not_found' : logging JSON des 404 dans _logs/not_found.jsonl (#52)
- case 'view' : echo nu → templates/404.php pour brouillons/privés (#52)
- admin.js : clic sur ligne tableau → toggle bulk-check (#86)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- magic.php : GET=confirmation page, POST=consommation (protège vs scanners) (#27)
- verify_comment : email de notification à l'auteur de l'article (#44)
- login/index.php : rate limit par IP (MAGIC_MAX_PER_IP_HOUR=10) (#23)
- ArticleManager::duplicate() + route POST /duplicate/{uuid} + bouton ⧉ admin/articles (#7)
- post_view.php : cache JSON du rendu Markdown (invalidé sur mtime index.md) (#17)
- post_view.php : loading="lazy" sur toutes les <img> du contenu (#21)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SearchLogParser : visiteurs uniques par terme (IPs distinctes) au lieu de hits bruts (#41)
- SearchLogParser : paramètre $days (7/14), cache distinct par période, filtre logFiles par date (#46)
- admin/searches : boutons 7 j / 14 j, label dynamique, colonne « Visiteurs » (#41, #46)
- URL inconnue / slug absent : redirect 302 /search?q=… au lieu de page 404 (#57)
- edit_tags : masquer abbrev/camel si des valeurs connues existent pour le type (#48)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- admin/articles : champ filter_search (titre, insensible casse) cumulable avec auteur/catégorie/statut (#85)
- admin/articles : colonne ★ avec toggle rapide featured + filtre filter_featured (#84)
- post/ : date de modification sous la date de publication si modifié après mise en ligne (#81)
- sources/ : bouton ← Retour à l'article vers post/<slug> au lieu de /edit/<uuid> (#83)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- loadArticle($dir, false) dans loadAll() — meta.json seulement, pas d'index.md
- loadAll() enrichit les articles avec plain depuis search_index (1 lecture JSON)
- rebuildSearchIndex() lit index.md directement + ajoute featured au schéma
- getSearchIndex() rebuilde automatiquement si featured absent
- post_list, author_articles, author_profile : excerpts via plain, plus de Parsedown
- Ferme #24
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
En mode édition, le slug ne doit jamais changer. Suppression du
hidden[slug] dans step6.php et du bloc qui le sauvegardait dans
le draft overlay avant commitDraftOverlay().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mkArticleDir() crée les répertoires avec chmod 0775 explicite (bypass umask)
- delete() retourne bool et détecte l'échec sans reconstruire les index
- removeDir() supprime les warnings PHP (@unlink, @rmdir, @scandir)
- post_view.php affiche un message d'erreur si delete_failed=1
- index.php redirige vers l'article avec ?delete_failed=1 si échec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
En-têtes "Titre" et "Date" cliquables, indicateur ↑/↓, paramètres sort/dir
préservés lors du filtrage. Tri appliqué après filtres côté PHP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remplace body[data-density] + CSS externe par un élément <style id="density-fouc">
injecté dynamiquement dans <head>, insensible aux problèmes de spécificité et de cache CSS.
Remplace aussi closest() par une boucle parentNode et dataset par getAttribute.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Widget retiré du hero-search, replacé en position:fixed top-right (sous navbar)
- max-width !important pour garantir l'override de Bootstrap sur main.container-fluid
- transition douce sur main, caché en < 576px
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- post_list.php : section AJAX qui lit /trending?period=1h en XML (DOMParser) — plus de rendu PHP
- admin_stats.php : colonne "Pages les plus visitées" chargée en AJAX depuis /trending?period=14d XML
- index.php/stats : suppression de topGrouped pour /post/ ; seuls /book/ et ASN restent côté serveur
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Page d'accueil et /tendances lisent uniquement le cache trending_{period}.json
produit par le flux RSS /trending?period=…. Aucun parsing de logs en dehors du
flux RSS. Rubrique renommée "Meilleures audiences · 1 heure".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TrendingParser : lit les logs Apache, compte les visiteurs uniques (IPs, HTTP 200),
supporte plusieurs préfixes (/post/, /book/) et un seul parse via topGrouped()
- /trending?period=… : flux RSS des 50 articles les plus consultés, 10 périodes
de 10 min à 1 an, cache TTL adaptatif
- /tendances : page publique avec sélecteur de période, top 20 articles,
tableau des flux RSS et section méthodologie
- /admin/stats : remplace AccessLogParser (hits) par TrendingParser (visiteurs uniques)
- Page d'accueil : rubrique Tendances alimentée par les logs 1h (fallback DB)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Les données coûteuses (parsing des logs, batchLookup ASN) sont mises en cache
dans DATA_PATH/.stats_cache.json. Le cache expire après 60 secondes via filemtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le bouton "Mettre à jour" appelle désormais `sudo /usr/local/bin/folio-upgrade.sh`
via exec() plutôt que d'exécuter git pull + composer + migrations directement en PHP.
Le script shell (template dans scripts/server/) gère la séquence complète : clone fresh,
permissions www-data, restauration .env, composer install, migrations SQL, .sessions,
safe.directory. Le journal de la dernière mise à jour est conservé dans DATA_PATH/.upgrade-log
et affiché en <details> dans l'admin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>