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
This commit is contained in:
133
js/utils/katex-render.js
Normal file
133
js/utils/katex-render.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user