feat: page interactive, démos Play/Step et simulateur Carnot
Ajoute le pipeline PageSpec (validation, rendu, publication /p/{slug}),
les démos TipTap /demo, et le simulateur Carnot (modes frigo/PAC/moteur,
énergie kJ vs puissance W, unités K/°C/°F) avec correctifs d’équations KaTeX.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
91
memento-note/components/interactive-page/page-md.tsx
Normal file
91
memento-note/components/interactive-page/page-md.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import katex from 'katex'
|
||||
import { marked } from 'marked'
|
||||
import { sanitizeRichHtml } from '@/lib/sanitize-content'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/** Light markdown + inline $KaTeX$ for page prose / callouts / speak. */
|
||||
export function PageMd({
|
||||
md,
|
||||
className,
|
||||
}: {
|
||||
md: string
|
||||
className?: string
|
||||
}) {
|
||||
const html = useMemo(() => {
|
||||
const placeholders: string[] = []
|
||||
const withSlots = md.replace(/\$([^$]+)\$/g, (_, tex: string) => {
|
||||
const i = placeholders.length
|
||||
try {
|
||||
placeholders.push(
|
||||
katex.renderToString(tex, { displayMode: false, throwOnError: false })
|
||||
)
|
||||
} catch {
|
||||
placeholders.push(tex)
|
||||
}
|
||||
return `%%KATEX${i}%%`
|
||||
})
|
||||
|
||||
let out = marked.parse(withSlots, { gfm: true, breaks: true }) as string
|
||||
placeholders.forEach((frag, i) => {
|
||||
out = out.replace(`%%KATEX${i}%%`, frag)
|
||||
})
|
||||
return sanitizeRichHtml(out)
|
||||
}, [md])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:tracking-tight',
|
||||
className
|
||||
)}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function PageFormula({
|
||||
tex,
|
||||
caption,
|
||||
}: {
|
||||
tex: string
|
||||
caption?: string
|
||||
}) {
|
||||
const html = useMemo(() => {
|
||||
try {
|
||||
return katex.renderToString(tex, {
|
||||
displayMode: true,
|
||||
throwOnError: false,
|
||||
})
|
||||
} catch {
|
||||
return tex
|
||||
}
|
||||
}, [tex])
|
||||
|
||||
return (
|
||||
<figure
|
||||
className="my-5 overflow-x-auto rounded-xl border px-4 py-4 md:px-5"
|
||||
style={{
|
||||
background: 'var(--pp-paper)',
|
||||
borderColor: 'var(--pp-line)',
|
||||
borderLeft: '4px solid var(--pp-plum)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="text-[1.15em] [&_.katex-display]:m-0"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
{caption ? (
|
||||
<figcaption
|
||||
className="mt-2.5 text-sm"
|
||||
style={{ color: 'var(--pp-muted)' }}
|
||||
>
|
||||
{caption}
|
||||
</figcaption>
|
||||
) : null}
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user