5e88d44129
- Widget retiré du hero-search, replacé en position:fixed top-right (sous navbar) - max-width !important pour garantir l'override de Bootstrap sur main.container-fluid - transition douce sur main, caché en < 576px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
401 lines
17 KiB
PHP
401 lines
17 KiB
PHP
<?php
|
||
require_once BASE_PATH . '/src/Parsedown.php';
|
||
$Parsedown = new Parsedown();
|
||
|
||
ob_start();
|
||
|
||
// ─── Helpers locaux ────────────────────────────────────────────────────────
|
||
function _cardCoverStyle(array $post, array $allCats): string
|
||
{
|
||
$coverFile = $post['cover'] ?? '';
|
||
if ($coverFile !== '') {
|
||
return 'background-image: url(\'/file?uuid=' . rawurlencode($post['uuid']) . '&name=' . rawurlencode($coverFile) . '\')';
|
||
}
|
||
$cat = trim($post['category'] ?? '');
|
||
return 'background: ' . coverGradient($cat !== '' ? $cat : $post['uuid'], $allCats);
|
||
}
|
||
|
||
function _cardExcerpt(array $post, \Parsedown $pd, int $len = 120): string
|
||
{
|
||
return mb_strimwidth(strip_tags($pd->text($post['content'])), 0, $len, '…');
|
||
}
|
||
|
||
function _renderCard(array $post, array $privateCats, array $allCats, \Parsedown $pd): void
|
||
{
|
||
$postUrl = '/post/' . rawurlencode($post['slug']);
|
||
$isDraft = !$post['published'];
|
||
$isAvantPremiere = $post['published'] && strtotime((string)($post['published_at'] ?? '')) > time();
|
||
$postCat = trim($post['category'] ?? '');
|
||
$isPrivate = $postCat !== '' && in_array($postCat, $privateCats, true);
|
||
$isLocked = $isAvantPremiere && !hasCapability('view_previews');
|
||
$coverStyle = _cardCoverStyle($post, $allCats);
|
||
$preview = _cardExcerpt($post, $pd);
|
||
?>
|
||
<article class="card">
|
||
<?php if ($isDraft): ?>
|
||
<div class="draft-ribbon">Brouillon</div>
|
||
<?php elseif ($isAvantPremiere): ?>
|
||
<div class="premiere-ribbon">Avant-première</div>
|
||
<?php elseif ($isPrivate): ?>
|
||
<div class="private-ribbon">Privé</div>
|
||
<?php endif; ?>
|
||
<div class="card-cover" style="<?= $coverStyle ?>">
|
||
<?php if ($postCat !== ''): ?>
|
||
<span class="cover-category"><?= htmlspecialchars($postCat) ?></span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="card-body d-flex flex-column">
|
||
<h2 class="card-title">
|
||
<?php if ($isLocked): ?>
|
||
<?= htmlspecialchars($post['title']) ?>
|
||
<?php else: ?>
|
||
<a href="<?= htmlspecialchars($postUrl) ?>"><?= htmlspecialchars($post['title']) ?></a>
|
||
<?php endif; ?>
|
||
</h2>
|
||
<p class="card-text flex-grow-1"><?= htmlspecialchars($preview) ?></p>
|
||
<div class="post-entry-meta mt-auto">
|
||
<?php if ($isAvantPremiere): ?>
|
||
<span class="text-muted">Disponible le <?= htmlspecialchars(date('d/m/Y \à H\hi', strtotime((string)($post['published_at'] ?? '')))) ?></span>
|
||
<?php else: ?>
|
||
<span><?= htmlspecialchars(date('d/m/Y', strtotime((string)($post['published_at'] ?? $post['created_at'] ?? '')))) ?></span>
|
||
<?php endif; ?>
|
||
<?php if (!$isLocked): ?>
|
||
<a href="<?= htmlspecialchars($postUrl) ?>" class="post-entry-read">→ lire</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php if (!$isLocked): ?>
|
||
<a href="<?= htmlspecialchars($postUrl) ?>" class="stretched-link"></a>
|
||
<?php endif; ?>
|
||
</article>
|
||
<?php
|
||
}
|
||
?>
|
||
|
||
<?php if ($isHomepage ?? false): ?>
|
||
|
||
<?php /* ─── PAGE D'ACCUEIL ─────────────────────────────────────────────── */ ?>
|
||
|
||
<div class="hero-search">
|
||
<form class="hero-search-form" action="/search" method="GET" role="search">
|
||
<input class="hero-search-input"
|
||
type="search" name="q"
|
||
value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"
|
||
placeholder="Rechercher un article…"
|
||
aria-label="Rechercher un article"
|
||
autofocus>
|
||
<button type="submit" class="hero-search-btn">Rechercher</button>
|
||
</form>
|
||
<p class="hero-search-stats">
|
||
<?= $totalPublished ?> article<?= $totalPublished > 1 ? 's' : '' ?>
|
||
<?php if ($totalUpcoming > 0): ?>· <?= $totalUpcoming ?> à venir<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
|
||
<?php /* ─── Héro + derniers articles ─────────────────────────────────── */ ?>
|
||
<?php if ($heroPost): ?>
|
||
<section class="home-section home-section--first">
|
||
<h2 class="home-section-title">
|
||
Derniers articles
|
||
<?php if (!empty($heroPost['featured'] ?? false)): ?>
|
||
<span class="home-section-badge">À la une</span>
|
||
<?php endif; ?>
|
||
</h2>
|
||
|
||
<?php
|
||
$heroUrl = '/post/' . rawurlencode($heroPost['slug']);
|
||
$heroCat = trim($heroPost['category'] ?? '');
|
||
$heroCover = $heroPost['cover'] ?? '';
|
||
$heroCoverStyle = $heroCover !== ''
|
||
? 'background-image: url(\'/file?uuid=' . rawurlencode($heroPost['uuid']) . '&name=' . rawurlencode($heroCover) . '\')'
|
||
: 'background: ' . coverGradient($heroCat !== '' ? $heroCat : $heroPost['uuid'], $allCats ?? []);
|
||
$heroExcerpt = _cardExcerpt($heroPost, $Parsedown, 200);
|
||
$heroDate = date('d/m/Y', strtotime((string)($heroPost['published_at'] ?? $heroPost['created_at'] ?? '')));
|
||
$heroLocked = $heroPost['published'] && strtotime((string)($heroPost['published_at'] ?? '')) > time() && !hasCapability('view_previews');
|
||
?>
|
||
<a href="<?= $heroLocked ? '#' : htmlspecialchars($heroUrl) ?>" class="home-hero-card">
|
||
<div class="home-hero-card-cover" style="<?= $heroCoverStyle ?>">
|
||
<?php if (!empty($heroPost['featured'] ?? false)): ?>
|
||
<span class="home-hero-badge">À la une</span>
|
||
<?php endif; ?>
|
||
<div class="home-hero-card-meta">
|
||
<?php if ($heroCat !== ''): ?>
|
||
<div class="home-hero-card-cat"><?= htmlspecialchars($heroCat) ?></div>
|
||
<?php endif; ?>
|
||
<h3 class="home-hero-card-title"><?= htmlspecialchars($heroPost['title']) ?></h3>
|
||
<p class="home-hero-card-excerpt"><?= htmlspecialchars($heroExcerpt) ?></p>
|
||
<div class="home-hero-card-date"><?= htmlspecialchars($heroDate) ?> → lire</div>
|
||
</div>
|
||
</div>
|
||
</a>
|
||
|
||
<?php if (!empty($latestPosts)): ?>
|
||
<div class="post-grid">
|
||
<?php foreach ($latestPosts as $_lp): ?>
|
||
<?php _renderCard($_lp, $privateCats ?? [], $allCats ?? [], $Parsedown); ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
$moreCursor = $allPosts[5]['uuid'] ?? null;
|
||
if ($moreCursor === null && !empty($latestPosts)) {
|
||
$moreCursor = end($latestPosts)['uuid'] ?? null;
|
||
}
|
||
?>
|
||
<?php if ($moreCursor && count($allPosts) > 6): ?>
|
||
<div class="home-more-link">
|
||
<a href="/page/<?= rawurlencode($moreCursor) ?>" class="btn btn-outline-secondary btn-sm">
|
||
Voir plus d'articles →
|
||
</a>
|
||
</div>
|
||
<?php endif; ?>
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<?php /* ─── Meilleures audiences (AJAX — flux RSS XML /trending?period=1h) ── */ ?>
|
||
<section class="home-section" id="home-audiences-section" hidden>
|
||
<h2 class="home-section-title">
|
||
Meilleures audiences <span class="home-section-title-sub">· 1 heure</span>
|
||
</h2>
|
||
<div class="post-grid" id="home-audiences-grid"></div>
|
||
</section>
|
||
<script>
|
||
(function(){
|
||
var _g=[
|
||
'linear-gradient(135deg,#667eea 0%,#764ba2 100%)',
|
||
'linear-gradient(135deg,#f093fb 0%,#f5576c 100%)',
|
||
'linear-gradient(135deg,#4facfe 0%,#00f2fe 100%)',
|
||
'linear-gradient(135deg,#43e97b 0%,#38f9d7 100%)',
|
||
'linear-gradient(135deg,#fa709a 0%,#fee140 100%)',
|
||
'linear-gradient(135deg,#a18cd1 0%,#fbc2eb 100%)'
|
||
];
|
||
function _e(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||
fetch('/trending?period=1h')
|
||
.then(function(r){return r.ok?r.text():Promise.reject();})
|
||
.then(function(xml){
|
||
var doc=new DOMParser().parseFromString(xml,'application/xml');
|
||
var items=Array.from(doc.querySelectorAll('item')).slice(0,6);
|
||
if(!items.length)return;
|
||
var grid=document.getElementById('home-audiences-grid');
|
||
if(!grid)return;
|
||
grid.innerHTML=items.map(function(item,i){
|
||
var raw=(item.querySelector('title')||{textContent:''}).textContent;
|
||
var title=raw.replace(/\s*\(\d+\s+visiteurs?\)$/,'');
|
||
var link=((item.querySelector('link')||{}).textContent||'#').trim();
|
||
var pd=(item.querySelector('pubDate')||{textContent:''}).textContent;
|
||
var date='';
|
||
try{if(pd)date=new Date(pd).toLocaleDateString('fr-FR');}catch(e){}
|
||
var grad=_g[i%_g.length];
|
||
return '<article class="card">'
|
||
+'<div class="card-cover" style="background:'+grad+'"></div>'
|
||
+'<div class="card-body d-flex flex-column">'
|
||
+'<h2 class="card-title"><a href="'+_e(link)+'">'+_e(title)+'</a></h2>'
|
||
+'<div class="post-entry-meta mt-auto">'
|
||
+(date?'<span>'+_e(date)+'</span>':'')
|
||
+'<a href="'+_e(link)+'" class="post-entry-read">→ lire</a>'
|
||
+'</div></div>'
|
||
+'<a href="'+_e(link)+'" class="stretched-link"></a>'
|
||
+'</article>';
|
||
}).join('');
|
||
var s=document.getElementById('home-audiences-section');
|
||
if(s)s.hidden=false;
|
||
})
|
||
.catch(function(){});
|
||
})();
|
||
</script>
|
||
|
||
<?php /* ─── Récemment mis à jour ──────────────────────────────────────── */ ?>
|
||
<?php if (!empty($recentlyUpdated)): ?>
|
||
<section class="home-section">
|
||
<h2 class="home-section-title">Récemment mis à jour</h2>
|
||
<div class="home-compact-list">
|
||
<?php foreach ($recentlyUpdated as $_ru):
|
||
$_ruUrl = '/post/' . rawurlencode($_ru['slug']);
|
||
$_ruCat = trim($_ru['category'] ?? '');
|
||
$_ruThumb = _cardCoverStyle($_ru, $allCats ?? []);
|
||
$_ruDate = date('d/m/Y', strtotime((string)($_ru['updated_at'] ?? $_ru['published_at'] ?? '')));
|
||
?>
|
||
<a href="<?= htmlspecialchars($_ruUrl) ?>" class="home-compact-item">
|
||
<div class="home-compact-thumb" style="<?= $_ruThumb ?>"></div>
|
||
<div class="home-compact-meta">
|
||
<div class="home-compact-title"><?= htmlspecialchars($_ru['title']) ?></div>
|
||
<div class="home-compact-date">mis à jour le <?= htmlspecialchars($_ruDate) ?></div>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<?php /* ─── Redécouvertes ──────────────────────────────────────────────── */ ?>
|
||
<?php if (!empty($redecouvertes)): ?>
|
||
<section class="home-section">
|
||
<h2 class="home-section-title">Redécouvertes</h2>
|
||
<div class="home-compact-list">
|
||
<?php foreach ($redecouvertes as $_rd):
|
||
$_rdUrl = '/post/' . rawurlencode($_rd['slug']);
|
||
$_rdCat = trim($_rd['category'] ?? '');
|
||
$_rdThumb = _cardCoverStyle($_rd, $allCats ?? []);
|
||
$_rdDate = date('d/m/Y', strtotime((string)($_rd['published_at'] ?? $_rd['created_at'] ?? '')));
|
||
?>
|
||
<a href="<?= htmlspecialchars($_rdUrl) ?>" class="home-compact-item">
|
||
<div class="home-compact-thumb" style="<?= $_rdThumb ?>"></div>
|
||
<div class="home-compact-meta">
|
||
<div class="home-compact-title"><?= htmlspecialchars($_rd['title']) ?></div>
|
||
<div class="home-compact-date"><?= htmlspecialchars($_rdDate) ?></div>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<?php else: /* ─── VUE PAGINÉE / FILTRÉE ─────────────────────────────── */ ?>
|
||
|
||
<?php if ($cursor === '' && $filterCat === ''): ?>
|
||
<div class="hero-search">
|
||
<form class="hero-search-form" action="/search" method="GET" role="search">
|
||
<input class="hero-search-input"
|
||
type="search" name="q"
|
||
value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"
|
||
placeholder="Rechercher un article…"
|
||
aria-label="Rechercher un article"
|
||
autofocus>
|
||
<button type="submit" class="hero-search-btn">Rechercher</button>
|
||
</form>
|
||
<p class="hero-search-stats">
|
||
<?= $totalPublished ?> article<?= $totalPublished > 1 ? 's' : '' ?>
|
||
<?php if ($totalUpcoming > 0): ?>· <?= $totalUpcoming ?> à venir<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="post-grid">
|
||
<?php foreach ($posts as $post): ?>
|
||
<?php _renderCard($post, $privateCats ?? [], $allCats ?? [], $Parsedown); ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<?php if ($prevCursor !== null || $nextCursor !== null): ?>
|
||
<nav class="pagination-nav mt-5" aria-label="Navigation">
|
||
<?php
|
||
$hasCat = $filterCat !== '';
|
||
$catBase = $hasCat ? '/categorie/' . rawurlencode($filterCat) : null;
|
||
?>
|
||
<?php if ($prevCursor !== null): ?>
|
||
<?php
|
||
if ($prevCursor === '') {
|
||
$prevHref = $hasCat ? $catBase : '/';
|
||
} elseif ($hasCat) {
|
||
$prevHref = $catBase . '?cursor=' . rawurlencode($prevCursor);
|
||
} else {
|
||
$prevHref = '/page/' . rawurlencode($prevCursor);
|
||
}
|
||
?>
|
||
<a class="pagination-btn" href="<?= htmlspecialchars($prevHref) ?>">← Plus récents</a>
|
||
<?php endif; ?>
|
||
<?php if ($nextCursor !== null): ?>
|
||
<?php $nextHref = $hasCat ? $catBase . '?cursor=' . rawurlencode($nextCursor) : '/page/' . rawurlencode($nextCursor); ?>
|
||
<a class="pagination-btn ms-auto" href="<?= htmlspecialchars($nextHref) ?>">Plus anciens →</a>
|
||
<?php endif; ?>
|
||
</nav>
|
||
<?php endif; ?>
|
||
|
||
<?php endif; /* fin homepage / paginated */ ?>
|
||
|
||
<?php
|
||
$_tagCats = array_filter(
|
||
$allCats ?? [],
|
||
static fn ($cat) => isLoggedIn() || !in_array($cat, $privateCats ?? [], true),
|
||
ARRAY_FILTER_USE_KEY
|
||
);
|
||
arsort($_tagCats);
|
||
if (!empty($_tagCats)):
|
||
$_minCount = min($_tagCats);
|
||
$_maxCount = max($_tagCats);
|
||
?>
|
||
<nav class="tag-cloud mt-5" aria-label="Catégories">
|
||
<?php foreach ($_tagCats as $_catName => $_catCount):
|
||
$_size = $_minCount === $_maxCount
|
||
? 1.1
|
||
: round(0.8 + ($_catCount - $_minCount) / ($_maxCount - $_minCount) * 0.9, 2);
|
||
$_active = ($filterCat === $_catName);
|
||
?>
|
||
<a href="/categorie/<?= rawurlencode($_catName) ?>"
|
||
class="tag-cloud-item<?= $_active ? ' active' : '' ?>">
|
||
<?= htmlspecialchars($_catName) ?>
|
||
<span class="tag-count"><?= $_catCount ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php if ($filterCat !== ''): ?>
|
||
<a href="/" class="tag-cloud-reset">× tout afficher</a>
|
||
<?php endif; ?>
|
||
</nav>
|
||
<?php endif; ?>
|
||
|
||
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
|
||
<a href="/new" class="fab-new" title="Nouvel article" aria-label="Nouvel article">+</a>
|
||
<?php endif; ?>
|
||
|
||
<div class="density-widget" id="density-toggle-widget" role="group" aria-label="Taille d'affichage">
|
||
<button type="button" class="density-btn" data-d="l" title="Pleine largeur">L</button>
|
||
<button type="button" class="density-btn" data-d="m" title="Normal">M</button>
|
||
<button type="button" class="density-btn" data-d="s" title="Compact">S</button>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
var KEY = 'folio_density';
|
||
var cur = localStorage.getItem(KEY) || 'l';
|
||
|
||
function apply(d) {
|
||
if (d === 'l') {
|
||
delete document.body.dataset.density;
|
||
} else {
|
||
document.body.dataset.density = d;
|
||
}
|
||
document.querySelectorAll('.density-btn').forEach(function(btn){
|
||
btn.classList.toggle('active', btn.dataset.d === d);
|
||
});
|
||
}
|
||
|
||
apply(cur);
|
||
|
||
document.addEventListener('click', function(e){
|
||
var btn = e.target.closest('.density-btn');
|
||
if (!btn) return;
|
||
cur = btn.dataset.d;
|
||
localStorage.setItem(KEY, cur);
|
||
apply(cur);
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<?php
|
||
$content = ob_get_clean();
|
||
$title = siteTitle();
|
||
|
||
if (!empty($cursor)) {
|
||
$metaRobots = 'noindex, follow';
|
||
$canonical = rtrim(APP_URL, '/') . '/';
|
||
} elseif ($filterCat !== '') {
|
||
$canonical = rtrim(APP_URL, '/') . '/categorie/' . rawurlencode($filterCat);
|
||
} else {
|
||
$canonical = rtrim(APP_URL, '/') . '/';
|
||
}
|
||
|
||
if (empty($cursor) && $filterCat === '') {
|
||
$jsonLd = json_encode([
|
||
'@context' => 'https://schema.org',
|
||
'@type' => 'WebSite',
|
||
'name' => siteTitle(),
|
||
'url' => rtrim(APP_URL, '/') . '/',
|
||
'description' => siteClaim(),
|
||
'inLanguage' => siteLang(),
|
||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||
}
|
||
|
||
$mainClass = 'container-fluid';
|
||
include __DIR__ . '/layout.php';
|