223 lines
11 KiB
PHP
223 lines
11 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use NeonBlog\ContentRepository;
|
|
use NeonBlog\View;
|
|
|
|
$site = $config->get('site', []);
|
|
$social = $config->get('social', []);
|
|
$titleText = isset($title) && $title !== $site['title'] ? $title . ' | ' . $site['title'] : $site['title'];
|
|
$description = $item['summary'] ?? $site['description'] ?? '';
|
|
$postCard = static function (array $entry): void { ?>
|
|
<article class="post-card">
|
|
<div class="post-card__meta">
|
|
<a href="<?= View::url('category/' . ContentRepository::slugify((string) $entry['category'])) ?>"><?= View::e($entry['category']) ?></a>
|
|
<span><?= View::date((string) $entry['date']) ?></span>
|
|
</div>
|
|
<h2><a href="<?= View::itemUrl($entry) ?>"><?= View::e($entry['title']) ?></a></h2>
|
|
<p><?= View::e($entry['summary']) ?></p>
|
|
<?php if (!empty($entry['tags'])): ?>
|
|
<div class="tag-row">
|
|
<?php foreach ($entry['tags'] as $tag): ?>
|
|
<a href="<?= View::url('tag/' . ContentRepository::slugify((string) $tag)) ?>"><?= View::e($tag) ?></a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php };
|
|
$renderPagination = static function (array $pagination, callable $url): void { ?>
|
|
<?php if (($pagination['pages'] ?? 1) > 1): ?>
|
|
<nav class="pagination" aria-label="Pagination">
|
|
<?php if ($pagination['has_previous']): ?>
|
|
<a class="pager" href="<?= $url((int) $pagination['previous']) ?>">Previous</a>
|
|
<?php endif; ?>
|
|
<span>Page <?= (int) $pagination['page'] ?> of <?= (int) $pagination['pages'] ?></span>
|
|
<?php if ($pagination['has_next']): ?>
|
|
<a class="pager" href="<?= $url((int) $pagination['next']) ?>">Next</a>
|
|
<?php endif; ?>
|
|
</nav>
|
|
<?php endif; ?>
|
|
<?php };
|
|
?><!doctype html>
|
|
<html lang="<?= View::e($site['language'] ?? 'en') ?>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><?= View::e($titleText) ?></title>
|
|
<meta name="description" content="<?= View::e($description) ?>">
|
|
<link rel="alternate" type="application/rss+xml" title="<?= View::e($site['title']) ?> feed" href="<?= View::url('feed.xml') ?>">
|
|
<link rel="stylesheet" href="<?= View::asset('style.css') ?>">
|
|
<script defer src="<?= View::asset('app.js') ?>"></script>
|
|
</head>
|
|
<body>
|
|
<header class="site-header">
|
|
<a class="brand" href="<?= View::url('') ?>">
|
|
<span class="brand__mark"></span>
|
|
<span>
|
|
<strong><?= View::e($site['title'] ?? 'Neon Blog') ?></strong>
|
|
<small><?= View::e($site['tagline'] ?? '') ?></small>
|
|
</span>
|
|
</a>
|
|
|
|
<nav class="main-nav" aria-label="Primary">
|
|
<a href="<?= View::url('') ?>">Home</a>
|
|
<?php foreach ($navPages as $pageItem): ?>
|
|
<a href="<?= View::itemUrl($pageItem) ?>"><?= View::e($pageItem['title']) ?></a>
|
|
<?php endforeach; ?>
|
|
<a href="<?= View::url('feed.xml') ?>">RSS</a>
|
|
</nav>
|
|
|
|
<form class="search-form" action="<?= View::url('search') ?>" method="get">
|
|
<input type="search" name="q" value="<?= View::e($search_query ?? '') ?>" placeholder="Search" aria-label="Search">
|
|
<button type="submit" aria-label="Search">Go</button>
|
|
</form>
|
|
|
|
<div class="theme-tools" aria-label="Theme controls">
|
|
<button class="mode-toggle" type="button" data-mode-toggle aria-label="Toggle light mode">Light</button>
|
|
<?php foreach (['green', 'blue', 'red', 'pink', 'purple', 'orange'] as $accent): ?>
|
|
<button class="swatch swatch--<?= $accent ?>" type="button" data-accent="<?= $accent ?>" aria-label="<?= ucfirst($accent) ?> accent"></button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="site-shell">
|
|
<section class="content-lane">
|
|
<?php if ($flash): ?>
|
|
<div class="notice notice--<?= View::e($flash['type']) ?>"><?= View::e($flash['message']) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($template === 'listing'): ?>
|
|
<header class="page-heading">
|
|
<p><?= View::e($site['description'] ?? '') ?></p>
|
|
<h1><?= View::e($heading ?? $title ?? '') ?></h1>
|
|
</header>
|
|
<?php if (($search_query ?? null) !== null): ?>
|
|
<form class="wide-search" action="<?= View::url('search') ?>" method="get">
|
|
<input type="search" name="q" value="<?= View::e($search_query) ?>" placeholder="Search posts and pages" aria-label="Search posts and pages">
|
|
<button type="submit">Search</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<div class="post-grid">
|
|
<?php foreach ($items as $entry): ?>
|
|
<?php $postCard($entry); ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if (empty($items)): ?>
|
|
<p class="empty-state"><?= View::e($empty_message ?? 'No posts found.') ?></p>
|
|
<?php endif; ?>
|
|
<?php $renderPagination($pagination, $pagination_url); ?>
|
|
<?php elseif ($template === 'single'): ?>
|
|
<article class="article">
|
|
<header class="article__header">
|
|
<?php if ($item['type'] === 'post'): ?>
|
|
<div class="post-card__meta">
|
|
<a href="<?= View::url('category/' . ContentRepository::slugify((string) $item['category'])) ?>"><?= View::e($item['category']) ?></a>
|
|
<span><?= View::date((string) $item['date']) ?></span>
|
|
<span><?= View::e($item['author']) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
<h1><?= View::e($item['title']) ?></h1>
|
|
<?php if (!empty($item['summary'])): ?>
|
|
<p><?= View::e($item['summary']) ?></p>
|
|
<?php endif; ?>
|
|
</header>
|
|
<div class="markdown">
|
|
<?= $item['html'] ?>
|
|
</div>
|
|
<?php if (!empty($item['tags'])): ?>
|
|
<footer class="tag-row article-tags">
|
|
<?php foreach ($item['tags'] as $tag): ?>
|
|
<a href="<?= View::url('tag/' . ContentRepository::slugify((string) $tag)) ?>"><?= View::e($tag) ?></a>
|
|
<?php endforeach; ?>
|
|
</footer>
|
|
<?php endif; ?>
|
|
</article>
|
|
|
|
<?php if ($config->get('comments.enabled', true) && $item['allow_comments']): ?>
|
|
<section class="comments" id="comments">
|
|
<h2>Comments</h2>
|
|
<?php if (empty($approved_comments)): ?>
|
|
<p class="empty-state">No comments yet.</p>
|
|
<?php endif; ?>
|
|
<?php foreach ($approved_comments as $comment): ?>
|
|
<article class="comment">
|
|
<header>
|
|
<strong><?= View::e($comment['author']) ?></strong>
|
|
<time datetime="<?= View::e($comment['created_at']) ?>"><?= View::date((string) $comment['created_at'], 'M j, Y g:i a') ?></time>
|
|
</header>
|
|
<p><?= nl2br(View::e($comment['body'])) ?></p>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
|
|
<form class="comment-form" action="<?= View::url('comment/' . $item['slug']) ?>" method="post">
|
|
<div class="form-grid">
|
|
<label>Name <input name="author" required maxlength="120"></label>
|
|
<label>Email <input type="email" name="email" required maxlength="180"></label>
|
|
</div>
|
|
<label>Website <input type="url" name="site_url" maxlength="220"></label>
|
|
<label class="honey-field">Leave this empty <input name="<?= View::e($config->get('comments.honeypot_field', 'website_url')) ?>" tabindex="-1" autocomplete="off"></label>
|
|
<label>Comment <textarea name="body" required rows="6" maxlength="5000"></textarea></label>
|
|
<?php if ($config->get('comments.captcha', true)): ?>
|
|
<input type="hidden" name="captcha_token" value="<?= View::e($captcha['token']) ?>">
|
|
<label><?= View::e($captcha['question']) ?> <input name="captcha_answer" required inputmode="numeric"></label>
|
|
<?php endif; ?>
|
|
<button type="submit">Post Comment</button>
|
|
</form>
|
|
</section>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<section class="error-view">
|
|
<h1><?= View::e($title ?? 'Error') ?></h1>
|
|
<p><?= View::e($message ?? 'Something went wrong.') ?></p>
|
|
</section>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<aside class="side-rail" aria-label="Blog metadata">
|
|
<?php if (is_array($social) && $social !== []): ?>
|
|
<section>
|
|
<h2>Social</h2>
|
|
<div class="social-list">
|
|
<?php foreach ($social as $network => $entry): ?>
|
|
<a href="<?= View::e($entry['url'] ?? '#') ?>" rel="me noopener" target="_blank"><?= View::e($entry['label'] ?? $network) ?></a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<section>
|
|
<h2>Categories</h2>
|
|
<ul class="link-list">
|
|
<?php foreach ($facets['categories'] as $category => $count): ?>
|
|
<li><a href="<?= View::url('category/' . ContentRepository::slugify((string) $category)) ?>"><?= View::e($category) ?></a><span><?= (int) $count ?></span></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Tags</h2>
|
|
<div class="tag-row">
|
|
<?php foreach ($facets['tags'] as $tag => $count): ?>
|
|
<a href="<?= View::url('tag/' . ContentRepository::slugify((string) $tag)) ?>"><?= View::e($tag) ?> <?= (int) $count ?></a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Archive</h2>
|
|
<ul class="link-list">
|
|
<?php foreach ($facets['archives'] as $month => $count): ?>
|
|
<li><a href="<?= View::url('archive/' . str_replace('-', '/', (string) $month)) ?>"><?= View::month((string) $month) ?></a><span><?= (int) $count ?></span></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</section>
|
|
</aside>
|
|
</main>
|
|
|
|
<footer class="site-footer">
|
|
<span><?= View::e($site['title'] ?? 'Neon Blog') ?></span>
|
|
<span>Powered by PHP, SQLite, JSON, and Markdown files.</span>
|
|
</footer>
|
|
</body>
|
|
</html>
|