feat: Wizard IA + Export PDF + fixes blocs
- Wizard IA: création de carnet complet (étudiant/prof/ingénieur)
- 3 profils, choix niveau, nombre de notes (3-12)
- Étapes numérotées, messages de progression, écran de succès
- Notes riches avec callouts, toggles, équations, colonnes
- Structured View auto-créé avec propriétés Statut/Difficulté
- Embeddings en arrière-plan
- Export PDF: clone le DOM réel, rend KaTeX, préserve couleurs callouts
- Fix: boutons toggle/callout en hover-only
- Fix: parsing JSON robuste (backslashes LaTeX)
- Fix: flushSync warning (queueMicrotask)
- Fix: drag handle clamp viewport
- Bouton wizard dans la sidebar (✨ à côté du +)
- i18n FR/EN complet
This commit is contained in:
@@ -19,7 +19,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles,
|
||||
Maximize2, Copy, ArrowLeft, ChevronRight, PanelRight, Check, Loader2, Save, MoreHorizontal,
|
||||
Trash2, LogOut, Wand2, Share2, Wind, Paperclip, GraduationCap, FileDown, FileUp, Mic, MicOff
|
||||
Trash2, LogOut, Wand2, Share2, Wind, Paperclip, GraduationCap, FileDown, FileUp, Mic, MicOff, Printer
|
||||
} from 'lucide-react'
|
||||
import { FlashcardGenerateDialog } from '@/components/flashcards/flashcard-generate-dialog'
|
||||
import { NoteShareDialog } from './note-share-dialog'
|
||||
@@ -89,6 +89,124 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportPdf = async () => {
|
||||
const editor = richTextEditorRef?.current?.getEditor()
|
||||
if (!editor) return
|
||||
const title = state.title || note.title || 'Note'
|
||||
|
||||
toast.loading(t('richTextEditor.pdfExportLoading') || 'Génération du PDF...', { id: 'pdf-export' })
|
||||
|
||||
try {
|
||||
const editorEl = document.querySelector('.ProseMirror') as HTMLElement
|
||||
if (!editorEl) throw new Error('Editor not found')
|
||||
|
||||
// Clone the editor DOM to process it for print
|
||||
const clone = editorEl.cloneNode(true) as HTMLElement
|
||||
|
||||
// Remove all action buttons, toolbars, and UI elements
|
||||
clone.querySelectorAll('button, .drag-handle, [contenteditable="false"], .opacity-0, .group-hover\\:opacity-100').forEach(el => el.remove())
|
||||
clone.querySelectorAll('[title]').forEach(el => {
|
||||
const title = el.getAttribute('title')
|
||||
if (title && (title.includes('Supprimer') || title.includes('Delete') || title.includes('Désactiver') || title.includes('Disable') || title.includes('Modifier') || title.includes('Edit'))) {
|
||||
el.remove()
|
||||
}
|
||||
})
|
||||
|
||||
// Render KaTeX equations properly
|
||||
const katex = (await import('katex')).default
|
||||
clone.querySelectorAll('.math-equation-block').forEach(el => {
|
||||
const latex = el.getAttribute('data-latex') || el.textContent || ''
|
||||
try {
|
||||
el.innerHTML = katex.renderToString(latex, { displayMode: true, throwOnError: false })
|
||||
} catch {}
|
||||
})
|
||||
clone.querySelectorAll('.inline-math').forEach(el => {
|
||||
const latex = el.getAttribute('data-latex') || el.textContent || ''
|
||||
try {
|
||||
el.innerHTML = katex.renderToString(latex, { displayMode: false, throwOnError: false })
|
||||
} catch {}
|
||||
})
|
||||
|
||||
// Force show all toggle content
|
||||
clone.querySelectorAll('[class*="hidden"]').forEach(el => {
|
||||
(el as HTMLElement).style.display = 'block'
|
||||
})
|
||||
|
||||
// Apply callout colors as inline styles (Tailwind classes won't work in print window)
|
||||
clone.querySelectorAll('[data-callout-type]').forEach(el => {
|
||||
const type = el.getAttribute('data-callout-type')
|
||||
const colors: Record<string, { bg: string; border: string }> = {
|
||||
info: { bg: '#eff6ff', border: '#93c5fd' },
|
||||
warning: { bg: '#fffbeb', border: '#fcd34d' },
|
||||
tip: { bg: '#faf5ff', border: '#c4b5fd' },
|
||||
success: { bg: '#f0fdf4', border: '#86efac' },
|
||||
danger: { bg: '#fef2f2', border: '#fca5a5' },
|
||||
}
|
||||
const c = colors[type || 'info'] || colors.info
|
||||
const inner = el.querySelector('div')
|
||||
if (inner) {
|
||||
(inner as HTMLElement).style.background = c.bg
|
||||
(inner as HTMLElement).style.borderColor = c.border
|
||||
}
|
||||
})
|
||||
|
||||
const cloneHtml = clone.innerHTML
|
||||
|
||||
const printWindow = window.open('', '_blank')
|
||||
if (!printWindow) {
|
||||
toast.error(t('richTextEditor.pdfExportBlocked') || 'Popup bloqué', { id: 'pdf-export' })
|
||||
return
|
||||
}
|
||||
|
||||
printWindow.document.write(`<!DOCTYPE html><html dir="auto"><head><meta charset="utf-8"><title>${title}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||
<style>
|
||||
@page { margin: 1.5cm; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 720px; margin: 0 auto; line-height: 1.7; color: #1a1a1a; font-size: 14px; }
|
||||
h1 { font-size: 1.8em; border-bottom: 2px solid #e5e5e5; padding-bottom: 0.3em; margin-bottom: 0.5em; }
|
||||
h2 { font-size: 1.4em; margin-top: 1.5em; }
|
||||
h3 { font-size: 1.2em; margin-top: 1.2em; }
|
||||
p { margin: 0.6em 0; }
|
||||
ul, ol { padding-left: 1.5em; }
|
||||
li { margin: 0.3em 0; }
|
||||
blockquote { border-left: 3px solid #d4d4d4; padding-left: 1em; color: #555; font-style: italic; margin: 0.8em 0; }
|
||||
pre { background: #f5f5f5; padding: 0.8em; border-radius: 6px; overflow-x: auto; font-size: 13px; }
|
||||
code { font-family: 'SF Mono', Menlo, monospace; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 0.5em 0.8em; text-align: left; }
|
||||
th { background: #f5f5f5; font-weight: 600; }
|
||||
img { max-width: 100%; height: auto; border-radius: 4px; }
|
||||
.callout-block { padding: 0.8em 1em; border-radius: 8px; border-left: 4px solid; margin: 0.8em 0; }
|
||||
.callout-block[data-callout-type="info"] { background: #eff6ff; border-color: #3b82f6; }
|
||||
.callout-block[data-callout-type="warning"] { background: #fffbeb; border-color: #f59e0b; }
|
||||
.callout-block[data-callout-type="tip"] { background: #faf5ff; border-color: #8b5cf6; }
|
||||
.callout-block[data-callout-type="success"] { background: #f0fdf4; border-color: #22c55e; }
|
||||
.callout-block[data-callout-type="danger"] { background: #fef2f2; border-color: #ef4444; }
|
||||
.toggle-block { border: 1px solid #e5e5e5; border-radius: 8px; margin: 0.8em 0; overflow: hidden; }
|
||||
.toggle-block > div:first-child { background: #f9f9f9; padding: 0.5em 0.8em; font-weight: 600; font-size: 0.85em; }
|
||||
.toggle-content { padding: 0.5em 0.8em; }
|
||||
.columns-block { display: flex; gap: 16px; margin: 0.8em 0; }
|
||||
.columns-block > div { flex: 1; }
|
||||
.math-equation-block { margin: 1em 0; text-align: center; }
|
||||
.outline-block { border: 1px solid #e5e5e5; border-radius: 8px; padding: 0.8em; margin: 1em 0; }
|
||||
.link-preview-block { border: 1px solid #e5e5e5; border-radius: 8px; overflow: hidden; margin: 0.8em 0; }
|
||||
.link-preview-searchable { display: none !important; }
|
||||
a { color: #A47148; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
@media print { body { margin: 0; max-width: 100%; } }
|
||||
</style></head><body><h1>${title}</h1>${cloneHtml}</body></html>`)
|
||||
printWindow.document.close()
|
||||
|
||||
setTimeout(() => {
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
toast.success(t('richTextEditor.pdfExportSuccess') || 'PDF prêt !', { id: 'pdf-export' })
|
||||
}, 800)
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || 'Erreur export PDF', { id: 'pdf-export' })
|
||||
}
|
||||
}
|
||||
|
||||
// ── Markdown import ───────────────────────────────────────────────────────
|
||||
const openMarkdownImport = () => {
|
||||
const input = document.createElement('input')
|
||||
@@ -334,6 +452,10 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
|
||||
<FileDown className="h-4 w-4 me-2" />
|
||||
{t('richTextEditor.exportMarkdown')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleExportPdf}>
|
||||
<Printer className="h-4 w-4 me-2" />
|
||||
{t('richTextEditor.exportPdf') || 'Exporter en PDF'}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={openMarkdownImport}>
|
||||
<FileUp className="h-4 w-4 me-2" />
|
||||
{t('richTextEditor.importMarkdown')}
|
||||
|
||||
Reference in New Issue
Block a user