From c3d6af8757ecec9c138fbceb0113b775f17ed8f7 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 16:45:59 +0300 Subject: [PATCH] fix(exam-prep): normalize LaTeX in dashboard preview text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/src/routes/exam-prep.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/backend/src/routes/exam-prep.js b/backend/src/routes/exam-prep.js index 75fb0b9..1a4e0f6 100644 --- a/backend/src/routes/exam-prep.js +++ b/backend/src/routes/exam-prep.js @@ -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(//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; }