Skip to content

Commit 596bd08

Browse files
committed
fix(katex): robust client-side fallback render — support $/85679, \(...\), \[...\]; auto-escape underscores in \text{...}; process more containers
1 parent fa2e66b commit 596bd08

File tree

2 files changed

+99
-25
lines changed

2 files changed

+99
-25
lines changed

static/js/katex_init.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Initialize KaTeX auto-render on page content with CSP-friendly external script
2+
(function(){
3+
function safeContainer(el){
4+
return !(el.closest('pre, code'));
5+
}
6+
function tokenizeMath(text){
7+
var tokens = [];
8+
var i = 0;
9+
var dels = [
10+
{l:'$$', r:'$$', d:true},
11+
{l:'\\[', r:'\\]', d:true},
12+
{l:'\\(', r:'\\)', d:false},
13+
{l:'$', r:'$', d:false},
14+
];
15+
while (i < text.length){
16+
// find earliest delimiter occurrence
17+
var best = null, bestIdx = -1;
18+
for (var di=0; di<dels.length; di++){
19+
var idx = text.indexOf(dels[di].l, i);
20+
if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) { best = dels[di]; bestIdx = idx; }
21+
}
22+
if (bestIdx === -1){
23+
tokens.push({t:'t', s:text.slice(i)});
24+
break;
25+
}
26+
if (bestIdx > i){ tokens.push({t:'t', s:text.slice(i, bestIdx)}); }
27+
var start = bestIdx + best.l.length;
28+
var end = text.indexOf(best.r, start);
29+
if (end === -1){
30+
// unmatched: treat as text
31+
tokens.push({t:'t', s:text.slice(bestIdx)});
32+
break;
33+
}
34+
var content = text.slice(start, end);
35+
tokens.push({t:'m', d:best.d, s:content});
36+
i = end + best.r.length;
37+
}
38+
return tokens;
39+
}
40+
function escapeUnderscoresInTextCommand(str){
41+
return str.replace(/\\text\{([^}]*)\}/g, function(_, inner){
42+
var out = '';
43+
for (var i=0;i<inner.length;i++){
44+
var ch = inner[i];
45+
if (ch === '_' && (i===0 || inner[i-1] !== '\\')){ out += '\\\\_'; }
46+
else { out += ch; }
47+
}
48+
return '\\\\text{' + out + '}';
49+
});
50+
}
51+
function rebuildWithKatex(el){
52+
if (!window.katex) return;
53+
var txt = el.textContent;
54+
if (!(/[\$]/.test(txt) || /\\\(|\\\)|\\\[|\\\]/.test(txt))) return;
55+
var tokens = tokenizeMath(txt);
56+
if (!tokens.some(function(x){return x.t==='m';})) return;
57+
while (el.firstChild) el.removeChild(el.firstChild);
58+
tokens.forEach(function(tok){
59+
if (tok.t==='t'){
60+
el.appendChild(document.createTextNode(tok.s));
61+
} else if (tok.t==='m'){
62+
var content = escapeUnderscoresInTextCommand(tok.s);
63+
var span = document.createElement('span');
64+
try { window.katex.render(content, span, {displayMode: tok.d}); } catch(_) { span.textContent = tok.s; }
65+
el.appendChild(span);
66+
}
67+
});
68+
el.setAttribute('data-katex-rebuilt','1');
69+
}
70+
function init(){
71+
if (typeof renderMathInElement === 'function') {
72+
try {
73+
renderMathInElement(document.body, {
74+
delimiters: [
75+
{left: "$$", right: "$$", display: true},
76+
{left: "\\(", right: "\\)", display: false},
77+
{left: "\\[", right: "\\]", display: true}
78+
],
79+
ignoredTags: ["script","noscript","style","textarea","pre","code","option"],
80+
});
81+
} catch (_) {}
82+
}
83+
if (window.katex) {
84+
document.querySelectorAll('.math-katex').forEach(function(el){
85+
try { window.katex.render(el.textContent, el, { displayMode: (el.dataset.display === 'true') }); } catch (_) {}
86+
});
87+
// Fallback fix: rebuild math in common containers, avoiding code blocks
88+
document.querySelectorAll('.e-content.body p, .e-content.body li, .e-content.body h1, .e-content.body h2, .e-content.body h3, .e-content.body h4, .e-content.body blockquote, .e-content.body td, .e-content.body th, .e-content.body dd, .e-content.body dt, .e-content.body figcaption').forEach(function(el){
89+
if (safeContainer(el) && el.getAttribute('data-katex-rebuilt') !== '1') rebuildWithKatex(el);
90+
});
91+
}
92+
}
93+
if (document.readyState === 'loading') {
94+
document.addEventListener('DOMContentLoaded', init);
95+
} else {
96+
init();
97+
}
98+
})();

templates/tabi/extend_body.html

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,5 @@
11
{# Local KaTeX auto-render for standard delimiters and robust shortcode rendering #}
22
{%- if macros_settings::evaluate_setting_priority(setting="katex", page=page | default(value=""), section=section | default(value=""), default_global_value=false) == "true" -%}
33
<script defer src="{{ get_url(path='js/katex_autorender.min.js', trailing_slash=false) | safe }}"></script>
4-
<script>
5-
document.addEventListener("DOMContentLoaded", function() {
6-
try {
7-
if (typeof renderMathInElement === 'function') {
8-
renderMathInElement(document.body, {
9-
delimiters: [
10-
{left: "$$", right: "$$", display: true},
11-
{left: "\\(", right: "\\)", display: false},
12-
{left: "\\[", right: "\\]", display: true}
13-
],
14-
ignoredTags: ["script","noscript","style","textarea","pre","code","option"],
15-
});
16-
}
17-
} catch (e) { /* no-op */ }
18-
19-
// Fallback: render shortcodes that avoid Markdown interference
20-
if (window.katex) {
21-
document.querySelectorAll('.math-katex').forEach(function(el){
22-
try {
23-
window.katex.render(el.textContent, el, { displayMode: (el.dataset.display === 'true') });
24-
} catch (e) { /* ignore parse errors */ }
25-
});
26-
}
27-
});
28-
</script>
4+
<script defer src="{{ get_url(path='js/katex_init.js', trailing_slash=false) | safe }}"></script>
295
{%- endif -%}

0 commit comments

Comments
 (0)