fix(exam-prep): normalize LaTeX in dashboard preview text

Recent-attempts widget on /exam-prep/:examKey was showing raw LaTeX
like '\dfrac{7}{9}' because stripPreview only removed HTML tags.
Now it also converts common LaTeX to readable unicode (fractions →
a/b, \sqrt → √, \cdot → ·, comparisons → ≤≥≠, Greek letters, etc.)
before truncating.

KaTeX rendering would be overkill for a 100-char preview row; this
just makes the existing text legible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 16:45:59 +03:00
parent 4154e0b791
commit c3d6af8757
+29 -3
View File
@@ -796,13 +796,39 @@ router.get('/:examKey/dashboard', (req, res) => {
});
});
/* Lightweight preview: strip tags, normalize whitespace, truncate. */
/* Lightweight preview: strip HTML, normalize LaTeX to readable unicode,
collapse whitespace, truncate. Used in dashboard "recent attempts" rows
where rendering KaTeX would be overkill but raw `$\dfrac{7}{9}$` is
ugly. Matches the spirit of tag-exam-tasks.js#stripText. */
function stripPreview(html) {
const text = String(html || '')
let text = String(html || '')
.replace(/<svg[\s\S]*?<\/svg>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/<[^>]+>/g, ' ');
// Common LaTeX → unicode/plain. Order matters: handle fractions/roots
// before stripping bare backslash commands.
text = text
.replace(/\\d?frac\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, '$1/$2')
.replace(/\\sqrt\s*\{([^{}]+)\}/g, '√($1)')
.replace(/\\cdot/g, '·')
.replace(/\\times/g, '×')
.replace(/\\pm/g, '±')
.replace(/\\mp/g, '∓')
.replace(/\\leq?/g, '≤')
.replace(/\\geq?/g, '≥')
.replace(/\\neq/g, '≠')
.replace(/\\approx/g, '≈')
.replace(/\\infty/g, '∞')
.replace(/\\pi/g, 'π')
.replace(/\\alpha/g, 'α').replace(/\\beta/g, 'β').replace(/\\gamma/g, 'γ')
.replace(/\\(?:left|right)/g, '')
.replace(/\\[a-zA-Z]+\s*/g, ' ') // any remaining LaTeX command
.replace(/[{}]/g, '') // leftover braces
.replace(/\$+/g, '') // math delimiters
.replace(/&[a-z]+;/gi, ' ') // html entities
.replace(/\s+/g, ' ')
.trim();
return text.length > 100 ? text.slice(0, 100) + '…' : text;
}