diff --git a/templates/post_form.php b/templates/post_form.php
index 1e9fb12..c25c7e1 100644
--- a/templates/post_form.php
+++ b/templates/post_form.php
@@ -9,6 +9,7 @@ $dateValue = isset($published_at)
?>
+
+
+
IA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/post_list.php b/templates/post_list.php
index ab71c44..b6a47c4 100644
--- a/templates/post_list.php
+++ b/templates/post_list.php
@@ -17,7 +17,13 @@ function _cardCoverStyle(array $post, array $allCats): string
function _cardExcerpt(array $post, \Parsedown $pd, int $len = 120): string
{
- return mb_strimwidth(strip_tags($pd->text($post['content'])), 0, $len, '…');
+ 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
diff --git a/templates/post_view.php b/templates/post_view.php
index e47f181..dd9e6f6 100644
--- a/templates/post_view.php
+++ b/templates/post_view.php
@@ -9,32 +9,62 @@ $_accentMap = [
];
$_tocItems = [];
$_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(
- '/<(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)
-);
+
+// 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();
@@ -95,6 +125,14 @@ $authorName = ($authorEmail !== '' && function_exists('authorDisplayName')
$authorProfileUrl = ($authorEmail !== '' && function_exists('authorProfileUrl')) ? authorProfileUrl($authorEmail) : '';
$authorSlugVal = ($authorEmail !== '' && function_exists('authorSlug')) ? authorSlug($authorEmail) : '';
$pubDate = htmlspecialchars(date('d/m/Y', strtotime((string)($article['published_at'] ?? $article['created_at'] ?? ''))));
+$modDate = '';
+$_updatedTs = strtotime((string)($article['updated_at'] ?? ''));
+$_publishedTs = strtotime((string)($article['published_at'] ?? $article['created_at'] ?? ''));
+if ($_updatedTs > 0 && $_publishedTs > 0 && $_updatedTs > $_publishedTs) {
+ $_frMonths = ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'];
+ $modDate = 'Modifié le ' . (int)date('j', $_updatedTs) . ' ' . $_frMonths[(int)date('n', $_updatedTs) - 1]
+ . ' ' . date('Y', $_updatedTs) . ' à ' . date('H', $_updatedTs) . 'h' . date('i', $_updatedTs);
+}
$hasCover = $coverFile !== '';
$heroExtraClass = $hasCover ? '' : ' article-cover--gradient';
$heroStyle = $hasCover ? '' : ' style="background:' . htmlspecialchars($gradient) . '"';
@@ -136,6 +174,9 @@ $hasSources = (!empty($externalLinks) || !empty($files))
·
= $pubDate ?>
+
+
= htmlspecialchars($modDate) ?>
+
@@ -220,6 +261,59 @@ $hasSources = (!empty($externalLinks) || !empty($files))