dcdcde5b4e
Багфиксы: - 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).
143 lines
7.3 KiB
JavaScript
143 lines
7.3 KiB
JavaScript
// Перенос §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');
|