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>
82 lines
2.2 KiB
TypeScript
82 lines
2.2 KiB
TypeScript
'use client'
|
|
|
|
import { useMemo } from 'react'
|
|
import katex from 'katex'
|
|
import 'katex/dist/katex.min.css'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
/** Render node/callout label: plain lines + inline $KaTeX$. */
|
|
export function DemoRichLabel({
|
|
text,
|
|
className,
|
|
lit,
|
|
}: {
|
|
text: string
|
|
className?: string
|
|
lit?: boolean
|
|
}) {
|
|
const lines = useMemo(() => {
|
|
return text.split(/\\n|\n/).map((line) => {
|
|
const parts: Array<{ type: 'text' | 'math'; value: string }> = []
|
|
const re = /\$([^$]+)\$/g
|
|
let last = 0
|
|
let m: RegExpExecArray | null
|
|
while ((m = re.exec(line)) !== null) {
|
|
if (m.index > last) {
|
|
parts.push({ type: 'text', value: line.slice(last, m.index) })
|
|
}
|
|
parts.push({ type: 'math', value: m[1] || '' })
|
|
last = m.index + m[0].length
|
|
}
|
|
if (last < line.length) parts.push({ type: 'text', value: line.slice(last) })
|
|
if (parts.length === 0) parts.push({ type: 'text', value: line })
|
|
return parts
|
|
})
|
|
}, [text])
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex flex-col items-center justify-center gap-0.5 text-center leading-snug',
|
|
className
|
|
)}
|
|
>
|
|
{lines.map((parts, i) => (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
'flex flex-wrap items-center justify-center gap-x-1',
|
|
i === 0
|
|
? lit
|
|
? 'text-[13px] font-semibold tracking-tight'
|
|
: 'text-[12.5px] font-semibold tracking-tight'
|
|
: 'text-[12px] font-medium opacity-90'
|
|
)}
|
|
>
|
|
{parts.map((p, j) => {
|
|
if (p.type === 'math') {
|
|
let html = p.value
|
|
try {
|
|
html = katex.renderToString(p.value, {
|
|
displayMode: false,
|
|
throwOnError: false,
|
|
})
|
|
} catch {
|
|
/* keep raw */
|
|
}
|
|
return (
|
|
<span
|
|
key={j}
|
|
className="katex-node inline-block [&_.katex]:text-[0.95em]"
|
|
dangerouslySetInnerHTML={{ __html: html }}
|
|
/>
|
|
)
|
|
}
|
|
return <span key={j}>{p.value}</span>
|
|
})}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|