import { notFound } from 'next/navigation' import { getPublishedNote } from '@/app/actions/notes-publishing' import { FileText, Calendar, Clock, Flag } from 'lucide-react' import { format } from 'date-fns' import katex from 'katex' 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 renderMathInHtml(html: string): string { let result = html 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}
` } }) 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 } }) return result } function decodeHtml(text: string): string { return text.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, "'") } function processContent(html: string): string { let result = renderMathInHtml(html) // Render callouts result = result.replace(/]*data-type="callout-block"[^>]*data-callout-type="([^"]*)"[^>]*>/g, (_, type) => { const colors: Record = { info: '#eff6ff|#3b82f6', warning: '#fffbeb|#f59e0b', tip: '#faf5ff|#8b5cf6', success: '#f0fdf4|#22c55e', danger: '#fef2f2|#ef4444', } const [bg, border] = (colors[type] || colors.info).split('|') return `
` }) // Close callouts result = result.replace(/<\/div>(\s*<\/div>)/g, '$1') // Outline blocks — remove (they don't work without the editor) result = result.replace(/]*data-type="outline-block"[^>]*><\/div>/g, '') // Link preview searchable text result = result.replace(/]*class="link-preview-searchable"[^>]*>[\s\S]*?<\/div>/g, '') // Remove buttons (delete/unwrap) result = result.replace(/]*>[\s\S]*?<\/button>/g, '') return result } function estimateReadingTime(html: string): number { const text = html.replace(/<[^>]+>/g, ' ').trim() const words = text.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 ( {/* Top bar */}
Momento
Signaler
{/* Article */}
{/* Meta */}
{note.publishedAt ? format(new Date(note.publishedAt), 'd MMM yyyy') : ''} · {readingTime} min de lecture
{/* Title */}

{note.title || 'Sans titre'}

{/* Author */} {note.user?.name && (
{note.user.image && ( )} par {note.user.name}
)} {/* Divider */}
{/* Content */}
{/* Custom styles injected */}
{/* Footer */} ) }