import { notFound } from 'next/navigation' import { getPublishedNote } from '@/app/actions/notes-publishing' import { Calendar, Clock, Flag } from 'lucide-react' import { format } from 'date-fns' import katex from 'katex' import { sanitizeRichHtml } from '@/lib/sanitize-content' export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params const note = await getPublishedNote(slug) if (!note) return { title: 'Note not found — Momento' } return { title: `${note.title || 'Published note'} — Momento`, description: note.content?.replace(/<[^>]+>/g, '').slice(0, 160), } } export const revalidate = 60 function decodeHtml(text: string): string { const map: Record = { '"': '"', '&': '&', '<': '<', '>': '>', ''': "'" } return text.replace(/&[a-z#0-9]+;/gi, m => map[m] || m) } function processContent(html: string): string { let result = html // KaTeX block math result = result.replace(/]*data-type="math-equation"[^>]*data-latex="([^"]*)"[^>]*><\/div>/g, (_, latex) => { try { return `
${katex.renderToString(decodeHtml(latex), { displayMode: true, throwOnError: false })}
` } catch { return `
${latex}
` } }) // KaTeX inline math result = result.replace(/]*data-type="inline-math"[^>]*data-latex="([^"]*)"[^>]*>.*?<\/span>/g, (_, latex) => { try { return katex.renderToString(decodeHtml(latex), { displayMode: false, throwOnError: false }) } catch { return latex } }) // Callouts result = result.replace(/]*data-type="callout-block"[^>]*data-callout-type="([^"]*)"[^>]*>/g, (_, type) => { const c: Record = { info: '#eff6ff|#3b82f6', warning: '#fffbeb|#f59e0b', tip: '#faf5ff|#8b5cf6', success: '#f0fdf4|#22c55e', danger: '#fef2f2|#ef4444', } const [bg, border] = (c[type] || c.info).split('|') return `
` }) // Remove outline blocks (need editor JS) result = result.replace(/]*data-type="outline-block"[^>]*><\/div>/g, '') // Remove link preview searchable text result = result.replace(/]*class="link-preview-searchable"[^>]*>[\s\S]*?<\/div>/g, '') // Remove editor buttons result = result.replace(/]*>[\s\S]*?<\/button>/g, '') // Remove contentEditable attributes result = result.replace(/contenteditable="[^"]*"/gi, '') return result } function estimateReadingTime(html: string): number { const words = html.replace(/<[^>]+>/g, ' ').trim().split(/\s+/).length return Math.max(1, Math.ceil(words / 200)) } export default async function PublishedNotePage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params const note = await getPublishedNote(slug) if (!note) notFound() const processedContent = processContent(note.content || '') const readingTime = estimateReadingTime(note.content || '') return ( <> {/* KaTeX + fonts CSS */}
{/* Top bar */}
Momento Signaler
{/* Article */}
{note.publishedAt ? format(new Date(note.publishedAt), 'd MMM yyyy') : ''} · {readingTime} min de lecture

{note.title || 'Sans titre'}

{note.user?.name && (
{note.user.image && } par {note.user.name}
)}
{/* Footer */}
) }