search: index plat search_index.json, reconstruit sur chaque write

This commit is contained in:
Cedric Abonnel
2026-05-12 01:40:22 +02:00
parent f236ea24de
commit 045e93cffd
9 changed files with 290 additions and 5 deletions
+57
View File
@@ -116,6 +116,7 @@ class ArticleManager
];
$this->writeMeta($dir, $meta);
file_put_contents($dir . '/index.md', ltrim($content));
$this->rebuildSearchIndex();
return $uuid;
}
@@ -174,6 +175,7 @@ class ArticleManager
$dir = $this->dataDir . '/' . $uuid;
$this->writeMeta($dir, $meta);
file_put_contents($dir . '/index.md', ltrim($content));
$this->rebuildSearchIndex();
}
public function autosave(string $uuid, string $title, string $content, string $slug): bool
@@ -543,6 +545,61 @@ class ArticleManager
if (is_dir($dir)) {
$this->removeDir($dir);
}
$this->rebuildSearchIndex();
}
// ------------------------------------------------------------------ //
// Index de recherche (fichier plat)
// ------------------------------------------------------------------ //
/**
* Reconstruit search_index.json à partir de tous les articles.
* Appelé automatiquement après chaque create/update/delete.
*/
public function rebuildSearchIndex(): void
{
$index = [];
foreach ($this->getAll() as $article) {
$index[] = [
'uuid' => $article['uuid'],
'slug' => $article['slug'] ?? '',
'title' => $article['title'] ?? '',
'category' => $article['category'] ?? '',
'published' => $article['published'],
'published_at' => $article['published_at'] ?? '',
'updated_at' => $article['updated_at'] ?? '',
'plain' => $this->stripForIndex($article['content'] ?? ''),
];
}
file_put_contents(
$this->dataDir . '/search_index.json',
json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
}
/** Retourne l'index pré-construit, ou null s'il n'existe pas encore. */
public function getSearchIndex(): ?array
{
$path = $this->dataDir . '/search_index.json';
if (!file_exists($path)) {
return null;
}
$data = json_decode((string) file_get_contents($path), true);
return is_array($data) ? $data : null;
}
/** Retire la syntaxe Markdown pour stocker du texte brut dans l'index. */
private function stripForIndex(string $md): string
{
$t = preg_replace('/!\[[^\]]*\]\([^)]+\)/', '', $md) ?? $md;
$t = preg_replace('/\[([^\]]+)\]\([^)]+\)/', '$1', $t) ?? $t;
$t = preg_replace('/```[\s\S]*?```/', '', $t) ?? $t;
$t = preg_replace('/`[^`]+`/', '', $t) ?? $t;
$t = preg_replace('/^#{1,6}\s*/m', '', $t) ?? $t;
$t = preg_replace('/[*_~]{1,3}([^*_~]+)[*_~]{1,3}/', '$1', $t) ?? $t;
$t = preg_replace('/^\s*[-*+|>]\s*/m', '', $t) ?? $t;
$t = preg_replace('/\n{2,}/', ' ', $t) ?? $t;
return trim($t);
}
// ------------------------------------------------------------------ //
+2 -1
View File
@@ -37,7 +37,8 @@ class SearchEngine
$results = [];
foreach ($articles as $article) {
$plain = $this->stripMarkdown($article['content'] ?? '');
// 'plain' est pré-calculé dans search_index.json, sinon on stripe à la volée
$plain = $article['plain'] ?? $this->stripMarkdown($article['content'] ?? '');
$tWords = $this->tokenize($article['title'] ?? '');
$cWords = $this->tokenize($article['category'] ?? '');
$pWords = $this->tokenize($plain);