@@ -3,41 +3,88 @@ import katex from 'katex';
33/**
44 * Process a DOM container to replace <img class="latex"> elements
55 * with KaTeX-rendered math. Leaves non-latex images (diagrams, figures) untouched.
6+ * Also hides [asy] Asymptote source blocks that can't be rendered client-side.
67 *
78 * The AoPS-scraped HTML stores LaTeX source in the `alt` attribute
8- * of images with class="latex", e.g.:
9- * <img class="latex" alt="$x^2 + 1$" src="...latex.artofproblemsolving.com/..." />
9+ * of images with class="latex". Supported delimiters:
10+ * $...$ inline math
11+ * $$...$$ display math
12+ * \[...\] display math
13+ * \(...\) inline math
14+ * bare text treated as inline math
1015 */
16+
17+ function stripDelimiters ( alt : string ) : { latex : string ; displayMode : boolean } {
18+ let latex = alt . trim ( ) ;
19+ let displayMode = false ;
20+
21+ if ( latex . startsWith ( '$$' ) && latex . endsWith ( '$$' ) ) {
22+ latex = latex . slice ( 2 , - 2 ) ;
23+ displayMode = true ;
24+ } else if ( latex . startsWith ( '\\[' ) && latex . endsWith ( '\\]' ) ) {
25+ latex = latex . slice ( 2 , - 2 ) ;
26+ displayMode = true ;
27+ } else if ( latex . startsWith ( '\\(' ) && latex . endsWith ( '\\)' ) ) {
28+ latex = latex . slice ( 2 , - 2 ) ;
29+ } else if ( latex . startsWith ( '$' ) && latex . endsWith ( '$' ) ) {
30+ latex = latex . slice ( 1 , - 1 ) ;
31+ }
32+
33+ return { latex, displayMode } ;
34+ }
35+
1136export function renderLatexInContainer ( container : HTMLElement ) : void {
37+ // Replace <img class="latex"> with KaTeX
1238 const images = container . querySelectorAll < HTMLImageElement > ( 'img.latex' ) ;
1339
1440 for ( const img of images ) {
1541 const alt = img . getAttribute ( 'alt' ) ;
1642 if ( ! alt ) continue ;
1743
18- // Strip surrounding $ or $$ delimiters
19- let latex = alt ;
20- let displayMode = false ;
21-
22- if ( latex . startsWith ( '$$' ) && latex . endsWith ( '$$' ) ) {
23- latex = latex . slice ( 2 , - 2 ) ;
24- displayMode = true ;
25- } else if ( latex . startsWith ( '$' ) && latex . endsWith ( '$' ) ) {
26- latex = latex . slice ( 1 , - 1 ) ;
27- }
44+ const { latex, displayMode } = stripDelimiters ( alt ) ;
2845
2946 try {
3047 const rendered = katex . renderToString ( latex , {
3148 throwOnError : false ,
3249 displayMode,
3350 output : 'html' ,
3451 } ) ;
35- const span = document . createElement ( 'span' ) ;
36- span . innerHTML = rendered ;
37- span . className = 'katex-rendered' ;
38- img . replaceWith ( span ) ;
52+
53+ if ( displayMode ) {
54+ const div = document . createElement ( 'div' ) ;
55+ div . innerHTML = rendered ;
56+ div . className = 'katex-rendered katex-display-block' ;
57+ img . replaceWith ( div ) ;
58+ } else {
59+ const span = document . createElement ( 'span' ) ;
60+ span . innerHTML = rendered ;
61+ span . className = 'katex-rendered' ;
62+ img . replaceWith ( span ) ;
63+ }
3964 } catch {
4065 // If KaTeX fails, leave the original image in place
4166 }
4267 }
68+
69+ // Hide [asy] Asymptote source code blocks (can't render client-side)
70+ // These appear as raw text in <p> elements
71+ const walker = document . createTreeWalker ( container , NodeFilter . SHOW_TEXT ) ;
72+ const asyNodes : Text [ ] = [ ] ;
73+ let node : Text | null ;
74+ while ( ( node = walker . nextNode ( ) as Text | null ) ) {
75+ if ( node . textContent ?. includes ( '[asy]' ) ) {
76+ asyNodes . push ( node ) ;
77+ }
78+ }
79+
80+ for ( const textNode of asyNodes ) {
81+ const parent = textNode . parentElement ;
82+ if ( ! parent ) continue ;
83+ const html = parent . innerHTML ;
84+ // Remove everything between [asy] and [/asy]
85+ const cleaned = html . replace ( / \[ a s y \] [ \s \S ] * ?\[ \/ a s y \] / g, '<em style="color: #999;">[Diagram]</em>' ) ;
86+ if ( cleaned !== html ) {
87+ parent . innerHTML = cleaned ;
88+ }
89+ }
4390}
0 commit comments