diff --git a/static/ui.js b/static/ui.js index 8eed5ab7..a7128bfc 100644 --- a/static/ui.js +++ b/static/ui.js @@ -81,12 +81,10 @@ function _renderUserFencedBlocks(text){ return `${esc(item.src)}`; }); let s=String(text||''); - // Stash math before escaping plain text; display delimiters must run before inline. - s=s.replace(/\$\$([\s\S]+?)\$\$/g,(_,m)=>stashMath('display',m)); - s=s.replace(/\\\[([\s\S]+?)\\\]/g,(_,m)=>stashMath('display',m)); - s=s.replace(/\$([^\s$\n][^$\n]*?[^\s$\n]|\S)\$/g,(_,m)=>stashMath('inline',m)); - s=s.replace(/\\\((.+?)\\\)/g,(_,m)=>stashMath('inline',m)); - // Extract fenced code blocks → stash, replace with null-token placeholder + // Extract fenced code blocks FIRST so math regexes never run inside fenced + // content. If math were stashed first, a user-typed code block containing + // \[..\] / \(..\) / $$..$$ would be rendered as a KaTeX block inside + //
 instead of as literal source. Mirrors renderMd()'s ordering.
   // CommonMark §4.5 line-anchored fence: the closing run must use at least
   // as many backticks as the opener, so inner triple-backtick fences remain content.
   s=s.replace(/(^|\n)[ ]{0,3}(`{3,})([^\n`]*)\n(?:([\s\S]*?)\n)?[ ]{0,3}\2`*[ \t]*(?=\n|$)/g,(_,lead,_fence,info,code)=>{
@@ -111,6 +109,12 @@ function _renderUserFencedBlocks(text){
     }
     return lead+'\x00UF'+(stash.length-1)+'\x00';
   });
+  // Now stash math from the OUTSIDE-of-fence text. Display delimiters must
+  // run before inline so $$..$$ isn't mis-parsed as $..$..$..$.
+  s=s.replace(/\$\$([\s\S]+?)\$\$/g,(_,m)=>stashMath('display',m));
+  s=s.replace(/\\\[([\s\S]+?)\\\]/g,(_,m)=>stashMath('display',m));
+  s=s.replace(/\$([^\s$\n][^$\n]*?[^\s$\n]|\S)\$/g,(_,m)=>stashMath('inline',m));
+  s=s.replace(/\\\((.+?)\\\)/g,(_,m)=>stashMath('inline',m));
   // Escape remaining plain text and convert newlines to 
s=esc(s).replace(/\n/g,'
'); // Restore stashed code blocks, then math placeholders as KaTeX targets. diff --git a/tests/test_issue347.py b/tests/test_issue347.py index 756bcd20..4513139d 100644 --- a/tests/test_issue347.py +++ b/tests/test_issue347.py @@ -115,6 +115,41 @@ where \\(L_i(f)\\) = SPL at angle \\(i\\).""" assert "\\(" not in html and "\\)" not in html, html +def test_user_code_block_with_latex_syntax_renders_as_literal_code(): + """User-bubble code blocks containing \\[..\\] / \\(..\\) / $$..$$ must + render as literal code source, not as KaTeX. _renderUserFencedBlocks + must stash code fences BEFORE math (mirroring renderMd's ordering); if + math is stashed first, a user-typed code block containing LaTeX-like + syntax gets a `
` placeholder dropped INSIDE + `
`, and the user's literal source is silently replaced by
+    rendered math.
+    """
+    sample = "```\n\\[ a + b \\] is wrong\n\\(L_i\\) too\n$$matrix$$\n```"
+    rendered = _run_renderers(sample)
+    user_html = rendered["user"]
+    # The whole code block should remain literal, no KaTeX wrappers inside.
+    assert "
" in user_html, user_html
+    assert "katex-block" not in user_html, user_html
+    assert "katex-inline" not in user_html, user_html
+    # Backslashes survive HTML escape unchanged; the user's source is intact.
+    assert "\\[ a + b \\]" in user_html, user_html
+    assert "\\(L_i\\)" in user_html, user_html
+    assert "$$matrix$$" in user_html, user_html
+
+
+def test_user_bubble_top_level_latex_still_renders_after_fence_reorder():
+    """Sibling regression: top-level math (outside any code fence) must
+    still render through KaTeX in user bubbles after the fence-first
+    reorder. Guards against an over-correction that disables user-bubble
+    math rendering entirely.
+    """
+    sample = "math: \\[ x + y \\]\n\nand inline \\(L_i\\)"
+    rendered = _run_renderers(sample)
+    user_html = rendered["user"]
+    assert 'class="katex-block" data-katex="display"' in user_html, user_html
+    assert 'class="katex-inline" data-katex="inline"' in user_html, user_html
+
+
 def test_katex_inline_placeholder_emitted():
     """renderMd restore pass must emit .katex-inline spans for inline math."""
     assert 'katex-inline' in UI_JS, \