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)
This commit is contained in:
66
memento-note/components/note-peek/note-peek-content.tsx
Normal file
66
memento-note/components/note-peek/note-peek-content.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'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) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user