fix 1.2.1 : cache index.md, H1 rendu, scroll wizard, titre Modifier

- ArticleManager : invalider le cache si index.md est plus récent que meta.json
- migration_001 : touch(meta.json) après maj index.md pour forcer l'invalidation
- post_view.php : masquer le H1 initial du contenu (déjà affiché par le template)
- step1.php : en-tête "Modifier" sans le titre de l'article
- wizard.js : retirer scrollToCursor (erroné sur auto-resize) ; Ctrl+Home/End via scrollIntoView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 23:07:15 +02:00
parent 1dbe6d8dd3
commit 72cb7acae4
7 changed files with 29 additions and 27 deletions
+11
View File
@@ -9,6 +9,17 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
--- ---
## [1.2.1] - 2026-05-14
### Corrigé
- Cache article invalidé si `index.md` est plus récent que `meta.json` (migration de contenu ne se reflétait pas)
- Migration 001 : `touch(meta.json)` après écriture de `index.md` pour invalider le cache
- `post_view.php` : le `# Titre` Markdown est retiré du rendu (déjà affiché par le template)
- Wizard étape 1 : en-tête affiche « Modifier » sans répéter le titre de l'article
- `wizard.js` : suppression de `scrollToCursor` (calcul erroné sur textarea auto-resize) ; Ctrl+Home / Ctrl+End scrollent correctement via `scrollIntoView`
---
## [1.2.0] - 2026-05-14 ## [1.2.0] - 2026-05-14
### Ajouté ### Ajouté
+7 -20
View File
@@ -13,20 +13,13 @@ document.addEventListener('DOMContentLoaded', function () {
ta.addEventListener('input', resizeTa); ta.addEventListener('input', resizeTa);
resizeTa(); resizeTa();
function scrollToCursor() { // Ctrl+Home / Ctrl+End : scroller la fenêtre vers le début/fin du textarea
var lineH = parseFloat(getComputedStyle(ta).lineHeight) || 20; ta.addEventListener('keydown', function (e) {
var padT = parseFloat(getComputedStyle(ta).paddingTop) || 8; if (!(e.ctrlKey || e.metaKey) || (e.key !== 'Home' && e.key !== 'End')) return;
var lines = ta.value.substr(0, ta.selectionStart).split('\n').length; requestAnimationFrame(function () {
var cursorY = ta.getBoundingClientRect().top + padT + lines * lineH; ta.scrollIntoView({ block: e.key === 'Home' ? 'start' : 'end', behavior: 'smooth' });
var margin = lineH * 3; });
if (cursorY > window.innerHeight - margin) { });
window.scrollBy({ top: cursorY - window.innerHeight + margin, behavior: 'instant' });
} else if (cursorY < margin) {
window.scrollBy({ top: cursorY - margin, behavior: 'instant' });
}
}
ta.addEventListener('keyup', scrollToCursor);
ta.addEventListener('click', scrollToCursor);
} }
// ─── Ctrl+Enter soumet le formulaire ──────────────────────────────────── // ─── Ctrl+Enter soumet le formulaire ────────────────────────────────────
@@ -61,12 +54,6 @@ document.addEventListener('DOMContentLoaded', function () {
var data = await res.json(); var data = await res.json();
if (data.ok) { if (data.ok) {
indicator.textContent = 'Sauvegardé à ' + data.time; indicator.textContent = 'Sauvegardé à ' + data.time;
// Mettre à jour le titre de la page si le serveur l'a extrait
var pageTitle = document.getElementById('wz-page-title');
if (pageTitle && data.title) {
var prefix = pageTitle.dataset.prefix || '';
pageTitle.textContent = prefix ? prefix + data.title : data.title;
}
} else { } else {
indicator.textContent = 'Erreur de sauvegarde'; indicator.textContent = 'Erreur de sauvegarde';
} }
+1 -1
View File
@@ -1 +1 @@
1.2.0 1.2.1
@@ -43,6 +43,7 @@ foreach (glob($dataDir . '/*/meta.json') as $metaPath) {
} }
file_put_contents($mdPath, '# ' . $title . "\n\n" . ltrim($content)); file_put_contents($mdPath, '# ' . $title . "\n\n" . ltrim($content));
touch($metaPath);
$updated++; $updated++;
} }
+3 -2
View File
@@ -1164,8 +1164,9 @@ class ArticleManager
$uuid = basename($dir); $uuid = basename($dir);
$cachePath = $this->articleCachePath($uuid); $cachePath = $this->articleCachePath($uuid);
// Utiliser le cache si plus récent que meta.json // Utiliser le cache si plus récent que meta.json ET index.md
if (file_exists($cachePath) && filemtime($cachePath) >= filemtime($metaPath)) { $contentMtime = file_exists($dir . '/index.md') ? filemtime($dir . '/index.md') : 0;
if (file_exists($cachePath) && filemtime($cachePath) >= filemtime($metaPath) && filemtime($cachePath) >= $contentMtime) {
$cached = json_decode((string) file_get_contents($cachePath), true); $cached = json_decode((string) file_get_contents($cachePath), true);
if (is_array($cached) && !empty($cached['uuid'])) { if (is_array($cached) && !empty($cached['uuid'])) {
return $cached; return $cached;
+3 -1
View File
@@ -9,6 +9,8 @@ $_accentMap = [
]; ];
$_tocItems = []; $_tocItems = [];
$_tocSeen = []; $_tocSeen = [];
// Le titre H1 est déjà affiché par le template ; on le retire du rendu.
$_rawForRender = preg_replace('/^\s*# [^\n]*\n*/u', '', $rawContent);
$_renderedContent = preg_replace_callback( $_renderedContent = preg_replace_callback(
'/<(h[23])>(.+?)<\/h[23]>/i', '/<(h[23])>(.+?)<\/h[23]>/i',
function ($m) use (&$_tocItems, &$_tocSeen, $_accentMap) { function ($m) use (&$_tocItems, &$_tocSeen, $_accentMap) {
@@ -31,7 +33,7 @@ $_renderedContent = preg_replace_callback(
$_tocItems[] = ['level' => $level, 'text' => $plain, 'id' => $id]; $_tocItems[] = ['level' => $level, 'text' => $plain, 'id' => $id];
return "<{$tag} id=\"" . htmlspecialchars($id) . "\">{$inner}</{$tag}>"; return "<{$tag} id=\"" . htmlspecialchars($id) . "\">{$inner}</{$tag}>";
}, },
$Parsedown->text($rawContent) $Parsedown->text($_rawForRender)
); );
ob_start(); ob_start();
+3 -3
View File
@@ -19,8 +19,7 @@ $_hasUuid = $_wizUuid !== '';
<!-- En-tête avec boutons ────────────────────────────────────────────────── --> <!-- En-tête avec boutons ────────────────────────────────────────────────── -->
<div class="d-flex align-items-center justify-content-between gap-3 mb-4 flex-wrap"> <div class="d-flex align-items-center justify-content-between gap-3 mb-4 flex-wrap">
<div> <div>
<h1 class="h4 mb-0" id="wz-page-title" <h1 class="h4 mb-0" id="wz-page-title"><?= $mode === 'create' ? 'Nouvel article' : 'Modifier' ?></h1>
data-prefix="<?= $mode === 'edit' ? 'Modifier — ' : '' ?>"><?= $mode === 'create' ? 'Nouvel article' : htmlspecialchars('Modifier — ' . ($article['title'] ?? '')) ?></h1>
<?php if ($_hasUuid): ?> <?php if ($_hasUuid): ?>
<span id="autosave-indicator" class="text-muted small"></span> <span id="autosave-indicator" class="text-muted small"></span>
<?php endif; ?> <?php endif; ?>
@@ -44,7 +43,8 @@ $_hasUuid = $_wizUuid !== '';
<div class="mb-3"> <div class="mb-3">
<label for="wz-content" class="form-label fw-semibold">Contenu <small class="text-muted fw-normal">(Markdown)</small></label> <label for="wz-content" class="form-label fw-semibold">Contenu <small class="text-muted fw-normal">(Markdown)</small></label>
<textarea class="form-control font-monospace" id="wz-content" name="content" rows="18" <textarea class="form-control font-monospace" id="wz-content" name="content" rows="18"
style="min-height:320px"><?= htmlspecialchars($content ?? '') ?></textarea> style="min-height:320px"
placeholder="# Titre de l'article&#10;&#10;Votre contenu ici…"><?= htmlspecialchars($content ?? '') ?></textarea>
</div> </div>
</div><!-- /col-lg-9 --> </div><!-- /col-lg-9 -->