Stream the page shell first so a loader shows while feeds fetch
Opening the page used to block on refreshStale() before a single byte went out, so the browser sat on a blank tab for the whole fetch. index.php now renders templates/shell.php (head, header, filters, skeleton loader) and flushes it, then refreshes and appends templates/content.php, which removes the loader as the feed HTML arrives. - templates/layout.php is split into shell.php + content.php - refresh chip moved to a right-aligned row above the feed: the toolbar paints before the refresh report exists - header story count shows the cached total, then corrects to the real one - skeleton styles match the card grid and keep the sources' chroma; a noscript style hides the loader for clients without JS - README: file map and page-load lifecycle updated
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\View;
|
||||
|
||||
/** @var string|null $activeSource */
|
||||
/** @var string|null $activeRange */
|
||||
/** @var array<string, mixed> $page */
|
||||
/** @var array<string, mixed> $refresh */
|
||||
|
||||
$activeRange = $activeRange ?? '';
|
||||
$errors = $refresh['errors'] ?? [];
|
||||
$total = (int) $page['total'];
|
||||
?>
|
||||
<?php if (($refresh['attempted'] ?? 0) > 0): ?>
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<span class="refresh-chip mono">
|
||||
<span aria-hidden="true">↻</span>
|
||||
<?= (int) $refresh['succeeded'] ?>/<?= (int) $refresh['attempted'] ?> feeds
|
||||
· <?= (int) $refresh['new_items'] ?> new
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($errors !== []): ?>
|
||||
<div class="alert alert-warning alert-dismissible fade show small alert-soft" role="alert">
|
||||
<strong>Some feeds could not be refreshed:</strong>
|
||||
<ul class="mb-0 ps-3">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= View::e((string) $error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="feed"
|
||||
class="row g-4"
|
||||
data-api="api/items.php"
|
||||
data-source="<?= View::e((string) ($activeSource ?? '')) ?>"
|
||||
data-range="<?= View::e($activeRange) ?>"
|
||||
data-page="1"
|
||||
data-page-size="<?= (int) $page['page_size'] ?>"
|
||||
data-has-more="<?= $page['has_more'] ? '1' : '0' ?>"
|
||||
data-total="<?= $total ?>">
|
||||
<?= View::render('cards', ['items' => $page['items']]) ?>
|
||||
</div>
|
||||
|
||||
<div id="feed-empty" class="empty-state text-center <?= $total > 0 ? 'd-none' : '' ?>">
|
||||
<div class="empty-mark" aria-hidden="true"></div>
|
||||
<?php if ($activeRange !== ''): ?>
|
||||
<p class="empty-title mb-1">Nothing in the past <?= View::e(View::rangeLabel($activeRange)) ?></p>
|
||||
<p class="small mb-0">Try a wider time range to see more.</p>
|
||||
<?php else: ?>
|
||||
<p class="empty-title mb-1">Nothing in the feed yet</p>
|
||||
<p class="small mb-0">Feeds may be rate limited right now — reload in a moment.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-5">
|
||||
<button id="load-more"
|
||||
type="button"
|
||||
class="btn btn-tally <?= $page['has_more'] ? '' : 'd-none' ?>">
|
||||
<span class="spinner-border spinner-border-sm me-2 d-none" role="status" aria-hidden="true"></span>
|
||||
Load more stories
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="assets/app.js" defer></script>
|
||||
<script>
|
||||
(function () {
|
||||
var loader = document.getElementById('feed-loader');
|
||||
if (loader !== null) {
|
||||
loader.remove();
|
||||
}
|
||||
|
||||
var count = document.getElementById('story-count');
|
||||
if (count !== null) {
|
||||
count.textContent = '<?= $total ?> stories';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user