From b571c7ddc1429f5d489ec6e973c899f23c65124d Mon Sep 17 00:00:00 2001 From: mashallow <35926768+TuanKietTran@users.noreply.github.com> Date: Sat, 11 Jul 2026 20:45:57 +0700 Subject: [PATCH] fix(markdown): stop currency dollars rendering as KaTeX inline math (#5132) --- static/js/markdown.js | 7 +++-- tests/test_markdown_rendering_js.py | 45 +++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/static/js/markdown.js b/static/js/markdown.js index 439206fcb..8735b83e7 100644 --- a/static/js/markdown.js +++ b/static/js/markdown.js @@ -661,8 +661,11 @@ export function mdToHtml(src, opts) { return placeholder; } catch (e) { return match; } }); - // Inline math: $...$ (not preceded/followed by $ or digit, not spanning multiple lines) - s = s.replace(/(? { + // Inline math: $...$ — single line only, and Pandoc-style delimiter rules so + // currency doesn't render as math ("$5 to $10"): the opening $ must be + // immediately followed by a non-space, the closing $ must be immediately + // preceded by a non-space and not followed by a digit. + s = s.replace(/(? { try { const raw = math.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); const placeholder = `___MATH_BLOCK_${mathBlocks.length}___`; diff --git a/tests/test_markdown_rendering_js.py b/tests/test_markdown_rendering_js.py index e0f493eff..2ffe8914f 100644 --- a/tests/test_markdown_rendering_js.py +++ b/tests/test_markdown_rendering_js.py @@ -18,12 +18,24 @@ def node_available(): pytest.skip("node binary not on PATH") -def _run_markdown_case(markdown: str, render_expr: str = "mod.mdToHtml(input)"): +def _run_markdown_case(markdown: str, render_expr: str = "mod.mdToHtml(input)", with_katex: bool = False): script = textwrap.dedent( r""" import fs from 'node:fs'; globalThis.window = { location: { origin: 'http://localhost' }, katex: null }; + if (__WITH_KATEX__) { + // Minimal stand-in for the CDN katex global: wraps the source so tests + // can assert what was (or wasn't) handed to KaTeX. + const katexStub = { + renderToString(src, opts) { + const display = !!(opts && opts.displayMode); + return `${src}`; + }, + }; + globalThis.window.katex = katexStub; + globalThis.katex = katexStub; + } globalThis.document = { readyState: 'loading', addEventListener() {}, @@ -77,7 +89,9 @@ def _run_markdown_case(markdown: str, render_expr: str = "mod.mdToHtml(input)"): const input = JSON.parse(process.argv[1]); console.log(JSON.stringify({ html: __RENDER_EXPR__ })); """ - ).replace("__RENDER_EXPR__", render_expr) + ).replace("__RENDER_EXPR__", render_expr).replace( + "__WITH_KATEX__", "true" if with_katex else "false" + ) result = subprocess.run( ["node", "--input-type=module", "-e", script, json.dumps(markdown)], cwd=_REPO, @@ -200,6 +214,33 @@ def test_inline_code_content_is_html_escaped(node_available): assert "" not in html +def test_currency_dollar_amounts_are_not_rendered_as_math(node_available): + # "$5 to $10" used to pair the two dollar signs as inline-math delimiters + # and render "5 to" through KaTeX. Pandoc-style rules now reject it: the + # closing $ is preceded by a space and followed by a digit. + html = _run_markdown_case( + "The price rose from $5 to $10 overnight.", with_katex=True + ) + + assert 'class="katex"' not in html + assert "$5" in html + assert "$10" in html + + +def test_inline_math_still_renders_through_katex(node_available): + html = _run_markdown_case("Pythagoras: $x^2 + y^2 = z^2$ holds.", with_katex=True) + + assert 'x^2 + y^2 = z^2' in html + assert "$" not in html + + +def test_display_math_still_renders_through_katex(node_available): + html = _run_markdown_case("$$\\frac{a}{b}$$", with_katex=True) + + assert 'data-display="true"' in html + assert "$$" not in html + + def test_dotted_python_import_paths_are_not_autolinked(node_available): html = _run_markdown_case( "from imblearn.combine import SMOTETomek\n"