feat: resolve live Nostr references in blog posts and add embeds

Support naddr/nevent/note slugs for unindexed posts, cache naddr lookups,
render Nostr embeds in markdown, and add a consistency check for events mirrors.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bbe
2026-06-29 03:57:32 +02:00
co-authored by Cursor
parent 6023991f5c
commit 2ef68222bf
23 changed files with 1066 additions and 264 deletions
+11 -31
View File
@@ -2,39 +2,15 @@ import { Navbar } from "@/components/public/Navbar";
import { Footer } from "@/components/public/Footer";
import { MeetupCard } from "@/components/public/MeetupCard";
import { AddToCalendarButton } from "@/components/public/AddToCalendarDialog";
import { getMeetupStartUtc } from "@/lib/meetupEventTime";
import { apiUrl } from "@/lib/api-base";
import { fetchMeetupsLive, partitionMeetups } from "@/lib/meetupsData";
// Re-render at most every 5 minutes so the upcoming/past split stays current.
export const revalidate = 300;
async function fetchMeetups(): Promise<any[]> {
try {
const res = await fetch(apiUrl("/meetups"), { next: { revalidate: 300 } });
if (!res.ok) return [];
const data = await res.json();
return Array.isArray(data) ? data : [];
} catch {
return [];
}
}
// Render at request time from the live backend so crawlers (no JS) always get
// the real list, and the count stays in lock-step with /events.md and /llms.txt.
export const dynamic = "force-dynamic";
export default async function EventsPage() {
const meetups = await fetchMeetups();
const now = new Date();
const upcoming = meetups.filter((m) => {
const start = getMeetupStartUtc(m.date, m.time || "00:00");
if (Number.isNaN(start.getTime())) return false;
return start >= now;
});
const past = meetups
.filter((m) => {
const start = getMeetupStartUtc(m.date, m.time || "00:00");
if (Number.isNaN(start.getTime())) return false;
return start < now;
})
.reverse();
const meetups = await fetchMeetupsLive();
const { upcoming, past } = partitionMeetups(meetups);
return (
<>
@@ -54,7 +30,11 @@ export default async function EventsPage() {
</div>
</header>
<div className="max-w-6xl mx-auto px-8 pb-24 space-y-20">
<div
className="max-w-6xl mx-auto px-8 pb-24 space-y-20"
data-upcoming-count={upcoming.length}
data-past-count={past.length}
>
<div>
<div className="flex items-center justify-between mb-8">
<h2 className="text-xl font-black flex items-center gap-3">