Files
Momento/memento-note/components/note-peek/note-peek-content.tsx
Antigravity 261eee2953
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 6m6s
CI / Deploy production (on server) (push) Successful in 26s
refactor: factorisation peek panel — NotePeekPanel réutilisable
Architecture:
- components/note-peek/use-note-peek.ts: hook (fetch + state + events)
- components/note-peek/note-peek-content.tsx: rendu markdown/richtext/KaTeX
- components/note-peek/note-peek-panel.tsx: panel (overlay + inline modes)
- components/note-peek/index.ts: exports
- lib/use-scroll-to-block.ts: utilitaire scroll vers data-id

insights/page.tsx:
- ~95 lignes (state + fetch + KaTeX useEffect + AnimatePresence) → 10 lignes
- peek.open(noteId) remplace handleNoteClick complexe
- <NotePeekPanel mode=overlay /> remplace tout le JSX du panel

NotePeekPanel gère:
- markdown (MarkdownContent) + richtext (DOMPurify + KaTeX lazy)
- RTL (slide gauche pour fa/ar)
- prefers-reduced-motion
- role=dialog + aria-modal (overlay mode)
- loading spinner
- bouton Maximize2 + X
- renderContent prop pour custom (éditeur read-only)
2026-07-04 23:37:37 +00:00

67 lines
3.1 KiB
TypeScript

'use client'
import { useEffect, useRef, type ReactNode } from 'react'
import DOMPurify from 'dompurify'
import { MarkdownContent } from '@/components/markdown-content'
import 'katex/dist/katex.min.css'
import type { Note } from '@/lib/types'
interface NotePeekContentProps {
note: Note
className?: string
children?: ReactNode
}
export function NotePeekContent({ note, className, children }: NotePeekContentProps) {
const htmlRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!htmlRef.current || note.isMarkdown) return
let cancelled = false
void (async () => {
const katex = (await import('katex')).default
if (cancelled || !htmlRef.current) return
htmlRef.current.querySelectorAll('.math-equation-block[data-latex], .inline-math[data-latex]').forEach(el => {
const latex = el.getAttribute('data-latex') || ''
const isDisplay = el.classList.contains('math-equation-block')
try { el.innerHTML = katex.renderToString(latex, { displayMode: isDisplay, throwOnError: false }) } catch {}
})
})()
return () => { cancelled = true }
}, [note.id, note.content, note.isMarkdown])
if (children) return <>{children}</>
if (note.isMarkdown) {
return <MarkdownContent content={note.content} className={className} />
}
return (
<div
ref={htmlRef}
dir="auto"
className={`editor-body prose prose-lg dark:prose-invert max-w-none leading-relaxed text-ink dark:text-dark-ink
[&_h1]:text-2xl [&_h1]:font-serif [&_h1]:font-semibold [&_h1]:mt-8 [&_h1]:mb-4
[&_h2]:text-xl [&_h2]:font-serif [&_h2]:font-semibold [&_h2]:mt-6 [&_h2]:mb-3
[&_h3]:text-lg [&_h3]:font-semibold [&_h3]:mt-5 [&_h3]:mb-2
[&_p]:my-3 [&_p]:leading-relaxed
[&_ul]:list-disc [&_ul]:ps-6 [&_ul]:my-3
[&_ol]:list-decimal [&_ol]:ps-6 [&_ol]:my-3
[&_li]:my-1
[&_blockquote]:border-l-4 [&_blockquote]:border-brand-accent [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground [&_blockquote]:my-4
[&_pre]:bg-zinc-100 [&_pre]:dark:bg-zinc-900 [&_pre]:p-4 [&_pre]:rounded-lg [&_pre]:overflow-x-auto [&_pre]:text-sm [&_pre]:my-4
[&_code]:bg-zinc-100 [&_code]:dark:bg-zinc-800 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-sm [&_code]:font-mono
[&_a]:text-blue-600 [&_a]:underline
[&_img]:rounded-lg [&_img]:max-w-full [&_img]:my-4
[&_table]:w-full [&_table]:my-4 [&_table]:border-collapse
[&_th]:border [&_th]:border-border [&_th]:p-2 [&_th]:bg-muted [&_th]:font-semibold [&_th]:text-left
[&_td]:border [&_td]:border-border [&_td]:p-2
[&_hr]:border-border [&_hr]:my-6
[&_[data-callout-type]]:p-4 [&_[data-callout-type]]:rounded-lg [&_[data-callout-type]]:my-4
[&_div[data-type='toggle-block']]:my-3
[&_div[data-type='toggle-block']_details]:rounded-lg [&_div[data-type='toggle-block']_details]:border [&_div[data-type='toggle-block']_details]:border-border ${className || ''}`}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(note.content) }}
/>
)
}