import katex from 'katex' import { sanitizePublishedHtml } from '@/lib/sanitize-content' import { preprocessMathInHtml } from '@/lib/text/math-preprocess' import { transformEditorBlocksForPublish } from '@/lib/publish/transform-editor-blocks' function decodeHtml(text: string): string { const map: Record = { '"': '"', '&': '&', '<': '<', '>': '>', ''': "'" } return text.replace(/&[a-z#0-9]+;/gi, (m) => map[m] || m) } function extractLatex(attrs: string, inner: string): string { const fromAttr = attrs.match(/data-latex=["']([^"']*)["']/i)?.[1] if (fromAttr) return decodeHtml(fromAttr) const text = inner.replace(/<[^>]+>/g, '').trim() return decodeHtml(text) } function isAlreadyKatex(html: string): boolean { return /class=["'][^"']*\bkatex\b/i.test(html) } function renderKatexDisplay(latex: string): string { const trimmed = latex.trim() if (!trimmed) return '' try { const rendered = katex.renderToString(trimmed, { displayMode: true, throwOnError: false }) return `
${rendered}
` } catch { return `
${trimmed}
` } } function renderKatexInline(latex: string): string { const trimmed = latex.trim() if (!trimmed) return '' try { const rendered = katex.renderToString(trimmed, { displayMode: false, throwOnError: false }) return `${rendered}` } catch { return `${trimmed}` } } /** Convertit nœuds éditeur + délimiteurs LaTeX en HTML KaTeX. */ export function renderMathInHtml(html: string): string { if (!html?.trim()) return '' let result = preprocessMathInHtml(html) // Blocs : data-type="math-equation" ou .math-equation-block result = result.replace( /]*(?:data-type=["']math-equation["']|class=["'][^"']*math-equation-block)[^>]*)>([\s\S]*?)<\/div>/gi, (full, attrs, inner) => { if (isAlreadyKatex(full)) return full const latex = extractLatex(attrs, inner) return latex ? renderKatexDisplay(latex) : full }, ) // Inline : data-type="inline-math" ou .inline-math result = result.replace( /]*(?:data-type=["']inline-math["']|class=["'][^"']*inline-math)[^>]*)>([\s\S]*?)<\/span>/gi, (full, attrs, inner) => { if (isAlreadyKatex(full)) return full const latex = extractLatex(attrs, inner) return latex ? renderKatexInline(latex) : full }, ) return result } /** Prépare le HTML de la note pour affichage public (KaTeX, callouts, nettoyage éditeur). */ export function processNoteHtmlForPublish(html: string): string { if (!html?.trim()) return '' let result = renderMathInHtml(html) // Blocs éditeur TipTap → HTML publication (exercices, toggles, callouts…) result = transformEditorBlocksForPublish(result) result = result.replace(/]*class="link-preview-searchable"[^>]*>[\s\S]*?<\/div>/g, '') // Figures éditeur → sémantique publication (img déjà traités ignorés) result = result.replace(/]*?)>/gi, (match, attrs) => { if (/class=["'][^"']*pub-/i.test(match)) return match const altMatch = attrs.match(/\balt=["']([^"']*)["']/i) const alt = altMatch?.[1] || '' return `
${alt ? `
${alt}
` : ''}
` }) return sanitizePublishedHtml(result) } export function extractPublishImageUrls(html: string): string[] { if (!html) return [] const urls = new Set() for (const match of html.matchAll(/]+src=["']([^"'>]+)["']/gi)) { const src = match[1]?.trim() if (src && isAllowedImageSrc(src)) urls.add(src) } return Array.from(urls) } export function isAllowedImageSrc(src: string): boolean { const t = src.trim() return t.startsWith('/uploads/') || t.startsWith('https://') || t.startsWith('http://') || t.startsWith('/api/') }