11import katex from 'katex' ;
2+ import { marked } from 'marked' ;
3+
4+ /**
5+ * Keep generated math HTML (including its multiline TeX annotation) opaque to
6+ * Markdown, then restore it before the caller sanitizes the complete HTML.
7+ */
8+ export function renderMarkdownWithLatex ( text : string ) : string {
9+ let prefix = 'CODEVERMATH' ;
10+ while ( text ?. includes ( prefix ) ) {
11+ prefix += 'X' ;
12+ }
13+
14+ const expressions : string [ ] = [ ] ;
15+ const protectedMarkdown = replaceLatex ( text , ( latex , displayMode ) => {
16+ const index = expressions . push ( renderKatex ( latex , displayMode ) ) - 1 ;
17+ return `${ prefix } ${ index } END` ;
18+ } ) ;
19+ const html = marked . parse ( protectedMarkdown ) ;
20+ return html . replace (
21+ new RegExp ( `${ prefix } (\\d+)END` , 'g' ) ,
22+ ( _match , index ) => expressions [ Number ( index ) ]
23+ ) ;
24+ }
225
326/**
427 * Pre-processes a Markdown/text string and replaces LaTeX math delimiters
5- * with KaTeX-rendered HTML **before** the string is passed to `marked`.
28+ * with KaTeX-rendered HTML. For Markdown, use renderMarkdownWithLatex instead
29+ * so Markdown syntax inside math cannot corrupt the generated HTML.
630 *
731 * Supports:
832 * - Display math: $$...$$ and \[...\]
@@ -12,6 +36,13 @@ import katex from 'katex';
1236 * so that dollar signs inside code are not treated as math delimiters.
1337 */
1438export function renderLatex ( text : string ) : string {
39+ return replaceLatex ( text , renderKatex ) ;
40+ }
41+
42+ function replaceLatex (
43+ text : string ,
44+ renderMath : ( latex : string , displayMode : boolean ) => string
45+ ) : string {
1546 if ( ! text ) {
1647 return text ;
1748 }
@@ -32,17 +63,17 @@ export function renderLatex(text: string): string {
3263
3364 // Display math: $$...$$ (must come before inline $ to avoid conflicts)
3465 segment = segment . replace ( / \$ \$ ( [ \s \S ] + ?) \$ \$ / g, ( _match , math ) => {
35- return renderKatex ( math . trim ( ) , true ) ;
66+ return renderMath ( math . trim ( ) , true ) ;
3667 } ) ;
3768
3869 // Display math: \[...\]
3970 segment = segment . replace ( / \\ \[ ( [ \s \S ] + ?) \\ \] / g, ( _match , math ) => {
40- return renderKatex ( math . trim ( ) , true ) ;
71+ return renderMath ( math . trim ( ) , true ) ;
4172 } ) ;
4273
4374 // Inline math: \(...\)
4475 segment = segment . replace ( / \\ \( ( [ \s \S ] + ?) \\ \) / g, ( _match , math ) => {
45- return renderKatex ( math . trim ( ) , false ) ;
76+ return renderMath ( math . trim ( ) , false ) ;
4677 } ) ;
4778
4879 // Inline math: $...$
@@ -58,7 +89,7 @@ export function renderLatex(text: string): string {
5889 segment = segment . replace (
5990 / (?< ! [ \\ $ ] ) \$ (? ! \$ ) ( \S (?: [ ^ $ \\ \n ] | \\ .) * ?\S | \S ) \$ (? ! \d ) / g,
6091 ( match , math ) => {
61- return hasTexSignal ( math ) ? renderKatex ( math . trim ( ) , false ) : match ;
92+ return hasTexSignal ( math ) ? renderMath ( math . trim ( ) , false ) : match ;
6293 }
6394 ) ;
6495
@@ -104,4 +135,3 @@ function renderKatex(latex: string, displayMode: boolean): string {
104135 return `<code class="katex-error" title="KaTeX parse error">${ escaped } </code>` ;
105136 }
106137}
107-
0 commit comments