src/components/now/NowUpdates.astro 2.3 K raw
1
---
2
import { createMarkdownRenderer } from "@/utils";
3
import { POSTS_API } from "@/data/constants";
4
5
const md = await createMarkdownRenderer();
6
7
interface Post {
8
	short_id: string;
9
	title: string | null;
10
	slug: string;
11
	published_date: string | null;
12
	meta_description: string | null;
13
	meta_image: string | null;
14
	canonical_url: string | null;
15
	lang: string;
16
	tags: string | null;
17
	content: string;
18
	created_at: string;
19
	updated_at: string;
20
}
21
22
interface PostsListResponse {
23
	posts: Post[];
24
}
25
26
let posts: Post[] = [];
27
let error = false;
28
29
try {
30
	const res = await fetch(`${POSTS_API}/posts`);
31
32
	if (res.ok) {
33
		const data = (await res.json()) as PostsListResponse;
34
		posts = (data.posts || []).slice().sort((a, b) => {
35
			const dateA = a.published_date ? new Date(a.published_date).getTime() : 0;
36
			const dateB = b.published_date ? new Date(b.published_date).getTime() : 0;
37
			return dateB - dateA;
38
		});
39
	} else {
40
		error = true;
41
	}
42
} catch (err) {
43
	console.error("Error fetching updates:", err);
44
	error = true;
45
}
46
---
47
48
{error && <p>Error loading recent updates.</p>}
49
50
{!error && posts.length === 0 && <p>No recent updates found.</p>}
51
52
{!error && posts.length > 0 && (
53
	<div class="space-y-4">
54
		{posts.map((post) => {
55
			const publishedAt = post.published_date
56
				? new Date(post.published_date).toLocaleDateString()
57
				: "";
58
59
			const contentHTML = post.content
60
				? md.render(post.content).trim()
61
					.replace(/<img\s([^>]*?)>/g, (_, attrs) => {
62
						const hasLoading = /loading\s*=/.test(attrs);
63
						const hasDecoding = /decoding\s*=/.test(attrs);
64
						const hasWidth = /width\s*=/.test(attrs);
65
						const extra = `${hasLoading ? "" : ' loading="lazy"'}${hasDecoding ? "" : ' decoding="async"'}${hasWidth ? "" : ' width="800"'}`;
66
						return `<img ${attrs}${extra} style="height:auto">`;
67
					})
68
				: "";
69
70
			return (
71
				<article class="border-b pb-6 mb-6 last:border-b-0">
72
					{post.title && (
73
						<a
74
							href={`/now/${post.slug}`}
75
							class="block hover:opacity-80 transition-opacity"
76
						>
77
							<h3 class="text-lg font-semibold mb-3">{post.title}</h3>
78
						</a>
79
					)}
80
					<div
81
						class="prose prose-invert max-w-none mb-3"
82
						set:html={contentHTML}
83
					/>
84
					<div class="flex items-center gap-2 text-sm text-zinc-500">
85
						<time>{publishedAt}</time>
86
					</div>
87
				</article>
88
			);
89
		})}
90
	</div>
91
)}