gohugoio / hugo-goldmark-extensions

Work in progress.
Apache License 2.0
19 stars 6 forks source link

passthrough: Use of $ as currency symbol #3

Closed jmooring closed 10 months ago

jmooring commented 10 months ago

This works on GitHub, GitLab, and in VS Code markdown previews:

markdown:

The price is $10.00, but members get a discount. The final price is $p' = p(1-d)$.

rendered by GitHub:

The price is $10.00, but members get a discount. The final price is $p' = p(1-d)$.


But with MathJax or KaTeX we render this to:

image

Workaround A

Disable the single $ inline delimiter, and use the parentheses delimiters instead:

[markup.goldmark.extensions.passthrough.delimiters]
block = [['$$', '$$'], ['\[', '\]']]
inline = [['\(', '\)']]
The price is $10.00, but members get a discount. The final price is \(p' = p(1-d)\).

This works with both KaTeX and MathJax.

Workaround B

Double-escape the currency symbol. This works with MathJax but fails with KaTeX.

The price is \\$10.00, but members get a discount. The final price is $p' = p(1-d)$.

FYI

GitLab renders with KaTeX, while GitHub renders with MathJax. MathJax v2.7 was a lot slower than KaTeX, but MathJax v3 is fast. A compelling comparison: https://www.intmath.com/cg5/katex-mathjax-comparison.php

j2kun commented 10 months ago

I had a test for escaping a dollar-sign (or any single-byte-delimiter fence) here: https://github.com/gohugoio/hugo-goldmark-extensions/blob/main/passthrough/passthrough_test.go#L163-L169

Is an escape sufficient? It's unclear to me how the example you posted above could be parsed correctly, even on GitHub, except by accident or heuristic. I figure the best solution is an explicit escape.

jmooring commented 10 months ago

GitHub, GitLab, and VS Code must be doing something different to handle this. I've tried variations, so it doesn't seem accidental.

Neither MathJax nor KaTeX handle it correctly without escaping the first currency symbol, which means my "gold standard" of using a shortcode behaves the same way (i.e., it's broken too).

Is an escape sufficient?

For now, yes, but at some point it would be nice to figure out how GitHub et. al. are handing this.


Looking forward...

In an ideal setup, we'd have render hooks for both inline and block passthrough snippets. In the render hooks we would set a .Page.Store value to trigger JS inclusion[^1] for the page, instead of including the JS for the entire site. That would mean you'd only have to remember to escape $ on pages with passthrough snippets.

Without the render hooks, we're back to a front matter boolean to trigger the JS inclusion, or remember to escape the $ on every page (which isn't great on a large or multi-author site).

When using a math shortcode, you can use the .Page.HasShortcode method to detect if the math shortcode has been used on the page, triggering JS inclusion if present. When using a math code block render hook, you set a .Page.Store value as described above.

[^1]: For example: https://gohugo.io/content-management/diagrams/#mermaid-diagrams

bep commented 10 months ago

The price is $10.00, but members get a discount. The final price is $p' = p(1-d)$.

My guess is they're handling the dollar amount (e.g. $10.00 $100) construct as a special case.

jmooring commented 10 months ago

It looks like it's just a white space thing.

$x $x$ → $x $x$ $x$ $x → $x$ $x

x$ $x$ → x$ $x$ $x$ x$ → $x$ x$

$x$x$ → $x$x$ $x$x$x$ → $x$x$x$

$x$ $x$ → $x$ $x$

j2kun commented 10 months ago

I thought the rule was: the starting $ must not have whitespace after it, and an ending $ must not have whitespace before it. But this one renders as mathmode unless something follows it

$x $ -> $x $ $x $y -> $x $y $x $ y -> $x $ y

jmooring commented 10 months ago

I've changed this issue's label from bug to enhancement, and I'm also closing it.

Neither MathJax nor KaTeX handle it correctly without escaping the first currency symbol, which means my "gold standard" of using a shortcode behaves the same way.

For example, this is broken too:

The price is $10.00, but members get a discount. The final price is {{< math >}}$p' = p(1-d)${{< /math >}}.

Given the scope of this implementation (we're just passing through), the only way I think we could handle this would be to detect the "stand alone" currency symbols and then escape them, which is... out of scope, and currently only works with MathJax.