perf : getAll() sans contenu, search_index + featured, excerpts via plain (v1.6.14)

- 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>
This commit is contained in:
2026-05-15 23:50:58 +02:00
parent c17cad9c66
commit 347e4be0b7
6 changed files with 67 additions and 32 deletions
+9
View File
@@ -5,6 +5,15 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnag
---
## [1.6.14] - 2026-05-15
### Modifié
- Perf : `getAll()` ne charge plus le contenu Markdown — `loadArticle()` reçoit `$withContent = false` dans `loadAll()`, seul `getByUuid()` lit encore `index.md` (#24)
- Perf : `search_index.json` enrichi du champ `featured` ; `rebuildSearchIndex()` lit `index.md` directement (indépendant du cache article)
- Perf : excerpts dans `post_list`, `author_articles`, `author_profile` proviennent du champ `plain` pré-calculé — plus de passage par Parsedown (#24)
---
## [1.6.13] - 2026-05-15
### Ajouté
+1 -1
View File
@@ -1 +1 @@
1.6.13
1.6.14
+35 -9
View File
@@ -44,7 +44,7 @@ class ArticleManager
continue;
}
$article = $this->loadArticle($dir);
$article = $this->loadArticle($dir, false);
if (!$article) {
continue;
}
@@ -53,6 +53,19 @@ class ArticleManager
usort($articles, static fn ($a, $b) => strcmp($b['published_at'] ?? '', $a['published_at'] ?? ''));
// Enrichir avec le plain text pré-calculé (pour les excerpts sans charger index.md)
$siPath = $this->dataDir . '/search_index.json';
if (file_exists($siPath)) {
$si = json_decode((string)file_get_contents($siPath), true);
if (is_array($si)) {
$plainByUuid = array_column($si, 'plain', 'uuid');
foreach ($articles as &$a) {
$a['plain'] = $plainByUuid[$a['uuid']] ?? '';
}
unset($a);
}
}
return $articles;
}
@@ -944,19 +957,25 @@ class ArticleManager
{
$index = [];
foreach ($this->getAll() as $article) {
$uuid = $article['uuid'] ?? '';
$contentPath = $this->dataDir . '/' . $uuid . '/index.md';
$content = $uuid !== '' && file_exists($contentPath)
? (string)file_get_contents($contentPath)
: '';
$index[] = [
'uuid' => $article['uuid'],
'uuid' => $uuid,
'slug' => $article['slug'] ?? '',
'title' => $article['title'] ?? '',
'category' => $article['category'] ?? '',
'author' => $article['author'] ?? '',
'cover' => $article['cover'] ?? '',
'featured' => (bool)($article['featured'] ?? false),
'published' => $article['published'],
'published_at' => $article['published_at'] ?? '',
'created_at' => $article['created_at'] ?? '',
'updated_at' => $article['updated_at'] ?? '',
'tags' => $article['tags'] ?? [],
'plain' => $this->stripForIndex($article['content'] ?? ''),
'plain' => $this->stripForIndex($content),
];
}
file_put_contents(
@@ -1027,8 +1046,8 @@ class ArticleManager
if (!is_array($data) || empty($data)) {
return null;
}
// Rebuild automatique si le format est obsolète (champs cover/created_at absents)
if (!array_key_exists('cover', $data[0])) {
// Rebuild automatique si le format est obsolète (champs manquants)
if (!array_key_exists('cover', $data[0]) || !array_key_exists('featured', $data[0])) {
$this->rebuildSearchIndex();
return $this->searchIndexCache;
}
@@ -1200,16 +1219,16 @@ class ArticleManager
};
}
private function loadArticle(string $dir): ?array
private function loadArticle(string $dir, bool $withContent = true): ?array
{
$metaPath = $dir . '/meta.json';
if (!file_exists($metaPath)) {
return null;
}
if ($withContent) {
$uuid = basename($dir);
$cachePath = $this->articleCachePath($uuid);
// Utiliser le cache si plus récent que meta.json ET index.md
$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);
@@ -1217,6 +1236,7 @@ class ArticleManager
return $cached;
}
}
}
$raw = file_get_contents($metaPath);
if ($raw === false) {
@@ -1227,8 +1247,11 @@ class ArticleManager
return null;
}
if ($withContent) {
$contentPath = $dir . '/index.md';
$meta['content'] = file_exists($contentPath) ? (string)file_get_contents($contentPath) : '';
}
$meta['published'] = (bool)($meta['published'] ?? false);
$meta['featured'] = (bool)($meta['featured'] ?? false);
$meta['files_meta'] = $meta['files_meta'] ?? [];
@@ -1242,12 +1265,15 @@ class ArticleManager
}
}
// Écrire le cache
if ($withContent) {
$uuid = $meta['uuid'];
$cachePath = $this->articleCachePath($uuid);
$cacheDir = dirname($cachePath);
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
file_put_contents($cachePath, json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
return $meta;
}
+1 -4
View File
@@ -1,6 +1,4 @@
<?php
require_once BASE_PATH . '/src/Parsedown.php';
$Parsedown = new Parsedown();
$_apName = $authorRow['display_name'] ?? '';
$_apSlug = $authorRow['profile_slug'] ?? '';
@@ -19,8 +17,7 @@ ob_start();
<?php else: ?>
<div class="post-grid">
<?php foreach ($posts as $post):
$html = $Parsedown->text($post['content']);
$preview = mb_strimwidth(strip_tags($html), 0, 120, '…');
$preview = mb_strimwidth($post['plain'] ?? '', 0, 120, '…');
$category = trim((string)($post['category'] ?? ''));
$gradient = coverGradient($category !== '' ? $category : $post['uuid'], $allCats ?? []);
$postUrl = '/post/' . rawurlencode($post['slug']);
+1 -4
View File
@@ -1,6 +1,4 @@
<?php
require_once BASE_PATH . '/src/Parsedown.php';
$Parsedown = new Parsedown();
ob_start();
@@ -36,8 +34,7 @@ $_initials = mb_strtoupper(mb_substr($_apName, 0, 1, 'UTF-8'), 'UTF-8');
<?php else: ?>
<div class="post-grid">
<?php foreach (array_slice($authorArticles, 0, 6) as $post):
$html = $Parsedown->text($post['content']);
$preview = mb_strimwidth(strip_tags($html), 0, 120, '…');
$preview = mb_strimwidth($post['plain'] ?? '', 0, 120, '…');
$category = trim((string)($post['category'] ?? ''));
$gradient = coverGradient($category !== '' ? $category : $post['uuid'], $allCats ?? []);
$postUrl = '/post/' . rawurlencode($post['slug']);
+6
View File
@@ -17,8 +17,14 @@ function _cardCoverStyle(array $post, array $allCats): string
function _cardExcerpt(array $post, \Parsedown $pd, int $len = 120): string
{
if (($post['plain'] ?? '') !== '') {
return mb_strimwidth($post['plain'], 0, $len, '…');
}
if (($post['content'] ?? '') !== '') {
return mb_strimwidth(strip_tags($pd->text($post['content'])), 0, $len, '…');
}
return '';
}
function _renderCard(array $post, array $privateCats, array $allCats, \Parsedown $pd): void
{