'a','â' => 'a','ä' => 'a','á' => 'a','é' => 'e','è' => 'e','ê' => 'e','ë' => 'e',
'î' => 'i','ï' => 'i','í' => 'i','ô' => 'o','ö' => 'o','ó' => 'o','ù' => 'u','û' => 'u',
'ü' => 'u','ú' => 'u','ç' => 'c','ñ' => 'n','æ' => 'ae','œ' => 'oe',
];
$_tocItems = [];
$_tocSeen = [];
// Cache du rendu Markdown (invalidé si index.md est plus récent)
$_mdFile = defined('DATA_PATH') ? DATA_PATH . '/' . ($article['uuid'] ?? '') . '/index.md' : '';
$_cacheFile = defined('DATA_PATH') ? DATA_PATH . '/' . ($article['uuid'] ?? '') . '/_cache/content_rendered.json' : '';
$_mdMtime = ($_mdFile !== '' && file_exists($_mdFile)) ? (int)filemtime($_mdFile) : 0;
$_renderedContent = null;
if ($_cacheFile !== '' && file_exists($_cacheFile)) {
$_tmp = json_decode((string)file_get_contents($_cacheFile), true);
if (is_array($_tmp) && isset($_tmp['ts'], $_tmp['html'], $_tmp['toc'])
&& (int)$_tmp['ts'] >= $_mdMtime && $_mdMtime > 0) {
$_renderedContent = $_tmp['html'];
$_tocItems = $_tmp['toc'];
}
}
if ($_renderedContent === null) {
// 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(
'/<(h[23])>(.+?)<\/h[23]>/i',
function ($m) use (&$_tocItems, &$_tocSeen, $_accentMap) {
$tag = $m[1];
$inner = $m[2];
$level = (int) substr($tag, 1);
$plain = strip_tags($inner);
$slug = trim(preg_replace(
'/[^a-z0-9]+/',
'-',
mb_strtolower(strtr($plain, $_accentMap), 'UTF-8')
), '-') ?: 'section';
if (isset($_tocSeen[$slug])) {
$_tocSeen[$slug]++;
$id = $slug . '-' . $_tocSeen[$slug];
} else {
$_tocSeen[$slug] = 0;
$id = $slug;
}
$_tocItems[] = ['level' => $level, 'text' => $plain, 'id' => $id];
return "<{$tag} id=\"" . htmlspecialchars($id) . "\">{$inner}{$tag}>";
},
$Parsedown->text($_rawForRender)
);
$_renderedContent = typographieHtml($_renderedContent ?? '');
// Lazy loading sur toutes les images du contenu
$_renderedContent = preg_replace('/]*)>/i', '
', $_renderedContent ?? '') ?? $_renderedContent;
// Écriture du cache
if ($_cacheFile !== '' && $_mdMtime > 0) {
@mkdir(dirname($_cacheFile), 0755, true);
@file_put_contents($_cacheFile, json_encode(
['ts' => $_mdMtime, 'html' => $_renderedContent, 'toc' => $_tocItems],
JSON_UNESCAPED_UNICODE
));
}
}
ob_start();
$coverFile = $article['cover'] ?? '';
$ogImage = $coverFile !== ''
? url('file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($coverFile))
: null;
$category = trim((string)($article['category'] ?? ''));
$gradient = coverGradient($category !== '' ? $category : $article['uuid'], $allCats ?? []);
// Pièces jointes (hors fichiers intégrés, thumbs et cover)
$attachments = [];
if ($files) {
$referenced = [];
preg_match_all('/\(\/file\?uuid=[^&]+&name=([^)]+)\)/', $rawContent, $m);
foreach ($m[1] as $encodedName) {
$referenced[rawurldecode($encodedName)] = true;
}
$attachments = array_values(array_filter(
$files,
static fn ($f) =>
!isset($referenced[$f['name']])
&& !str_starts_with($f['name'], '_thumb_')
&& $f['name'] !== $coverFile
));
}
$externalLinks = $article['external_links'] ?? [];
?>