fix(textbooks): Физика 9 — STATE collision, KaTeX escape, авто-init симуляций

Три бага из жалобы пользователя:

1) phys9_legacy.js упал с 'Identifier STATE has already been declared' —
   const STATE в монолите конфликтовал с const STATE в chapter inline JS.
   Скрипт extract_phys9_legacy.cjs теперь оборачивает извлечённый код в IIFE
   и явно экспортит через window 70 функций (upd*/draw*/init*/start*/lab*/
   check*/toggle*/render*/show*/...) + 7 const-массивов (TASKS_PN, PUZ_PN).

2) В боковой панели формулы рендерились как 'Delta vecr' вместо Δr⃗ —
   мой переход на JSON.stringify в gen_phys9_ch.js добавил лишний слой
   escape backslash. Уменьшил \\ → \ в SIDEBAR_ROWS, TIPS_HTML,
   PARA_SUBS, LR_SUBS (90 строк). Цепочка теперь: source \Delta → string
   \Delta → JSON "\\Delta" → HTML JS \Delta → runtime \Delta →
   KaTeX \Delta ✓.

3) 'не работают симуляции' — функции из legacy.js были доступны, но
   chapter goTo(id) их не вызывал. Добавлен авто-вызов upd<N>(),
   startAnim<N>(), init<N>(), draw<N>() при переключении на параграф,
   и updLab<N>(), drawLab<N>() — для ЛР.
This commit is contained in:
Maxim Dolgolyov
2026-05-30 09:06:20 +03:00
parent c26423b7d4
commit 66bd7ac1f4
8 changed files with 412 additions and 194 deletions
+58
View File
@@ -0,0 +1,58 @@
// Извлекает <script> из physics_9.html в frontend/js/phys9_legacy.js.
// Оборачивает в IIFE (избегаем коллизий STATE/PARAS с chapter inline JS).
// Эспортит в window все функции с префиксами upd|draw|init|start|set|toggle|lab|check + TASKS_PN/PUZ_PN/массивы.
'use strict';
const fs = require('fs');
const path = require('path');
const SRC = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_9.html');
const DST = path.join(__dirname, '..', '..', 'frontend', 'js', 'phys9_legacy.js');
const h = fs.readFileSync(SRC, 'utf8');
const scriptMatch = h.match(/<script>([\s\S]*?)<\/script>/);
if (!scriptMatch) { console.error('No <script> found'); process.exit(1); }
const raw = scriptMatch[1];
// Очистка emoji
const clean = raw.replace(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{27BF}]|[\u{1F000}-\u{1F2FF}]|[\u{FE0F}]/gu, '');
// Найти границу setup-кода
const setupStart = clean.search(/^upd1\d\(\);/m);
const fnsPart = setupStart > 0 ? clean.slice(0, setupStart) : clean;
const setupPart = setupStart > 0 ? clean.slice(setupStart) : '';
// Сканируем все function-declarations и const-объявления массивов
const fnNames = [...new Set([...fnsPart.matchAll(/^function\s+(\w+)\s*\(/gm)].map(m => m[1]))];
const constNames = [...new Set([...fnsPart.matchAll(/^const\s+(TASKS_\w+|PUZ_\w+|QUIZ_\w+|MATCH_\w+)\s*=/gm)].map(m => m[1]))];
// Только идентификаторы, которые могут использоваться извне (по префиксу или капсу)
const exportFns = fnNames.filter(n => /^(upd|draw|init|start|set|toggle|lab|check|reset|next|go|run|play|stop|render|update|show|hide|build|switch|select|apply|calc|recalc|animate|tick)/i.test(n));
const exportList = [...exportFns, ...constNames];
const header = `// Auto-extracted from frontend/textbooks/physics_9.html (legacy monolith).
// Wrapped in IIFE — avoids collisions with chapter inline JS (STATE, PARAS, etc.).
// All upd*/draw*/init*/start*/lab*/check*/toggle* functions + TASKS_PN arrays
// are explicitly attached to window at the end.
// eslint-disable
(function(){
"use strict";
`;
const exportTail = '\n\n// === Expose handlers + task pools to global scope ===\n' +
exportList.map(name => `try { if (typeof ${name} !== "undefined") window.${name} = ${name}; } catch(e) {}`).join('\n') +
'\n})();\n';
const wrapped = setupStart > 0
? header + fnsPart + '\ntry {\n' + setupPart + '\n} catch(e) { console.warn("phys9_legacy setup skipped:", e.message); }\n' + exportTail
: header + fnsPart + exportTail;
fs.writeFileSync(DST, wrapped);
console.log('phys9_legacy.js:', wrapped.length, 'bytes');
console.log('Exported functions:', exportFns.length);
console.log('Exported consts:', constNames.length);
console.log('Sample exports:', exports.slice(0, 12).join(', '), '...');
// Sanity parse
try { new Function(wrapped); console.log('OK parse'); }
catch(e) { console.error('PARSE FAIL:', e.message); process.exit(1); }