pagination curseur, layout 3 colonnes article, sidebar fixe

This commit is contained in:
Cedric Abonnel
2026-05-12 00:42:51 +02:00
parent d774042be9
commit be09fad48f
91 changed files with 8152 additions and 816 deletions
+231 -65
View File
@@ -3,89 +3,254 @@ require_once __DIR__ . '/../src/Parsedown.php';
$Parsedown = new Parsedown();
ob_start();
?>
<a href="/" class="btn btn-secondary mb-3">← Retour</a>
<?php
$coverFile = $article['cover'] ?? '';
$ogImage = $coverFile !== ''
? url('file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($coverFile))
: null;
?>
<div class="card mb-4">
<?php if (!$article['published']): ?>
<div class="draft-ribbon">Brouillon</div>
<?php endif; ?>
<?php if ($coverFile !== ''): ?>
<div class="article-cover">
<img src="/file?uuid=<?= rawurlencode($article['uuid']) ?>&name=<?= rawurlencode($coverFile) ?>"
alt="<?= htmlspecialchars($article['title']) ?>">
</div>
<?php endif; ?>
<div class="card-body">
<h2 class="card-title"><?= htmlspecialchars($article['title']) ?></h2>
<div class="card-text post-content">
<?= $Parsedown->text($rawContent) ?>
</div>
$category = trim((string)($article['category'] ?? ''));
$gradient = coverGradient($category !== '' ? $category : $article['uuid'], $allCats ?? []);
<p class="text-muted small mt-2">
Publié le <?= htmlspecialchars(date('d/m/Y H:i', strtotime((string)($article['published_at'] ?? $article['created_at'] ?? '')))) ?>
</p>
</div>
</div>
<?php if ($files): ?>
<?php
// Sépare les fichiers intégrés (référencés dans le contenu) des pièces jointes
// 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_filter($files, static fn ($f) => !isset($referenced[$f['name']]));
?>
<?php if ($attachments): ?>
<section class="mb-4">
<h5>Pièces jointes</h5>
<div class="row g-3">
<?php foreach ($attachments as $file): ?>
<div class="col-sm-6 col-md-4">
<div class="card">
<?php
$fileUrl = '/file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($file['name']);
?>
<?php if ($file['is_image']): ?>
<img src="<?= htmlspecialchars($fileUrl) ?>" class="card-img-top" alt="<?= htmlspecialchars($file['name']) ?>" style="max-height:200px;object-fit:cover">
<?php elseif ($file['is_video']): ?>
<video controls class="w-100" style="max-height:200px"><source src="<?= htmlspecialchars($fileUrl) ?>"></video>
<?php elseif ($file['is_audio']): ?>
<audio controls class="w-100"><source src="<?= htmlspecialchars($fileUrl) ?>"></audio>
<?php endif; ?>
<div class="card-body p-2">
<a href="<?= htmlspecialchars($fileUrl) ?>" class="card-title small d-block text-truncate" target="_blank">
<?= htmlspecialchars($file['name']) ?>
</a>
<small class="text-muted"><?= htmlspecialchars(number_format($file['size'] / 1024, 1)) ?> Ko</small>
</div>
</div>
</div>
$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'] ?? [];
$hasLeftSidebar = !empty($categorySidebar ?? []);
?>
<div class="row g-4 align-items-start flex-nowrap-lg">
<?php if ($hasLeftSidebar): ?>
<div class="post-sidebar-col order-2 order-lg-1">
<aside class="left-sidebar">
<?php foreach ($categorySidebar as $catName => $catArticles): ?>
<div class="left-sidebar-section">
<a href="/?cat=<?= rawurlencode($catName) ?>" class="left-sidebar-cat">
<?= htmlspecialchars($catName) ?>
</a>
<ul class="left-sidebar-list">
<?php foreach ($catArticles as $ca): ?>
<li>
<a href="/post/<?= rawurlencode($ca['slug'] ?? '') ?>">
<?= htmlspecialchars($ca['title']) ?>
</a>
</li>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</ul>
</div>
<?php endforeach; ?>
</aside>
</div>
<?php endif; ?>
<?php if (function_exists('isAdmin') && isAdmin()): ?>
<div class="d-flex gap-2 mt-3">
<a href="/?action=edit&uuid=<?= htmlspecialchars($article['uuid']) ?>" class="btn btn-primary">Modifier</a>
<a href="/?action=delete&uuid=<?= htmlspecialchars($article['uuid']) ?>"
class="btn btn-danger"
onclick="return confirm('Supprimer cet article définitivement ?')">Supprimer</a>
<!-- Colonne principale -->
<div class="col order-1 order-lg-2">
<div class="card mb-4">
<?php if (!$article['published']): ?>
<div class="draft-ribbon">Brouillon</div>
<?php elseif ($isPrivateCat ?? false): ?>
<div class="private-ribbon">Privé</div>
<?php endif; ?>
<?php
$authorEmail = $article['author'] ?? '';
$authorName = ($authorEmail !== '' && function_exists('authorDisplayName')) ? authorDisplayName($authorEmail) : '';
$pubDate = htmlspecialchars(date('d/m/Y', strtotime((string)($article['published_at'] ?? $article['created_at'] ?? ''))));
$hasCover = $coverFile !== '';
$heroExtraClass = $hasCover ? '' : ' article-cover--gradient';
$heroStyle = $hasCover ? '' : ' style="background:' . htmlspecialchars($gradient) . '"';
$hasSources = (!empty($externalLinks) || !empty($files))
&& function_exists('canDoOnArticle') && canDoOnArticle('view_sources', $article);
?>
<div class="article-cover article-cover--hero<?= $heroExtraClass ?>"<?= $heroStyle ?>>
<?php if ($hasCover): ?>
<img src="/file?uuid=<?= rawurlencode($article['uuid']) ?>&name=<?= rawurlencode($coverFile) ?>"
alt="<?= htmlspecialchars($article['title']) ?>">
<?php endif; ?>
<div class="article-hero-text">
<!-- Haut : retour + actions admin -->
<div class="article-hero-top">
<a href="/" class="hero-btn">← Retour</a>
<?php if (function_exists('isAdmin') && isAdmin()): ?>
<a href="/?action=edit&uuid=<?= rawurlencode($article['uuid']) ?>" class="hero-btn ms-auto">✎ Modifier</a>
<a href="/?action=delete&uuid=<?= rawurlencode($article['uuid']) ?>"
class="hero-btn hero-btn--danger"
data-confirm="Supprimer cet article définitivement ?">🗑 Supprimer</a>
<?php endif; ?>
</div>
<!-- Bas : titre + actions secondaires -->
<div class="article-hero-bottom">
<div class="article-hero-left">
<?php if ($category !== ''): ?>
<span class="cover-category"><?= htmlspecialchars($category) ?></span>
<?php endif; ?>
<h1 class="article-title"><?= htmlspecialchars($article['title']) ?></h1>
<p class="article-hero-meta">
<?php if ($authorName !== ''): ?>
<span><?= htmlspecialchars($authorName) ?></span>
<span class="mx-1 opacity-50">·</span>
<?php endif; ?>
<?= $pubDate ?>
</p>
</div>
<div class="article-hero-right">
<?php if ($hasSources): ?>
<a href="/?action=sources&uuid=<?= rawurlencode($article['uuid']) ?>" class="hero-btn"> Sources</a>
<?php endif; ?>
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
<form method="post" action="/?action=rate" class="d-flex align-items-center gap-2">
<input type="hidden" name="uuid" value="<?= htmlspecialchars($article['uuid']) ?>">
<?php if ($ratingStats['count'] > 0): ?>
<span class="hero-rating-score">
<?= number_format((float)($ratingStats['avg'] ?? 0), 1) ?> <span style="opacity:.6">/ 5</span>
</span>
<?php endif; ?>
<div class="star-rating star-rating--hero">
<?php for ($s = 5; $s >= 1; $s--): ?>
<input type="radio" id="star<?= $s ?>-<?= $article['uuid'] ?>"
name="rating" value="<?= $s ?>"
<?= (int)($userRating ?? 0) === $s ? 'checked' : '' ?>
onchange="this.form.submit()">
<label for="star<?= $s ?>-<?= $article['uuid'] ?>" title="<?= $s ?>★">★</label>
<?php endfor; ?>
</div>
</form>
<?php elseif ($ratingStats['count'] > 0): ?>
<span class="hero-rating-score">
★ <?= number_format((float)($ratingStats['avg'] ?? 0), 1) ?>
<span style="opacity:.6">(<?= $ratingStats['count'] ?>)</span>
</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="card-text post-content">
<?= $Parsedown->text($rawContent) ?>
</div>
</div>
</div>
<?php if (!isLoggedIn() && $ratingStats['count'] > 0): ?>
<p class="text-muted small mt-3">
Note : <?= number_format((float)($ratingStats['avg'] ?? 0), 1) ?>/5
— <a href="/login">Connectez-vous</a> pour noter.
</p>
<?php endif; ?>
</div><!-- /col principale -->
<div class="post-sidebar-col order-3">
<aside class="related-sidebar">
<?php if (!empty($attachments)): ?>
<h6 class="related-sidebar-title">Pièces jointes</h6>
<div class="d-flex flex-column gap-2 mb-4">
<?php foreach ($attachments as $file):
$fileUrl = '/file?uuid=' . rawurlencode($article['uuid']) . '&name=' . rawurlencode($file['name']);
?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="source-card">
<?php if ($file['is_image']): ?>
<div class="source-card-thumb" style="background-image:url(<?= htmlspecialchars($fileUrl) ?>);background-size:cover;background-position:center"></div>
<?php elseif ($file['is_video']): ?>
<div class="source-card-thumb source-card-thumb--link">▶</div>
<?php elseif ($file['is_audio']): ?>
<div class="source-card-thumb source-card-thumb--link">♪</div>
<?php else: ?>
<div class="source-card-thumb source-card-thumb--pdf">📎</div>
<?php endif; ?>
<div class="source-card-body">
<div class="source-card-title"><?= htmlspecialchars($file['name']) ?></div>
<div class="source-card-meta"><?= htmlspecialchars(number_format($file['size'] / 1024, 1)) ?> Ko</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($externalLinks)): ?>
<h6 class="related-sidebar-title">Liens &amp; sources</h6>
<div class="d-flex flex-column gap-2 mb-4">
<?php foreach ($externalLinks as $lnk):
$lMeta = $lnk['meta'] ?? [];
$lTitle = $lnk['name'] ?? '';
$lUrl = $lnk['url'] ?? '';
$lHost = parse_url($lUrl, PHP_URL_HOST) ?? $lUrl;
$lDate = $lMeta['date'] ?? '';
$lSite = $lMeta['site_name'] ?? $lHost;
$lImage = $lMeta['og_image'] ?? '';
$lMime = $lMeta['mime'] ?? 'text/html';
$lPages = $lMeta['pages'] ?? null;
$lFormat = $lMeta['page_size'] ?? '';
$isPdf = ($lMime === 'application/pdf');
?>
<a href="<?= htmlspecialchars($lUrl) ?>" target="_blank" rel="noopener" class="source-card">
<?php if ($lImage && str_starts_with($lImage, '/')): ?>
<div class="source-card-thumb" style="background-image:url(<?= htmlspecialchars($lImage) ?>);background-size:cover;background-position:center"></div>
<?php elseif ($isPdf): ?>
<div class="source-card-thumb source-card-thumb--pdf">📑</div>
<?php else: ?>
<div class="source-card-thumb source-card-thumb--link">↗</div>
<?php endif; ?>
<div class="source-card-body">
<div class="source-card-title"><?= htmlspecialchars($lTitle) ?></div>
<div class="source-card-meta">
<?= htmlspecialchars($lSite) ?>
<?php if ($lDate): ?> · <?= htmlspecialchars(substr($lDate, 0, 10)) ?><?php endif; ?>
<?php if ($isPdf && $lPages): ?> · PDF <?= $lPages ?>p.<?php endif; ?>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<h6 class="related-sidebar-title">Dans la même catégorie</h6>
<?php if (!empty($relatedArticles ?? [])): ?>
<?php foreach ($relatedArticles as $rel):
$relCover = $rel['cover'] ?? '';
$relCat = trim($rel['category'] ?? '');
$relGradient = coverGradient($relCat !== '' ? $relCat : $rel['uuid'], $allCats ?? []);
$relDate = date('d/m/Y', strtotime((string)($rel['published_at'] ?? $rel['created_at'] ?? '')));
?>
<a href="/post/<?= rawurlencode($rel['slug'] ?? '') ?>" class="related-card">
<div class="related-card-thumb" style="<?= $relCover !== ''
? 'background-image:url(/file?uuid=' . rawurlencode($rel['uuid']) . '&name=' . rawurlencode($relCover) . ');background-size:cover;background-position:center'
: 'background:' . htmlspecialchars($relGradient) ?>">
</div>
<div class="related-card-body">
<div class="related-card-title"><?= htmlspecialchars($rel['title']) ?></div>
<div class="related-card-date"><?= $relDate ?></div>
</div>
</a>
<?php endforeach; ?>
<?php else: ?>
<p class="text-muted small">Aucun autre article dans cette catégorie.</p>
<?php endif; ?>
</aside>
</div>
</div><!-- /row -->
<?php
$content = ob_get_clean();
$title = htmlspecialchars($article['title']);
@@ -95,4 +260,5 @@ $ogImage = $article['og_image'] ?? '';
$ogType = 'article';
$ogUrl = url('post/' . rawurlencode($article['slug'] ?? ''));
$articlePublishedAt = $article['published_at'] ?? '';
$mainClass = 'container-fluid';
include __DIR__ . '/layout.php';