Files
algebra-webapp/js/utils/katex-render.js
renato97 45582e5f59 feat: interactive linear algebra practice web app
- 68 exercises from UBA FCE chapters 1-3
- Step-by-step solutions with KaTeX rendering
- Theory panels (26 topics) expandable per exercise
- Matrix builder (2x2/3x3/4x4) with 7 operations
- System solver (Gauss, Gauss-Jordan, Cramer, Rouché-Frobenius)
- Glassmorphism UI with dark mode
- Canvas particle background
- ARIA accessibility (keyboard nav, screen reader)
- Zero build step - open index.html directly
2026-05-20 01:26:40 -03:00

134 lines
4.0 KiB
JavaScript

/**
* KaTeX Render Helper
* Wraps katex.render() with error handling for both display and inline modes.
* Security-hardened with strict mode and LaTeX sanitization.
*/
const KatexRenderer = {
/**
* Macro whitelist — only macros that KaTeX does NOT natively support.
* Prevents injection via \href, \url, \includegraphics, etc.
* REMOVED: Standard KaTeX commands (\vec, \overrightarrow, \cdot, \times,
* \sqrt, \frac, \pi, \alpha, \beta, \text, \implies, \neq, \infty, \lambda,
* \mu, \sum, \int, \dots, \vdots, \ddots, \hline, \det, \operatorname)
* — KaTeX already supports these natively; redefining them causes infinite
* recursion or strict mode violations.
*/
KATEX_MACROS: {
'\\ran': '\\text{ran}',
'\\nul': '\\text{nul}',
'\\rg': '\\text{rg}',
'\\sen': '\\sin',
'\\tg': '\\tan'
},
KATEX_OPTIONS: {
displayMode: true,
throwOnError: false,
trust: false,
strict: false,
macros: {}
},
/**
* Sanitize LaTeX input by removing potentially dangerous commands.
* @param {string} latex - Raw LaTeX string
* @returns {string} - Sanitized LaTeX safe for KaTeX
*/
sanitizeLatex(latex) {
if (!latex || typeof latex !== 'string') return '';
return latex
.replace(/\\href\{[^}]*\}\{[^}]*\}/g, '')
.replace(/\\url\{[^}]*\}/g, '')
.replace(/\\includegraphics\{[^}]*\}/g, '')
.replace(/\\write\{[^}]*\}/g, '')
.replace(/\\input\{[^}]*\}/g, '')
.replace(/\\include\{[^}]*\}/g, '')
.replace(/\\href\{[^}]*\}/g, '');
},
/**
* Render a LaTeX expression into a DOM element.
* @param {HTMLElement|string} el - Target element or CSS selector
* @param {string} latex - LaTeX string to render
* @param {Object} options - KaTeX options override
* @returns {boolean} true if rendered successfully
*/
render(el, latex, options = {}) {
const target = typeof el === 'string' ? document.querySelector(el) : el;
if (!target) {
console.warn('[KatexRenderer] Element not found:', el);
return false;
}
if (typeof katex === 'undefined') {
target.textContent = latex;
console.warn('[KatexRenderer] KaTeX not loaded');
return false;
}
const mergedOptions = Object.assign({}, this.KATEX_OPTIONS, {
macros: Object.assign({}, this.KATEX_MACROS, options.macros || {})
});
const sanitized = this.sanitizeLatex(latex);
try {
katex.render(sanitized, target, mergedOptions);
return true;
} catch (e) {
target.innerHTML = '<span style="color:#e17055;font-style:italic;">[Error LaTeX]</span>';
console.warn('[KatexRenderer] Parse error:', e.message, '\nLaTeX:', sanitized);
return false;
}
},
/**
* Render LaTeX in display mode (centered block).
*/
renderDisplay(el, latex) {
return this.render(el, latex, { displayMode: true });
},
/**
* Render LaTeX in inline mode.
*/
renderInline(el, latex) {
return this.render(el, latex, { displayMode: false });
},
/**
* Render all math expressions inside a container element.
* Uses KaTeX auto-render extension for $...$ and $$...$$ delimiters.
* @param {HTMLElement} container - DOM element to scan for math
*/
renderAll(container) {
if (typeof renderMathInElement === 'undefined') {
console.warn('[KatexRenderer] auto-render extension not loaded');
return;
}
renderMathInElement(container || document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true }
],
throwOnError: false,
trust: false,
strict: false,
macros: this.KATEX_MACROS
});
},
/**
* Create a span with rendered math and return it.
* Useful for building DOM fragments.
*/
createMathSpan(latex, displayMode = false) {
const span = document.createElement('span');
this.render(span, latex, { displayMode });
return span;
}
};