fix(ct-math): литеральные угловые скобки в формулах уроков ломали KaTeX

Блок formula вставляет tex в HTML без экранирования, поэтому литеральная
"меньше"-скобка (напр. в "0 le r lt d") принималась браузером за HTML-тег и
формула не рендерилась (показывался сырой $$...$$). Заменено на \lt и \gt
(KaTeX рендерит их как отношения).

- seed_ctmath_lessons_rest.js: исправлены 4 формулы в исходнике (числа,
  модуль, показ/лог равносильности, производная-монотонность).
- fix_ctmath_formula_lt.js: фикс уже залитых блоков курса 13 (dry/--apply).
  Флешкарты не затронуты (mathHtmlFC через textContent экранирует сам).

Запись (UPDATE 4 блоков) запускает пользователь.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-15 12:05:47 +03:00
parent a982628d04
commit 4b23d768f2
2 changed files with 33 additions and 4 deletions
+29
View File
@@ -0,0 +1,29 @@
'use strict';
/*
* Фикс: блок formula вставляет tex в HTML БЕЗ экранирования ($$...$$), поэтому
* литеральные '<' / '>' в формуле браузер принимает за HTML-тег → KaTeX не рендерит.
* Заменяем литеральные '<' → '\lt', '>' → '\gt' в tex всех formula-блоков курса 13
* (KaTeX их рендерит как отношения). Идемпотентно. dry по умолчанию, запись --apply.
* node backend/scripts/fix_ctmath_formula_lt.js [--apply]
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
const rows = db.prepare(`SELECT lb.id, lb.lesson_id, lb.data FROM lesson_blocks lb
JOIN lessons l ON l.id=lb.lesson_id WHERE l.course_id=13 AND lb.type='formula'`).all();
const upd = db.prepare('UPDATE lesson_blocks SET data=? WHERE id=?');
let changed = 0;
for (const r of rows) {
let d; try { d = JSON.parse(r.data); } catch { continue; }
if (!d.tex || !/[<>]/.test(d.tex)) continue;
const before = d.tex;
d.tex = d.tex.replace(/</g, '\\lt ').replace(/>/g, '\\gt ');
changed++;
console.log(`block ${r.id} (lesson ${r.lesson_id}):`);
console.log(' было:', before);
console.log(' стало:', d.tex);
if (APPLY) upd.run(JSON.stringify(d), r.id);
}
console.log(`\n${APPLY ? 'Обновлено' : '(dry) к обновлению'}: ${changed} формул.`);
if (!APPLY) console.log('Запись: --apply');
+4 -4
View File
@@ -27,7 +27,7 @@ const LESSONS = [
{ section: 'Числа и вычисления', title: 'Числа, делимость и проценты', read: 8, blocks: [
H('Числа, делимость и проценты'),
P('Действительные числа на координатной прямой нужно уметь оценивать и сравнивать. Деление с остатком записывается формулой ниже.'),
F('n = d\\cdot q + r,\\qquad 0\\le r<d', 'Деление с остатком'),
F('n = d\\cdot q + r,\\qquad 0\\le r\\lt d', 'Деление с остатком'),
P('Проценты: $p\\%$ числа $a$ равно $\\dfrac{p}{100}\\cdot a$. Увеличение на $p\\%$ — умножение на $\\left(1+\\dfrac{p}{100}\\right)$, уменьшение — на $\\left(1-\\dfrac{p}{100}\\right)$.'),
F('\\text{НОД}(a,b)\\cdot\\text{НОК}(a,b)=a\\cdot b', 'Связь НОД и НОК'),
H('Разбор А4', 3),
@@ -63,7 +63,7 @@ const LESSONS = [
F('x_{1,2}=\\dfrac{-b\\pm\\sqrt{D}}{2a},\\ \\ D=b^2-4ac;\\qquad x_1x_2=\\dfrac{c}{a},\\ \\ x_1+x_2=-\\dfrac{b}{a}', 'Квадратное уравнение и теорема Виета'),
SIM('quadratic', 'Корни квадратного уравнения и дискриминант'),
P('Метод интервалов: разложить на множители, отметить нули, расставить знаки по промежуткам. Учитывать кратность корня (при чётной кратности знак не меняется).'),
F('|x|=a\\Rightarrow x=\\pm a\\ (a\\ge0);\\qquad |f(x)|<a\\Leftrightarrow -a<f(x)<a', 'Модуль'),
F('|x|=a\\Rightarrow x=\\pm a\\ (a\\ge0);\\qquad |f(x)|\\lt a\\Leftrightarrow -a\\lt f(x)\\lt a', 'Модуль'),
CI('Двойное неравенство $a\\le f(x)<b$ решают как систему; целые решения отбирают на полученном промежутке.'),
H('Разбор А5', 3),
P('Произведение действительных корней уравнения $x^2-5x+6=0$ по теореме Виета равно $6$ (корни $2$ и $3$).'),
@@ -79,7 +79,7 @@ const LESSONS = [
]},
{ section: 'Уравнения и неравенства', title: 'Показательные, логарифмические, иррациональные', read: 12, blocks: [
H('Показательные, логарифмические, иррациональные уравнения и неравенства'),
F('a^{f}=a^{g}\\Leftrightarrow f=g;\\qquad \\log_a f=\\log_a g\\Leftrightarrow f=g>0', 'Равносильные переходы'),
F('a^{f}=a^{g}\\Leftrightarrow f=g;\\qquad \\log_a f=\\log_a g\\Leftrightarrow f=g\\gt 0', 'Равносильные переходы'),
F('\\sqrt{f}=g\\ \\Leftrightarrow\\ \\begin{cases}g\\ge0\\\\ f=g^2\\end{cases}', 'Иррациональное уравнение'),
CI('Метод рационализации (для неравенств): знак $\\log_a f-\\log_a g$ совпадает со знаком $(a-1)(f-g)$; знак $a^{f}-a^{g}$ — со знаком $(a-1)(f-g)$. Экономит время на сложных неравенствах.'),
CW('В логарифмических всегда выписывайте ОДЗ: аргумент $>0$, основание $>0$ и $\\ne1$.'),
@@ -99,7 +99,7 @@ const LESSONS = [
H('Функции: свойства, графики, производная'),
P('Ключевые свойства: ОДЗ, чётность (если $f(-x)=f(x)$ — чётная, график симметричен относительно $Oy$; если $f(-x)=-f(x)$ — нечётная), монотонность, нули.'),
SIM('graphtransform', 'Преобразования графиков: сдвиги и растяжения'),
F('f\'>0\\Rightarrow\\text{возрастает};\\quad f\'<0\\Rightarrow\\text{убывает};\\quad f\'=0\\ \\text{со сменой знака}\\Rightarrow\\text{экстремум}', 'Производная и поведение функции'),
F('f\'\\gt 0\\Rightarrow\\text{возрастает};\\quad f\'\\lt 0\\Rightarrow\\text{убывает};\\quad f\'=0\\ \\text{со сменой знака}\\Rightarrow\\text{экстремум}', 'Производная и поведение функции'),
H('Разбор В2 (квадратичная)', 3),
P('$f(x)=x^2-6x+5$: нули $1$ и $5$ (их сумма $6$); $f(0)=5$; вершина при $x=3$, наименьшее значение $f(3)=-4$.'),
CS('Сумма нулей $=6$; наименьшее значение $=-4$.'),