Files
Ty Clifford 6ab3058a92 v3
2026-05-20 21:08:23 -04:00

267 lines
11 KiB
PHP

<?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>