Files
Learn_System/backend/scripts/migrate_phys9_ch4.js
T
Maxim Dolgolyov dcdcde5b4e fix(textbooks): Физика 9 — escape § в num + phys9_legacy.js + финалы 5 глав
Багфиксы:
- gen_phys9_ch.js: убран двойной escape \u00a7 → литерал §
  (раньше карточка показывала '\u00a7 1' вместо '§ 1')
- phys9_legacy.js (262 КБ): извлечён весь JS монолита для глобальных onclick-
  обработчиков (startAnim1, lab11add/all/reset, checkNum, togglePend36 и пр.).
  Setup-код в конце обёрнут в try/catch — он рассчитан на DOM монолита.
- migrate_phys9_ch4.js + migrate_phys9_content.js: подключают phys9_legacy.js
  во все 5 ch-файлов перед закрытием <head>.

Финалы глав (write_phys9_finals.js):
- ch1: 5 задач (кинематика — поезд, разгон, окружность, лодка/река)
- ch2: 5 задач (динамика — трение, Гук, свободное падение, перегрузка)
- ch3: 5 задач (статика — рычаг, Архимед, блок, КПД накл. плоск., льдина)
- ch4: 5 задач (импульс — неупр. удар, ЗСЭ, мощность крана, пуля, бросок)
- ch5: 5 контрольных по практикуму (среднее, ЛР2, ЛР4, ЛР6, ЛР10)

Все задачи с автопроверкой через checkNum() (теперь работает из legacy.js).
2026-05-30 08:55:00 +03:00

