import { NEWSLETTER_KIND, NEWSLETTER_TAG } from '@/lib/types'; /** * The subset of a static newsletter issue we need to turn it into a NIP-23 * long-form article (kind 30023). Kept structural (not importing the page) to * avoid a page → lib → page import cycle. */ export interface StaticIssueLike { number: number; date: string; // ISO "YYYY-MM-DD" vibe: string; updates: { title: string; tag: string; body: string }[]; spotOfTheWeek?: { title: string; location: string; why: string; mapperName?: string; image?: string; href?: string; }; communityNote: string; } /** Stable addressable identifier for a backfilled archive issue. */ export function issueSlug(number: number): string { return `issue-${number}`; } /** Human title for an issue, e.g. "SkateSpots Drop #005". */ export function issueTitle(number: number): string { return `SkateSpots Drop #${String(number).padStart(3, '0')}`; } /** Render a static issue's structured sections into a Markdown article body. */ export function issueToMarkdown(issue: StaticIssueLike): string { const lines: string[] = []; lines.push(`_${issue.vibe}_`); lines.push(''); lines.push('## What shipped'); lines.push(''); for (const u of issue.updates) { lines.push(`### ${u.title}`); lines.push(`**${u.tag}**`); lines.push(''); lines.push(u.body); lines.push(''); } if (issue.spotOfTheWeek) { const s = issue.spotOfTheWeek; lines.push('## Spot of the Week'); lines.push(''); lines.push(`**${s.title}** — ${s.location}`); lines.push(''); lines.push(s.why); if (s.mapperName) { lines.push(''); lines.push(`_Mapped by ${s.mapperName}_`); } lines.push(''); } lines.push('## Community'); lines.push(''); lines.push(issue.communityNote); lines.push(''); lines.push('---'); lines.push(''); lines.push('Read every drop at https://skate-spots.org/#/newsletter'); return lines.join('\n'); } /** A one-line summary for the article's NIP-23 `summary` tag. */ export function issueSummary(issue: StaticIssueLike): string { return issue.vibe.length > 180 ? `${issue.vibe.slice(0, 177)}…` : issue.vibe; } /** Build the full (unsigned) kind-30023 event template for a static issue. */ export function buildIssueArticle(issue: StaticIssueLike) { const publishedAt = Math.floor(new Date(issue.date + 'T12:00:00Z').getTime() / 1000); const image = issue.spotOfTheWeek?.image; return { kind: NEWSLETTER_KIND, content: issueToMarkdown(issue), tags: [ ['d', issueSlug(issue.number)], ['title', issueTitle(issue.number)], ['summary', issueSummary(issue)], ...(image ? [['image', image]] : []), ['published_at', String(Number.isFinite(publishedAt) ? publishedAt : Math.floor(Date.now() / 1000))], ['t', NEWSLETTER_TAG], ['t', 'skatespots'], ['t', 'newsletter'], ['t', 'skateboarding'], ['alt', `SkateSpots newsletter issue: ${issueTitle(issue.number)}`], ] as string[][], }; }