import { notFound } from 'next/navigation'
import { getPublishedNote } from '@/app/actions/notes-publishing'
import { Flag, Sparkles } from 'lucide-react'
import { format } from 'date-fns'
import { fr } from 'date-fns/locale'
import { computePublishedSourceHash } from '@/lib/publish/template-render'
import { processNoteHtmlForPublish } from '@/lib/publish/process-note-html'
import { REWRITE_SHARED_CSS, KATEX_PUBLISH_CSS } from '@/lib/publish/shared-css'
import { ReadingProgress } from '@/components/publish/reading-progress'
import { CopyLinkButton } from '@/components/publish/copy-link-button'
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const note = await getPublishedNote(slug)
if (!note) return { title: 'Note introuvable — Memento' }
return {
title: `${note.title || 'Note publiée'} — Memento`,
description: note.content?.replace(/<[^>]+>/g, '').slice(0, 160),
}
}
export const revalidate = 60
function estimateReadingTime(html: string): number {
const words = html.replace(/<[^>]+>/g, ' ').trim().split(/\s+/).filter(Boolean).length
return Math.max(1, Math.ceil(words / 200))
}
function formatDate(d: Date | string | null | undefined, pattern = 'd MMMM yyyy') {
if (!d) return null
return format(new Date(d), pattern, { locale: fr })
}
/* ─── Shared base CSS (selection, print, typography polish) ─────────────── */
const BASE_RESET = `
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
img { max-width: 100%; height: auto; }
::selection { background: color-mix(in srgb, var(--pub-accent, #A47148) 28%, transparent); }
@media print {
.pub-nav, .pub-progress, .pub-copy, .pub-report { display: none !important; }
.pub-root { min-height: auto !important; }
}
`
const ARTICLE_TYPO = `
.pub-article img { max-width: 100%; height: auto; }
.pub-article h2 { scroll-margin-top: 5rem; }
.pub-article h3 { scroll-margin-top: 5rem; }
.pub-article p + p { margin-top: 0; }
.pub-article .pub-body-source > p:first-of-type::first-letter,
.pub-article .pub-rewrite-body > p:first-of-type::first-letter {
font-weight: 700;
}
.pub-article mark {
background: color-mix(in srgb, var(--pub-accent, #A47148) 22%, transparent);
padding: 0.05em 0.2em;
border-radius: 2px;
}
.pub-article hr {
border: none;
border-top: 1px solid color-mix(in srgb, var(--pub-accent, #A47148) 25%, transparent);
margin: 2.5em auto;
width: 40%;
}
.pub-article .pub-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-top: 2.5em;
}
.pub-article .pub-gallery-img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 8px;
display: block;
}
.pub-article .pub-figure { margin: 2.25em 0; text-align: center; }
.pub-article .pub-figure-img {
max-width: 100%;
border-radius: 10px;
box-shadow: 0 16px 48px rgba(0,0,0,0.1);
}
.pub-article .pub-figure-caption {
margin-top: 0.7em;
font-size: 0.82em;
opacity: 0.65;
font-style: italic;
}
`
/* ─── MAGAZINE — editorial prestige ─────────────────────────────────────── */
function MagazinePage({ note, bodyHtml, readingTime, slug, isStale }: PageProps) {
const dateLabel = formatDate(note.publishedAt)
return (
<>