UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 46.9 kB
{"version":3,"sources":["../src/components/HtmlMathRenderer/HtmlMathRenderer.tsx","../src/components/MarkdownMathRenderer/MarkdownMathRenderer.tsx","../src/components/HtmlMathRenderer/utils.ts"],"sourcesContent":["import { CSSProperties, forwardRef, memo, ReactNode, Ref } from 'react';\nimport 'katex/dist/katex.min.css';\nimport { KatexMath } from './KatexMath';\nimport { cn } from '../../utils/utils';\nimport { normalizeLineBreaksInHtml } from '../../utils/htmlLineBreaks';\nimport MarkdownMathRenderer from '../MarkdownMathRenderer/MarkdownMathRenderer';\nimport {\n isLikelyMarkdown,\n processHtmlWithMath,\n sanitizeHtmlForDisplay,\n} from './utils';\n\nexport interface HtmlMathRendererProps {\n /** HTML content to render, may contain LaTeX math expressions */\n content: string;\n /** Additional CSS class names */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n /** Whether to sanitize HTML before rendering (default: true) */\n sanitize?: boolean;\n /** Custom error renderer for math errors */\n renderMathError?: (latex: string) => ReactNode;\n /** Test ID for testing */\n testId?: string;\n /** Whether to render as inline element (span) instead of block (div). Use when inside labels or other phrasing content. */\n inline?: boolean;\n}\n\n/**\n * HtmlMathRenderer - Renders HTML content with LaTeX math expressions\n *\n * Supports multiple LaTeX formats:\n * - Display mode: $$...$$ (centered block)\n * - Inline mode: $...$ (inline with text)\n * - LaTeX tags: <latex>...</latex>\n * - Editor spans: <span class=\"math-formula\" data-latex=\"...\">\n * - Legacy spans: <span class=\"math-expression\" data-math=\"...\">\n * - LaTeX environments: \\begin{...}...\\end{...}\n *\n * @example\n * ```tsx\n * <HtmlMathRenderer\n * content=\"<p>A fórmula é: $$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$</p>\"\n * />\n * ```\n */\nconst HtmlMathRenderer = forwardRef<HTMLElement, HtmlMathRendererProps>(\n (\n {\n content,\n className,\n style,\n sanitize = true,\n renderMathError,\n testId,\n inline = false,\n },\n ref\n ) => {\n // AI-generated questions/resolutions arrive as Markdown + LaTeX. The HTML\n // pipeline below would render their `**`/`####`/`*` tokens literally and\n // collapse line breaks, so route that content to the Markdown renderer.\n // Inline usage stays on the HTML path to keep phrasing-content validity\n // (Markdown emits block elements: <p>, <ul>, <h4>, ...).\n //\n // Note: `renderMathError` is intentionally not forwarded here. The Markdown\n // path renders math via rehype-katex, which already degrades gracefully on\n // invalid LaTeX (KaTeX's built-in red error output) rather than throwing.\n // `renderMathError` is an HTML-path-only customization and is currently\n // unused by any consumer; honoring it on the Markdown path would require a\n // bespoke rehype plugin for no practical gain.\n if (!inline && content && isLikelyMarkdown(content)) {\n return (\n <MarkdownMathRenderer\n ref={ref as Ref<HTMLDivElement>}\n content={content}\n className={className}\n style={style}\n testId={testId}\n />\n );\n }\n\n const defaultErrorRenderer = (latex: string) => (\n <span className=\"text-error-600 text-sm\">Math Error: {latex}</span>\n );\n\n const errorRenderer = renderMathError || defaultErrorRenderer;\n\n const renderContent = () => {\n if (!content) return null;\n\n // Question content is stored as plain text with `\\n` line breaks mixed\n // with loose inline tags. Newlines are insignificant whitespace in HTML,\n // so without this the whole thing renders as one collapsed run of text.\n // Content that is already structured HTML passes through untouched.\n //\n // Breaks are always emitted as <br>, never <p>: the mixed text+math path\n // below wraps each part in its own element, which would cut a paragraph\n // in half across a math expression. <br> is inline and survives the split\n // intact — and `[&_p]:mb-0` below means paragraphs carry no spacing here\n // anyway, so there is nothing to gain from block elements.\n const normalizedContent = normalizeLineBreaksInHtml(content, {\n inline: true,\n });\n\n const processedContent = sanitize\n ? sanitizeHtmlForDisplay(normalizedContent)\n : normalizedContent;\n\n const parts = processHtmlWithMath(processedContent);\n\n // If all parts are text (or empty), render as plain HTML. Use the\n // joined parts (not the raw `processedContent`) so post-split fixes\n // applied inside `processHtmlWithMath` — like decoding `\\$` escapes\n // to literal `$` — actually reach the rendered output.\n if (parts.every((part) => part.type === 'text')) {\n const joinedHtml = parts.map((part) => part.content).join('');\n // Use span for inline mode to allow valid nesting in labels\n const Element = inline ? 'span' : 'div';\n return (\n <Element\n dangerouslySetInnerHTML={{\n __html: joinedHtml || processedContent,\n }}\n />\n );\n }\n\n // Generate stable keys based on content\n const getPartKey = (part: (typeof parts)[0], idx: number) => {\n const contentHash = (part.latex || part.content).slice(0, 20);\n return `${part.type}-${idx}-${contentHash}`;\n };\n\n return (\n <>\n {parts.map((part, index) => {\n const key = getPartKey(part, index);\n if (part.type === 'math' && part.latex) {\n return (\n <KatexMath\n key={key}\n math={part.latex}\n renderError={() => errorRenderer(part.latex!)}\n />\n );\n } else if (part.type === 'block-math' && part.latex) {\n // When inline mode, render inline to avoid block-level elements inside span\n if (inline) {\n return (\n <KatexMath\n key={key}\n math={part.latex}\n renderError={() => errorRenderer(part.latex!)}\n />\n );\n }\n return (\n <div key={key} className=\"my-2.5 text-center\">\n <KatexMath\n math={part.latex}\n displayMode\n renderError={() => errorRenderer(part.latex!)}\n />\n </div>\n );\n } else {\n return (\n <span\n key={key}\n dangerouslySetInnerHTML={{ __html: part.content }}\n />\n );\n }\n })}\n </>\n );\n };\n\n const sharedClassName = cn(\n // Base styles\n 'leading-relaxed',\n // Paragraph styles\n '[&_p]:mb-0',\n // Hide the KaTeX MathML accessibility layer visually (still readable\n // to screen readers). Tailwind preflight overrides the KaTeX default\n // `position: absolute; clip:...` rule, so the MathML layer ends up\n // duplicating every formula as raw text next to the visual render.\n '[&_.katex-mathml]:sr-only',\n // Table styles (only relevant for block mode, but harmless for inline)\n '[&_table]:border-collapse [&_table]:w-full [&_table]:my-2.5 [&_table]:table-auto',\n '[&_table_td]:border [&_table_td]:border-border-200 [&_table_td]:p-2 [&_table_td]:min-w-[50px] [&_table_td]:align-top',\n '[&_table_th]:border [&_table_th]:border-border-200 [&_table_th]:p-2 [&_table_th]:min-w-[50px] [&_table_th]:align-top [&_table_th]:bg-background-50 [&_table_th]:font-semibold',\n '[&_table_tr:nth-child(even)]:bg-background-50/50',\n '[&_table_tr:hover]:bg-background-100/50',\n // Image styles\n '[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-md [&_img]:my-2',\n // Link styles\n '[&_a]:text-primary-500 [&_a]:underline [&_a:hover]:text-primary-600',\n // Text formatting styles\n '[&_b]:font-bold [&_strong]:font-bold',\n '[&_i]:italic [&_em]:italic',\n '[&_u]:underline',\n className\n );\n\n if (inline) {\n return (\n <span\n ref={ref as Ref<HTMLSpanElement>}\n className={sharedClassName}\n style={style}\n data-testid={testId}\n >\n {renderContent()}\n </span>\n );\n }\n\n return (\n <div\n ref={ref as Ref<HTMLDivElement>}\n className={sharedClassName}\n style={style}\n data-testid={testId}\n >\n {renderContent()}\n </div>\n );\n }\n);\n\nHtmlMathRenderer.displayName = 'HtmlMathRenderer';\n\nexport default memo(HtmlMathRenderer);\n","import { CSSProperties, forwardRef, memo } from 'react';\nimport 'katex/dist/katex.min.css';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport rehypeKatex from 'rehype-katex';\nimport { cn } from '../../utils/utils';\nimport { looksLikeLatex } from '../HtmlMathRenderer/utils';\n\nexport interface MarkdownMathRendererProps {\n /** Markdown content, may contain LaTeX math expressions ($...$ / $$...$$) */\n content: string;\n /** Additional CSS class names */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n /** Test ID for testing */\n testId?: string;\n}\n\n/**\n * Strips zero-width / invisible characters that occasionally leak into\n * AI-generated content and break KaTeX parsing.\n */\nconst stripInvisibleChars = (str: string): string =>\n str.replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '');\n\n/**\n * Escapes the `$` of inline `$...$` spans that do NOT look like real math, so\n * `remark-math` leaves them as literal text. Without this, currency prose like\n * \"R$ 15,00 ... R$ 42,00\" gets paired up and rendered as gibberish math.\n *\n * Reuses the exact `looksLikeLatex` heuristic the HTML renderer applies to\n * single-`$` spans, so both renderers agree on what is math vs currency.\n * Display math (`$$...$$`) is intentionally left untouched (handled below).\n */\nexport const protectCurrencyInlineMath = (markdown: string): string =>\n markdown.replaceAll(\n /(?<!\\\\)\\$(?!\\$)([^\\n$]+?)(?<!\\\\)\\$(?!\\$)/g,\n (match, inner: string) =>\n looksLikeLatex(inner) ? match : match.replaceAll('$', String.raw`\\$`)\n );\n\n/**\n * `remark-math` only renders a `$$...$$` block as centered display math when\n * the delimiters sit on their own lines (\"math flow\"). AI content usually puts\n * the whole equation on a single line (`$$x = y$$`), which would otherwise be\n * rendered inline. Reflow single-line `$$...$$` into block form so equations\n * stay centered, matching how the HTML renderer treats `$$`.\n */\nexport const reflowDisplayMath = (markdown: string): string =>\n markdown.replaceAll(\n /(?<!\\$)\\$\\$(?!\\$)([^\\n]+?)\\$\\$(?!\\$)/g,\n (_match, inner: string) => `\\n\\n$$\\n${inner.trim()}\\n$$\\n\\n`\n );\n\n/**\n * Runs `transform` over the markdown while shielding fenced code blocks and\n * inline code spans: each is stashed behind a placeholder, the transform runs\n * on the rest, then the originals are restored before the markdown reaches\n * react-markdown. Without this, the currency / display-math passes would\n * rewrite literal `$`/`$$` that authors put inside code samples (e.g. a fenced\n * block containing `price = $value`). The placeholder is purely transient (it\n * never reaches the markdown parser) and contains no `$`, so the passes ignore\n * it.\n */\nconst withProtectedCode = (\n markdown: string,\n transform: (input: string) => string\n): string => {\n const stash: string[] = [];\n const tokenized = markdown.replaceAll(\n /```[\\s\\S]*?```|`[^`\\n]*`/g,\n (segment) => {\n const token = `__CODE_SEG_${stash.length}__`;\n stash.push(segment);\n return token;\n }\n );\n return transform(tokenized).replaceAll(\n /__CODE_SEG_(\\d+)__/g,\n (_match, index: string) => stash[Number(index)]\n );\n};\n\nconst preprocessMarkdown = (content: string): string =>\n withProtectedCode(stripInvisibleChars(content), (safe) =>\n reflowDisplayMath(protectCurrencyInlineMath(safe))\n );\n\n/**\n * MarkdownMathRenderer - Renders Markdown content with embedded LaTeX math.\n *\n * Used for AI-generated questions and resolutions, which arrive as Markdown\n * (`**bold**`, `#### headings`, `* lists`, paragraphs) mixed with LaTeX\n * (`$...$` inline, `$$...$$` display). Built on `react-markdown` with\n * `remark-gfm` (tables/lists), `remark-math` + `rehype-katex` (math).\n *\n * SECURITY: raw HTML is intentionally NOT enabled (no `rehype-raw`). This\n * renderer only receives content classified as Markdown (no HTML tags) by\n * `isLikelyMarkdown`; HTML content keeps flowing through `HtmlMathRenderer`,\n * which sanitizes it. Adding raw-HTML support here would reintroduce an XSS\n * vector and requires a dedicated sanitization pipeline + security review.\n */\nconst MarkdownMathRenderer = forwardRef<\n HTMLDivElement,\n MarkdownMathRendererProps\n>(({ content, className, style, testId }, ref) => {\n const sharedClassName = cn(\n 'leading-relaxed',\n // Paragraph spacing\n '[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0',\n // Headings\n '[&_h1]:text-xl [&_h1]:font-bold [&_h1]:mt-3 [&_h1]:mb-2',\n '[&_h2]:text-lg [&_h2]:font-bold [&_h2]:mt-3 [&_h2]:mb-2',\n '[&_h3]:text-base [&_h3]:font-semibold [&_h3]:mt-2.5 [&_h3]:mb-1.5',\n '[&_h4]:text-base [&_h4]:font-semibold [&_h4]:mt-2.5 [&_h4]:mb-1.5',\n '[&_h5]:font-semibold [&_h6]:font-semibold',\n // Lists\n '[&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-2',\n '[&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:my-2',\n '[&_li]:my-1 [&_li>ul]:my-1 [&_li>ol]:my-1',\n // Text formatting\n '[&_b]:font-bold [&_strong]:font-bold',\n '[&_i]:italic [&_em]:italic',\n '[&_u]:underline',\n '[&_blockquote]:border-l-4 [&_blockquote]:border-border-200 [&_blockquote]:pl-3 [&_blockquote]:text-text-700',\n '[&_code]:rounded [&_code]:bg-background-100 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-sm',\n // Hide the KaTeX MathML accessibility layer visually (still readable to\n // screen readers). Tailwind preflight overrides KaTeX's default clip\n // rule, so without this the MathML layer duplicates every formula as\n // raw text next to the visual render.\n '[&_.katex-mathml]:sr-only',\n // Display math spacing\n '[&_.katex-display]:my-2.5',\n // Tables (GFM)\n '[&_table]:border-collapse [&_table]:w-full [&_table]:my-2.5 [&_table]:table-auto',\n '[&_table_td]:border [&_table_td]:border-border-200 [&_table_td]:p-2 [&_table_td]:min-w-[50px] [&_table_td]:align-top',\n '[&_table_th]:border [&_table_th]:border-border-200 [&_table_th]:p-2 [&_table_th]:min-w-[50px] [&_table_th]:align-top [&_table_th]:bg-background-50 [&_table_th]:font-semibold',\n // Images and links\n '[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-md [&_img]:my-2',\n '[&_a]:text-primary-500 [&_a]:underline [&_a:hover]:text-primary-600',\n className\n );\n\n return (\n <div\n ref={ref}\n className={sharedClassName}\n style={style}\n data-testid={testId}\n >\n <ReactMarkdown\n remarkPlugins={[remarkGfm, remarkMath]}\n rehypePlugins={[rehypeKatex]}\n >\n {preprocessMarkdown(content)}\n </ReactMarkdown>\n </div>\n );\n});\n\nMarkdownMathRenderer.displayName = 'MarkdownMathRenderer';\n\nexport default memo(MarkdownMathRenderer);\n","/**\n * Utilities for processing HTML content with LaTeX math expressions\n */\n\nexport interface MathPart {\n type: 'text' | 'math' | 'block-math';\n content: string;\n latex?: string;\n}\n\n/**\n * Generates a random ID for placeholder uniqueness\n * Uses crypto.randomUUID() (Web Crypto API - available in all modern browsers)\n */\nconst generateSecureRandomId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Cleans LaTeX string from invisible characters and decodes HTML entities\n * that the editor saved in place of math operators.\n *\n * Why decode here: KaTeX is a LaTeX parser, not an HTML parser. If the\n * source has `&lt;`/`&gt;` (because the editor HTML-escaped them on save)\n * KaTeX throws \"Expected 'EOF', got '&'\". `&lt;`/`&gt;` map to the\n * equivalent `\\lt`/`\\gt` commands. `&amp;` decodes back to a bare `&` \\u2014\n * NOT `\\&` \\u2014 because in LaTeX `&` is the alignment character (used by\n * `\\begin{align}`, matrices, etc.); rewriting it to `\\&` would break\n * alignment and stop already-escaped `\\&` from round-tripping.\n */\nexport const cleanLatex = (str: string): string => {\n return str\n .replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '')\n .replaceAll(/&amp;lt;|&lt;/gi, String.raw`\\lt `)\n .replaceAll(/&amp;gt;|&gt;/gi, String.raw`\\gt `)\n .replaceAll(/&amp;amp;|&amp;/gi, '&')\n .trim();\n};\n\n/**\n * Heuristic that flags a string as \"likely real LaTeX math\" vs prose.\n * Used to reject `$...$` blocks that wrap regular text \\u2014 typically\n * happens when authors type `$` as a currency symbol and the renderer\n * pairs unrelated occurrences as math delimiters, sending Portuguese\n * prose to KaTeX (which then renders each letter as a math variable).\n *\n * Approach:\n * - Backslash commands / sub-super / grouping braces \\u2192 definitely math.\n * - Otherwise, treat it as PROSE only when it contains 2+ real words\n * (runs of 3+ letters). A sentence like \"15,00 pelo custo fixo\" has\n * many such words; genuine math \\u2014 `x = 1`, `a + b`, `1 < 2`, `f0`,\n * even a lone `abc` \\u2014 does not. This keeps normal spaced equations\n * rendering while still rejecting currency-`$` prose.\n */\nexport const looksLikeLatex = (str: string): boolean => {\n if (/[\\\\^_{}]/.test(str)) return true;\n const words = str.match(/[a-zA-Z]{3,}/g);\n if (words && words.length >= 2) return false;\n return true;\n};\n\n/**\n * HTML element names the backoffice RichEditor emits. Kept as a Set (instead\n * of a long regex alternation) so the tag-detection regex below stays simple\n * and cheap to reason about.\n */\nconst HTML_TAG_NAMES = new Set([\n 'p',\n 'div',\n 'span',\n 'br',\n 'b',\n 'strong',\n 'i',\n 'em',\n 'u',\n 's',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'a',\n 'img',\n 'table',\n 'thead',\n 'tbody',\n 'tfoot',\n 'tr',\n 'td',\n 'th',\n 'blockquote',\n 'pre',\n 'code',\n 'sub',\n 'sup',\n 'font',\n 'latex',\n]);\n\n/**\n * True when `content` contains a real HTML element tag (open or close) whose\n * name is one the editor produces. A single generic regex captures any\n * `<tag`/`</tag` candidate; membership is then checked against the Set, which\n * keeps the regex trivial and avoids a giant alternation.\n */\nconst containsHtmlTag = (content: string): boolean => {\n for (const match of content.matchAll(/<\\/?([a-z][a-z0-9]*)/gi)) {\n if (HTML_TAG_NAMES.has(match[1].toLowerCase())) return true;\n }\n return false;\n};\n\n/**\n * Heuristic that decides whether `content` is Markdown (as opposed to the\n * HTML the backoffice RichEditor produces). AI-generated questions and\n * resolutions arrive as Markdown + LaTeX (`**bold**`, `#### heading`,\n * `* lists`, `$...$`, `$$...$$`), whereas the editor stores HTML (`<p>`,\n * `<b>`, `<span class=\"math-formula\" data-latex=\"...\">`). The two sources are\n * cleanly separable, so we bias toward the proven HTML path:\n *\n * - If any real HTML element tag is present we return `false` and let\n * `HtmlMathRenderer` handle it exactly as before (math-formula spans,\n * currency heuristic, katex-error recovery, sanitization).\n * - Otherwise we return `true` only when an unmistakable Markdown marker is\n * present (ATX heading, bold, bullet/ordered list, GFM table, or a paragraph\n * break), which is what breaks today: Markdown rendered as HTML shows the raw\n * `**`/`####`/`*` tokens and collapses line breaks.\n *\n * Regex note: line-anchored signals use the `m` flag with `^` and restrict\n * horizontal whitespace to `[ \\t]` (never `\\s`, which also matches `\\n`). This\n * keeps each quantifier on a single line and avoids the super-linear\n * backtracking that `(?:^|\\n)\\s*…\\s+` can trigger across newlines.\n */\nexport const isLikelyMarkdown = (content: string): boolean => {\n if (!content) return false;\n\n // Presence of real HTML element tags => treat as HTML (RichEditor output).\n if (containsHtmlTag(content)) return false;\n\n const MARKDOWN_SIGNALS = [\n /^#{1,6}[ \\t]+\\S/m, // ATX heading (#, ##, ... ######)\n /\\*\\*[^*\\n]+\\*\\*/, // bold\n /__[^_\\n]+__/, // bold (underscore)\n /^[ \\t]*[-*+][ \\t]+\\S/m, // bullet list\n /^[ \\t]*\\d+\\.[ \\t]+\\S/m, // ordered list\n /\\|[ \\t]*:?-+:?[ \\t]*\\|/, // GFM table delimiter row (|---|---|)\n /\\n\\n/, // paragraph break (only reached when no HTML tag is present)\n ];\n return MARKDOWN_SIGNALS.some((pattern) => pattern.test(content));\n};\n\n/**\n * Recovers usable LaTeX source from `<span class=\"katex-error\">` wrappers\n * that previous editor cycles persisted into the database. The error's\n * `title` attribute carries the original LaTeX after \"at position N: \".\n * Replaces each wrapper with `$LATEX$` so downstream patterns can render\n * it cleanly via KaTeX.\n *\n * No-op outside browser contexts (no `document`).\n */\nconst recoverFromKatexErrorSpans = (htmlContent: string): string => {\n if (\n typeof document === 'undefined' ||\n !/class=\"[^\"]*katex-error/i.test(htmlContent)\n ) {\n return htmlContent;\n }\n\n const tempContainer = document.createElement('div');\n tempContainer.innerHTML = htmlContent;\n\n const sanitizeRecoveredLatex = (raw: string): string =>\n raw\n // Strip combining marks KaTeX puts in error titles at the error pos\n .replaceAll(/[\\u0300-\\u036F]/g, '')\n // Drop truncated tag markers leftover from broken serialization.\n // Done while literal `<`/`>` are still present (before they get\n // mapped to LaTeX commands below).\n .replaceAll(/<\\/?[a-zA-Z][a-zA-Z0-9]*\\s*>?$/g, '')\n // Map comparison operators to equivalent LaTeX commands. We match\n // both the entity forms AND the literal `<`/`>` characters: the\n // `title` attribute is entity-decoded by the DOM when read, so we\n // often get literal `<`/`>`. Mapping them to `\\lt`/`\\gt` also avoids\n // re-encoding to `&lt;`/`&gt;` on DOM serialization (which would make\n // `looksLikeLatex` reject the recovered block as non-math).\n // `&amp;` decodes to a bare `&` (the LaTeX alignment character), not\n // `\\&`, so `\\begin{align}` environments survive recovery.\n .replaceAll(/&amp;lt;|&lt;|</gi, String.raw`\\lt `)\n .replaceAll(/&amp;gt;|&gt;|>/gi, String.raw`\\gt `)\n .replaceAll(/&amp;amp;|&amp;/gi, '&')\n .trim();\n\n Array.from(tempContainer.querySelectorAll('.katex-error')).forEach(\n (errorNode) => {\n if (!tempContainer.contains(errorNode)) return;\n\n const title = errorNode.getAttribute('title') || '';\n // Note: no `\\s*` before the capture group — `\\s*(.+)` would let\n // whitespace be split ambiguously between the two, causing\n // super-linear backtracking (ReDoS). The capture is trimmed by\n // sanitizeRecoveredLatex below instead.\n const positionMatch = /at position\\s+\\d+:(.+)$/.exec(title);\n let recovered = positionMatch ? positionMatch[1] : '';\n\n if (!recovered) {\n // Fallback: collect from inner <annotation> elements, skipping the\n // visual `katex-html` layer to avoid duplicating Unicode glyphs.\n const parts: string[] = [];\n const walk = (n: Node) => {\n if (n.nodeType === Node.TEXT_NODE) {\n parts.push(n.textContent || '');\n return;\n }\n if (n.nodeType !== Node.ELEMENT_NODE) return;\n const el = n as Element;\n if (el.tagName.toLowerCase() === 'annotation') {\n parts.push(el.textContent || '');\n return;\n }\n if (el.classList.contains('katex-html')) return;\n Array.from(el.childNodes).forEach(walk);\n };\n Array.from(errorNode.childNodes).forEach(walk);\n recovered = parts.join(' ');\n }\n\n recovered = sanitizeRecoveredLatex(recovered);\n if (recovered) {\n errorNode.replaceWith(document.createTextNode(`$${recovered}$`));\n } else {\n errorNode.remove();\n }\n }\n );\n\n return tempContainer.innerHTML;\n};\n\n/**\n * Decodes `\\$` escape sequences to literal `$` characters. Applied only to\n * text fragments outside math blocks \\u2014 the `(?<!\\\\)` lookbehind in the\n * `$...$` matcher already skipped these, but the `\\` is still in the output\n * unless we decode it. Without this, currency strings like `R\\$ 130,00`\n * render with a visible backslash.\n */\nconst decodeDollarEscapes = (text: string): string =>\n text.replaceAll(String.raw`\\$`, '$');\n\n/**\n * Dangerous attributes that should be removed for XSS protection\n */\nconst DANGEROUS_ATTRIBUTES = new Set([\n 'contenteditable',\n 'srcdoc',\n 'formaction',\n 'xlink:href',\n]);\n\n/**\n * Dangerous URI schemes that should be removed from href/src attributes\n */\nconst DANGEROUS_URI_PATTERN = /^\\s*(javascript|vbscript|data):/i;\n\n/**\n * Sanitizes HTML content for safe display\n * Removes event handlers, dangerous attributes, script/style tags, and javascript: URIs\n */\nexport const sanitizeHtmlForDisplay = (htmlContent: string): string => {\n if (!htmlContent) return htmlContent;\n\n // Create a temporary div to parse HTML\n if (typeof document === 'undefined') {\n // Server-side: use regex-based sanitization as fallback\n let sanitized = htmlContent;\n // Remove script tags\n sanitized = sanitized.replaceAll(\n /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n ''\n );\n // Remove style tags\n sanitized = sanitized.replaceAll(\n /<style\\b[^<]*(?:(?!<\\/style>)<[^<]*)*<\\/style>/gi,\n ''\n );\n // Remove on* event handlers (split into separate patterns to avoid ReDoS)\n sanitized = sanitized.replaceAll(/ on[a-z]+=\"[^\"]*\"/gi, '');\n sanitized = sanitized.replaceAll(/ on[a-z]+='[^']*'/gi, '');\n sanitized = sanitized.replaceAll(/ on[a-z]+=[^\\s>\"']+/gi, '');\n // Remove dangerous URI schemes (javascript, vbscript, data) - matching client-side DANGEROUS_URI_PATTERN\n sanitized = sanitized.replaceAll(\n / href=\"(?:javascript|vbscript|data):[^\"]*\"/gi,\n ''\n );\n sanitized = sanitized.replaceAll(\n / href='(?:javascript|vbscript|data):[^']*'/gi,\n ''\n );\n sanitized = sanitized.replaceAll(\n / src=\"(?:javascript|vbscript|data):[^\"]*\"/gi,\n ''\n );\n sanitized = sanitized.replaceAll(\n / src='(?:javascript|vbscript|data):[^']*'/gi,\n ''\n );\n sanitized = sanitized.replaceAll(\n / action=\"(?:javascript|vbscript|data):[^\"]*\"/gi,\n ''\n );\n sanitized = sanitized.replaceAll(\n / action='(?:javascript|vbscript|data):[^']*'/gi,\n ''\n );\n return sanitized;\n }\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = htmlContent;\n\n // Remove script and style tags entirely\n const dangerousTags = tempDiv.querySelectorAll(\n 'script, style, iframe, object, embed'\n );\n dangerousTags.forEach((element) => element.remove());\n\n // Process all elements\n const allElements = tempDiv.querySelectorAll('*');\n allElements.forEach((element) => {\n // Get all attribute names\n const attributeNames = element.getAttributeNames();\n\n attributeNames.forEach((attrName) => {\n const lowerAttrName = attrName.toLowerCase();\n\n // Remove all on* event handler attributes (onclick, onerror, onload, etc.)\n if (lowerAttrName.startsWith('on')) {\n element.removeAttribute(attrName);\n return;\n }\n\n // Remove dangerous attributes\n if (DANGEROUS_ATTRIBUTES.has(lowerAttrName)) {\n element.removeAttribute(attrName);\n return;\n }\n\n // Check href, src, and action for dangerous URIs\n if (\n lowerAttrName === 'href' ||\n lowerAttrName === 'src' ||\n lowerAttrName === 'action'\n ) {\n const value = element.getAttribute(attrName);\n if (value && DANGEROUS_URI_PATTERN.test(value)) {\n element.removeAttribute(attrName);\n }\n }\n });\n });\n\n return tempDiv.innerHTML;\n};\n\n/**\n * Processes HTML content and extracts math expressions\n * Returns an array of parts (text and math) for rendering\n */\nexport const processHtmlWithMath = (htmlContent: string): MathPart[] => {\n if (!htmlContent) return [];\n\n // Pre-pass: recover original LaTeX from any `katex-error` wrappers that\n // older editor saves left in the content. This turns persisted error HTML\n // into clean `$LATEX$` strings so the steps below can render them.\n let processedContent = recoverFromKatexErrorSpans(htmlContent);\n const parts: MathPart[] = [];\n\n // Generate unique sentinel per call to avoid collision with content\n const sentinel = `__MATH_${generateSecureRandomId()}_`;\n\n // Step 1: Handle math-formula spans (from the editor)\n const mathFormulaPattern =\n /<span[^>]*class=\"math-formula\"[^>]*data-latex=\"([^\"]*)\"[^>]*>[\\s\\S]*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n mathFormulaPattern,\n (match, latex) => {\n const isDisplayMode = match.includes('data-display-mode=\"true\"');\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: isDisplayMode ? 'block-math' : 'math',\n content: match,\n latex: cleanLatex(latex),\n });\n return placeholder;\n }\n );\n\n // Step 2: Handle wrapped math expressions (from math modal - legacy)\n const wrappedMathPattern =\n /<span[^>]*class=\"math-expression\"[^>]*data-math=\"([^\"]*)\"[^>]*>.*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n wrappedMathPattern,\n (match, latex) => {\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: 'math',\n content: match,\n latex: cleanLatex(latex),\n });\n return placeholder;\n }\n );\n\n // Step 3: Handle raw $$...$$ expressions (display mode) - BEFORE single $\n const doubleDollarPattern = /(?<!\\\\)\\$\\$([\\s\\S]+?)\\$\\$/g;\n processedContent = processedContent.replaceAll(\n doubleDollarPattern,\n (match, latex) => {\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: 'block-math',\n content: match,\n latex: cleanLatex(latex),\n });\n return placeholder;\n }\n );\n\n // Step 4: Handle single $...$ expressions for inline math.\n // Skip matches whose content doesn't look like LaTeX — those are usually\n // currency `$` symbols pairing up across prose (e.g. `R$ 15,00 ... R$ 42,00`)\n // and sending Portuguese text to KaTeX produces gibberish output.\n const singleDollarPattern = /(?<!\\\\)\\$([\\s\\S]+?)\\$/g;\n processedContent = processedContent.replaceAll(\n singleDollarPattern,\n (match, latex) => {\n if (!looksLikeLatex(latex)) return match;\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: 'math',\n content: match,\n latex: cleanLatex(latex),\n });\n return placeholder;\n }\n );\n\n // Step 5: Handle <latex>...</latex> tags for inline math\n const latexTagPattern =\n /(?:<latex>|&lt;latex&gt;)([\\s\\S]*?)(?:<\\/latex>|&lt;\\/latex&gt;)/g;\n processedContent = processedContent.replaceAll(\n latexTagPattern,\n (match, latex) => {\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: 'math',\n content: match,\n latex: cleanLatex(latex),\n });\n return placeholder;\n }\n );\n\n // Step 6: Handle standalone LaTeX environments (align, equation, pmatrix, etc.)\n const latexEnvPattern = /\\\\begin\\{([^}]+)\\}([\\s\\S]*?)\\\\end\\{\\1\\}/g;\n processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {\n const placeholder = `${sentinel}${parts.length}__`;\n parts.push({\n type: 'block-math',\n content: match,\n latex: cleanLatex(match),\n });\n return placeholder;\n });\n\n // Step 7: Split remaining content by placeholders\n const finalParts: MathPart[] = [];\n let currentIndex = 0;\n // Escape sentinel for regex (though it should be safe alphanumeric)\n const escapedSentinel = sentinel.replaceAll(\n /[.*+?^${}()|[\\]\\\\]/g,\n String.raw`\\$&`\n );\n const placeholderPattern = new RegExp(\n String.raw`${escapedSentinel}(\\d+)__`,\n 'g'\n );\n let match;\n\n while ((match = placeholderPattern.exec(processedContent)) !== null) {\n // Add text before math\n if (match.index > currentIndex) {\n finalParts.push({\n type: 'text',\n content: decodeDollarEscapes(\n processedContent.slice(currentIndex, match.index)\n ),\n });\n }\n\n // Add math expression\n const mathIndex = Number.parseInt(match[1], 10);\n if (parts[mathIndex]) {\n finalParts.push(parts[mathIndex]);\n }\n\n currentIndex = match.index + match[0].length;\n }\n\n // Add remaining text\n if (currentIndex < processedContent.length) {\n finalParts.push({\n type: 'text',\n content: decodeDollarEscapes(processedContent.slice(currentIndex)),\n });\n }\n\n return finalParts;\n};\n\n/**\n * Checks if content contains any math expressions\n */\nexport const containsMath = (content: string): boolean => {\n if (!content) return false;\n\n // Check for various math patterns\n const patterns = [\n /\\$\\$[\\s\\S]+?\\$\\$/, // Display mode $$...$$\n /(?<!\\\\)\\$[\\s\\S]+?\\$/, // Inline mode $...$\n /<span[^>]*class=\"math-formula\"/, // Editor spans\n /<span[^>]*class=\"math-expression\"/, // Legacy spans\n /<latex>|&lt;latex&gt;/, // LaTeX tags\n /\\\\begin\\{[^}]+\\}/, // LaTeX environments\n ];\n\n return patterns.some((pattern) => pattern.test(content));\n};\n\n/**\n * Extracts plain text from HTML content (removes all tags and LaTeX notation)\n */\nexport const stripHtml = (htmlContent: string): string => {\n if (!htmlContent) return '';\n\n let content = htmlContent;\n\n // Remove math-formula spans (keep nothing as the LaTeX is in data attribute)\n content = content.replaceAll(\n /<span[^>]*class=\"math-formula\"[^>]*>[\\s\\S]*?<\\/span>/g,\n ''\n );\n\n // Remove math-expression spans (legacy)\n content = content.replaceAll(\n /<span[^>]*class=\"math-expression\"[^>]*>[\\s\\S]*?<\\/span>/g,\n ''\n );\n\n // Remove $$...$$ block math\n content = content.replaceAll(/\\$\\$[\\s\\S]+?\\$\\$/g, '');\n\n // Remove $...$ inline math\n content = content.replaceAll(/\\$[^$]+\\$/g, '');\n\n // Remove <latex>...</latex> tags\n content = content.replaceAll(\n /(?:<latex>|&lt;latex&gt;)[\\s\\S]*?(?:<\\/latex>|&lt;\\/latex&gt;)/g,\n ''\n );\n\n // Remove LaTeX environments like \\begin{...}...\\end{...}\n // Using non-greedy match without backreference to avoid ReDoS vulnerability\n content = content.replaceAll(\n /\\\\begin\\{[a-zA-Z*]+\\}[\\s\\S]*?\\\\end\\{[a-zA-Z*]+\\}/g,\n ''\n );\n\n // Remove HTML tags\n if (typeof document === 'undefined') {\n // Server-side: use regex (excluding both < and > prevents quadratic backtracking)\n return content.replaceAll(/<[^<>]*>/g, '').trim();\n }\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = content;\n return (tempDiv.textContent || tempDiv.innerText || '').trim();\n};\n"],"mappings":"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","names":["forwardRef","memo","match","jsx","forwardRef","memo"]}