v3
This commit is contained in:
+266
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
/**
|
||||
* status/post.php — Single update view
|
||||
*
|
||||
* URL: /status/post.php?id=<uid>
|
||||
* Returns 404 with styled error card if the ID is not found.
|
||||
*/
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// ── Load helpers (reuse from index) ──────────────────────────────────────────
|
||||
function status_load_posts(): array {
|
||||
if (!file_exists(STATUS_JSON_PATH)) return [];
|
||||
$raw = json_decode(file_get_contents(STATUS_JSON_PATH), true);
|
||||
return is_array($raw) ? $raw : [];
|
||||
}
|
||||
|
||||
function status_relative_time(string $iso): string {
|
||||
try {
|
||||
$dt = new DateTime($iso, new DateTimeZone('UTC'));
|
||||
$now = new DateTime('now', new DateTimeZone('UTC'));
|
||||
$diff = $now->getTimestamp() - $dt->getTimestamp();
|
||||
if ($diff < 60) return 'just now';
|
||||
if ($diff < 3600) return floor($diff / 60) . 'm ago';
|
||||
if ($diff < 86400) return floor($diff / 3600) . 'h ago';
|
||||
if ($diff < 604800) return floor($diff / 86400) . 'd ago';
|
||||
if ($diff < 2592000) return floor($diff / 604800) . 'w ago';
|
||||
return $dt->format('M j, Y');
|
||||
} catch (Exception $e) { return ''; }
|
||||
}
|
||||
|
||||
function status_abs_date(string $iso): string {
|
||||
try {
|
||||
$dt = new DateTime($iso, new DateTimeZone('UTC'));
|
||||
return $dt->format('F j, Y · g:i A') . ' UTC';
|
||||
} catch (Exception $e) { return $iso; }
|
||||
}
|
||||
|
||||
function status_iso(string $iso): string {
|
||||
try {
|
||||
$dt = new DateTime($iso, new DateTimeZone('UTC'));
|
||||
return $dt->format(DateTime::ATOM);
|
||||
} catch (Exception $e) { return $iso; }
|
||||
}
|
||||
|
||||
// ── Find post ────────────────────────────────────────────────────────────────
|
||||
$req_id = trim($_GET['id'] ?? '');
|
||||
$all_posts = status_load_posts();
|
||||
$post = null;
|
||||
$post_idx = null;
|
||||
|
||||
foreach ($all_posts as $i => $p) {
|
||||
if (($p['id'] ?? '') === $req_id) {
|
||||
$post = $p;
|
||||
$post_idx = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$found = ($post !== null);
|
||||
if (!$found) http_response_code(404);
|
||||
|
||||
// ── Prev / next navigation ───────────────────────────────────────────────────
|
||||
$prev_post = ($found && $post_idx > 0) ? $all_posts[$post_idx - 1] : null;
|
||||
$next_post = ($found && $post_idx < count($all_posts) - 1) ? $all_posts[$post_idx + 1] : null;
|
||||
|
||||
// ── Meta ──────────────────────────────────────────────────────────────────────
|
||||
$page_title = $found
|
||||
? 'Update #' . htmlspecialchars($post['id']) . ' — ' . htmlspecialchars(STATUS_SITE_NAME)
|
||||
: '404 Not Found — ' . htmlspecialchars(STATUS_SITE_NAME);
|
||||
|
||||
$canonical = STATUS_BASE_URL . '/post.php?id=' . rawurlencode($req_id);
|
||||
$og_desc = $found ? mb_substr(strip_tags($post['text'] ?? ''), 0, 200) : 'Update not found.';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $page_title ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($og_desc) ?>">
|
||||
<?php if (!$found): ?><meta name="robots" content="noindex"><?php endif; ?>
|
||||
<link rel="canonical" href="<?= htmlspecialchars($canonical) ?>">
|
||||
|
||||
<meta property="og:title" content="<?= $found ? 'Update #' . htmlspecialchars($post['id']) : '404' ?>">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="<?= htmlspecialchars($canonical) ?>">
|
||||
<meta property="og:site_name" content="<?= htmlspecialchars(STATUS_SITE_NAME) ?>">
|
||||
<meta property="og:description" content="<?= htmlspecialchars($og_desc) ?>">
|
||||
<?php if ($found && !empty($post['date'])): ?>
|
||||
<meta property="article:published_time" content="<?= htmlspecialchars(status_iso($post['date'])) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="shortcut icon" href="<?= STATUS_FAVICON_ICO ?>">
|
||||
<link rel="apple-touch-icon" href="<?= STATUS_FAVICON_JPG ?>">
|
||||
|
||||
<?php if ($prev_post): ?>
|
||||
<link rel="prev" href="<?= htmlspecialchars(STATUS_BASE_URL . '/post.php?id=' . rawurlencode($prev_post['id'])) ?>">
|
||||
<?php endif; ?>
|
||||
<?php if ($next_post): ?>
|
||||
<link rel="next" href="<?= htmlspecialchars(STATUS_BASE_URL . '/post.php?id=' . rawurlencode($next_post['id'])) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
|
||||
<?php require __DIR__ . '/shared_styles.php'; ?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page" role="main">
|
||||
|
||||
<!-- ── Top bar ─────────────────────────────────────────────────────────── -->
|
||||
<header class="topbar">
|
||||
<a class="back-link" href="<?= htmlspecialchars(STATUS_BASE_URL) ?>/">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="19" y1="12" x2="5" y2="12"/>
|
||||
<polyline points="12 19 5 12 12 5"/>
|
||||
</svg>
|
||||
All updates
|
||||
</a>
|
||||
<?php if ($found): ?>
|
||||
<span class="topbar-badge">#<?= htmlspecialchars($post['id']) ?></span>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<?php if (!$found): ?>
|
||||
<!-- ── 404 ──────────────────────────────────────────────────────────────── -->
|
||||
<div class="card card-error">
|
||||
<div class="error-icon" aria-hidden="true">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="error-title">Update not found</h1>
|
||||
<p class="error-body">
|
||||
No update with ID <code><?= htmlspecialchars($req_id) ?></code> exists.
|
||||
It may have been deleted or the link may be incorrect.
|
||||
</p>
|
||||
<a class="btn-back" href="<?= htmlspecialchars(STATUS_BASE_URL) ?>/">
|
||||
← Back to all updates
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<!-- ── Single post ──────────────────────────────────────────────────────── -->
|
||||
|
||||
<div class="page-heading">
|
||||
<p class="eyebrow"></p>
|
||||
<h1 class="post-single-title">
|
||||
#<?= htmlspecialchars($post['id']) ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="card card-single">
|
||||
<article>
|
||||
|
||||
<!-- Meta row -->
|
||||
<div class="post-meta">
|
||||
<?php if (!empty($post['date'])): ?>
|
||||
<time class="post-time" datetime="<?= htmlspecialchars(status_iso($post['date'])) ?>"
|
||||
title="<?= htmlspecialchars(status_abs_date($post['date'])) ?>">
|
||||
<?= htmlspecialchars(status_relative_time($post['date'])) ?>
|
||||
</time>
|
||||
<span class="post-abs-date"><?= htmlspecialchars(status_abs_date($post['date'])) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php foreach ((array)($post['tags'] ?? []) as $tag): ?>
|
||||
<a class="post-tag"
|
||||
href="<?= htmlspecialchars(STATUS_BASE_URL . '/?tag=' . rawurlencode($tag)) ?>"
|
||||
title="All updates tagged <?= htmlspecialchars($tag) ?>"><?= htmlspecialchars($tag) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<p class="post-body"><?= htmlspecialchars($post['text'] ?? '') ?></p>
|
||||
|
||||
<!-- Optional link -->
|
||||
<?php if (!empty($post['link'])): ?>
|
||||
<a class="post-link-large"
|
||||
href="<?= htmlspecialchars($post['link']) ?>"
|
||||
<?= str_starts_with($post['link'], 'http') ? 'target="_blank" rel="noopener noreferrer"' : '' ?>>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
<?= htmlspecialchars($post['link_label'] ?? $post['link']) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Permalink row -->
|
||||
<div class="single-meta-row">
|
||||
<div class="single-uid-block">
|
||||
<span class="single-uid-label">UID</span>
|
||||
<code class="single-uid"><?= htmlspecialchars($post['id']) ?></code>
|
||||
</div>
|
||||
<?php if (!empty($post['date'])): ?>
|
||||
<div class="single-uid-block">
|
||||
<span class="single-uid-label">Published</span>
|
||||
<code class="single-uid"><?= htmlspecialchars(status_iso($post['date'])) ?></code>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- ── Prev / next navigation ───────────────────────────────────────────── -->
|
||||
<?php if ($prev_post || $next_post): ?>
|
||||
<nav class="post-nav" aria-label="Adjacent posts">
|
||||
<div class="post-nav-inner">
|
||||
|
||||
<?php if ($prev_post): ?>
|
||||
<a class="post-nav-btn post-nav-prev"
|
||||
href="<?= htmlspecialchars(STATUS_BASE_URL . '/post.php?id=' . rawurlencode($prev_post['id'])) ?>">
|
||||
<span class="post-nav-dir">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="19" y1="12" x2="5" y2="12"/>
|
||||
<polyline points="12 19 5 12 12 5"/>
|
||||
</svg>
|
||||
Newer
|
||||
</span>
|
||||
<span class="post-nav-uid">#<?= htmlspecialchars($prev_post['id']) ?></span>
|
||||
<span class="post-nav-snippet"><?= htmlspecialchars(mb_substr($prev_post['text'] ?? '', 0, 60)) ?>…</span>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<div class="post-nav-btn post-nav-empty" aria-hidden="true"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($next_post): ?>
|
||||
<a class="post-nav-btn post-nav-next"
|
||||
href="<?= htmlspecialchars(STATUS_BASE_URL . '/post.php?id=' . rawurlencode($next_post['id'])) ?>">
|
||||
<span class="post-nav-dir">
|
||||
Older
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
<polyline points="12 5 19 12 12 19"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="post-nav-uid">#<?= htmlspecialchars($next_post['id']) ?></span>
|
||||
<span class="post-nav-snippet"><?= htmlspecialchars(mb_substr($next_post['text'] ?? '', 0, 60)) ?>…</span>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<div class="post-nav-btn post-nav-empty" aria-hidden="true"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; // end $found ?>
|
||||
|
||||
<footer class="page-foot">
|
||||
<span class="foot-note"><?= htmlspecialchars(STATUS_SITE_NAME) ?> · status log</span>
|
||||
<a class="foot-link" href="<?= htmlspecialchars(STATUS_HOME_URL) ?>">← Back to home</a>
|
||||
</footer>
|
||||
|
||||
</div><!-- /page -->
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user