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 */}
{/* 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 */}
Momento
{' — Votre mémoire augmentée par l\'IA'}
)
}