143 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Перенос §31-36 из монолитного physics_9.html в physics_9_ch4.html.
// - Извлекает CSS-блок монолита и инжектит в ch4 (стили .para-hero, .formula-grid, .fcard, .def-box, .remember-box и т.д. нужны)
// - Извлекает HTML-тело каждого §31..§36
// - Убирает emoji (нарушают правило проекта) и Font Awesome <i> теги
// - Подключает Font Awesome CDN для совместимости (на случай если внутри остались)
// - Заменяет STUB-builder в physics_9_ch4.html на реальный контент
'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', 'textbooks', 'physics_9_ch4.html');
const src = fs.readFileSync(SRC, 'utf8');
let ch4 = fs.readFileSync(DST, 'utf8');
// === 1. Извлекаем CSS-блок монолита ===
const styleStart = src.indexOf('<style>') + '<style>'.length;
const styleEnd = src.indexOf('</style>', styleStart);
const monolithCss = src.slice(styleStart, styleEnd);
console.log('monolith CSS:', monolithCss.length, 'bytes');
// === 2. Извлекаем тела §31..§36 ===
const PARAS = {};
const REF_END_36 = src.indexOf('Проверка закона сохранения импульса');
const refEnd = src.lastIndexOf('<!-- ═', REF_END_36 > 0 ? REF_END_36 : src.length);
for (let n = 31; n <= 36; n++) {
const tag = `id="tab-ref${n}"`;
const i = src.indexOf(tag);
if (i < 0) { console.log('miss', n); continue; }
// Найти конец: следующий tab-ref или (для §36) refEnd
let j;
if (n < 36) {
j = src.indexOf(`id="tab-ref${n+1}"`, i);
// Откатимся к комментарию ═══ перед следующим
const cm = src.lastIndexOf('<!--', j);
if (cm > i + 1000) j = cm;
} else {
j = refEnd;
}
// Найти позицию открывающего <div class="content ..." id="tab-refN">
const divStart = src.lastIndexOf('<div class="content', i);
// Извлекаем сырое тело — от <div class="content" id="tab-refN"> до boundary j
const raw = src.slice(divStart, j);
PARAS[n] = raw;
console.log(${n}: ${raw.length} bytes`);
}
// === 3. Очистка: убрать emoji и Font Awesome <i> теги ===
function clean(s) {
return s
// Emoji (Unicode supplementary + dingbats + misc symbols)
.replace(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{27BF}]|[\u{1F000}-\u{1F2FF}]|[\u{FE0F}]/gu, '')
// Font Awesome icons - заменяем на пусто (либо можно на SVG ниже)
.replace(/<i\s+class="fa[s ][^"]*"[^>]*>\s*<\/i>/g, '')
// Лишние пробелы после удалений
.replace(/(\s)\s+/g, '$1')
.trim();
}
// === 4. Преобразуем тело каждого § в формат builder'а ===
// Builder ожидает: html += makeCard('theory', name, '§N', `BODY`);
// Поскольку наше body уже — большой готовый HTML с собственными классами, оборачиваем напрямую в <div>.
const PARA_NAMES = {
31:'Импульс тела. Импульс системы тел',
32:'Закон сохранения импульса. Реактивное движение',
33:'Механическая работа. Мощность',
34:'Потенциальная энергия',
35:'Кинетическая энергия. Полная энергия системы тел',
36:'Закон сохранения энергии',
};
// === 5. Заменяем STUB-builder каждого pN в ch4 файле ===
for (let n = 31; n <= 36; n++) {
const pid = 'p' + n;
let body = clean(PARAS[n]);
// Удаляем внешний <div class="content..." id="tab-refN"> и закрывающий </div>
body = body.replace(/^<div\s+class="content[^"]*"\s+id="tab-ref\d+">/, '');
// Найти и удалить ровно один соответствующий закрывающий </div> в конце
// (поскольку HTML может быть несбалансированным, безопаснее сделать regex по последнему </div>\s* в строке)
body = body.replace(/<\/div>\s*$/, '');
// Экранируем backticks и ${...} для template literal
const esc = body.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
// Найти стандартный stub-блок для pN
// Stub имеет вид: makeCard('theory', "<name>", "§<n>", `\n <p>...в разработке...</p>\n <p>...</p>\n <p style="margin-top:10px;...">\n <b>Phase 0:</b>...<b>Phase 4+:</b>...\n </p>\n `);
// Используем regex с захватом до закрывающего `);
const stubRegex = new RegExp(
`makeCard\\('theory', "${PARA_NAMES[n].replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}", "\\u00a7${n}", \`[\\s\\S]*?\`\\);`
);
const match = ch4.match(stubRegex);
if (!match) {
console.error(`STUB not found for ${pid}`);
// Try simpler matcher
const simpleStub = `makeCard('theory', "${PARA_NAMES[n]}", "§${n}", `;
const idx = ch4.indexOf(simpleStub);
console.log(` simple-match for "${simpleStub.slice(0,50)}..." at`, idx);
process.exit(1);
}
const replacement = `makeCard('theory', ${JSON.stringify(PARA_NAMES[n])}, "§${n}", \`\n${esc}\n \`);`;
ch4 = ch4.replace(stubRegex, () => replacement);
console.log(${n} → builder replaced (${body.length} bytes)`);
}
// === 6. Инжектим CSS монолита в ch4 (перед </style>) ===
// Чтобы не дублировать — проверим, не уже ли инжекчено
if (!ch4.includes('/* === MONOLITH CSS (migrated from physics_9.html) === */')) {
const inject = `\n/* === MONOLITH CSS (migrated from physics_9.html) === */\n${monolithCss}\n/* === END MONOLITH CSS === */\n`;
ch4 = ch4.replace('</style>', inject + '</style>');
console.log('Monolith CSS injected');
}
// === 7. Подключим Font Awesome CDN (на случай оставшихся <i>) ===
if (!ch4.includes('font-awesome')) {
ch4 = ch4.replace(
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">',
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">\n<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">'
);
console.log('Font Awesome CDN linked');
}
// === 8. phys9_legacy.js ===
if (!ch4.includes('phys9_legacy.js')) {
ch4 = ch4.replace(
'<script src="/js/phys.js" defer></script>',
'<script src="/js/phys.js" defer></script>\n<script src="/js/phys9_legacy.js" defer></script>'
);
console.log('phys9_legacy.js linked');
}
fs.writeFileSync(DST, ch4);
console.log('OK ch4 →', DST, 'bytes:', ch4.length);
// Sanity: parse inline scripts
const scriptMatches = [...ch4.matchAll(/<script>([\s\S]*?)<\/script>/g)];
console.log('inline <script> count:', scriptMatches.length);
for (const m of scriptMatches) {
try { new Function(m[1]); }
catch(e) { console.error('JS PARSE FAIL:', e.message); process.exit(1); }
}
console.log('all inline JS parses OK');