feat(textbooks): инжект task-панелей §31-36 в physics_9_ch4.html
Жалоба: 'каждому параграфу там [в монолите] есть задачи, тут нет'.
В монолите physics_9.html — отдельный tab-tasks блок содержит 36 ptab-pN
панелей (~1.3 KB каждая) со scaffold'ом score-bar/prog-wrap/nav-dots/
taskArea/feedback/summary. Сами задачи (TASKS_P31..P36) рендерятся в
taskAreapN через goToTask('pN', i).
migrate_phys9_tasks.cjs:
- Извлекает ptab-p31..p36 из монолита (clean emoji + FA<i>)
- Внутри каждого build_pN в ch4 после theory body добавляет <div class='wg'>
с заголовком 'Задачи §N · Тренажёр §N' и вставляет ptab HTML
- Через 80 мс после render вызывает goToTask('pN', 0) → рендерит первую
задачу из TASKS_PN
Не делаю это для §1-30: TASKS_P1..P30 не определены в монолите
(там было решение делать тренажёры только для главы 'Законы сохранения').
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// Inject task panels (ptab-pN scaffold + JS auto-render) into physics_9_ch4.html
|
||||
// for §31..§36 — the only paragraphs with TASKS_PN arrays in the monolith.
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const TBOOKS = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
|
||||
const SRC = path.join(TBOOKS, 'physics_9.html');
|
||||
const DST = path.join(TBOOKS, 'physics_9_ch4.html');
|
||||
|
||||
const src = fs.readFileSync(SRC, 'utf8');
|
||||
let ch4 = fs.readFileSync(DST, 'utf8');
|
||||
|
||||
// === Extract each ptab-pN block (N in 31..36) ===
|
||||
function clean(s) {
|
||||
return s
|
||||
.replace(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{27BF}]|[\u{1F000}-\u{1F2FF}]|[\u{FE0F}]/gu, '')
|
||||
.replace(/<i\s+class="fa[s ][^"]*"[^>]*>\s*<\/i>/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractPtab(n) {
|
||||
const tag = `id="ptab-p${n}"`;
|
||||
const i = src.indexOf(tag);
|
||||
if (i < 0) return null;
|
||||
// Откатываемся к открывающему div
|
||||
const divStart = src.lastIndexOf('<div', i);
|
||||
// Ищем следующий ptab или конец tab-tasks
|
||||
let endTag = src.indexOf(`id="ptab-p${n+1}"`, i);
|
||||
if (endTag < 0) endTag = src.indexOf(`id="ptab-hard"`, i);
|
||||
if (endTag < 0) endTag = i + 2000;
|
||||
// Закрывающий </div> непосредственно перед следующим ptab
|
||||
let closingDiv = src.lastIndexOf('</div>', endTag);
|
||||
if (closingDiv < divStart) closingDiv = endTag;
|
||||
return src.slice(divStart, closingDiv + 6);
|
||||
}
|
||||
|
||||
const PTABS = {};
|
||||
for (let n = 31; n <= 36; n++) {
|
||||
const b = extractPtab(n);
|
||||
if (b) PTABS[n] = clean(b);
|
||||
}
|
||||
console.log('Extracted ptabs:', Object.keys(PTABS).map(k => `p${k}:${PTABS[k].length}b`).join(' '));
|
||||
|
||||
// === Inject ptab block + auto-render call into each build_pN in ch4 ===
|
||||
// Pattern: each build_pN ends with `box.innerHTML = html; renderMath(box); wireReadBtn('pN');`
|
||||
// We append the ptab HTML to the same box, then call window.goToTask('pN', 0) to start.
|
||||
|
||||
for (let n = 31; n <= 36; n++) {
|
||||
if (!PTABS[n]) continue;
|
||||
const pid = 'p' + n;
|
||||
// Escape for template literal
|
||||
const esc = PTABS[n].replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
||||
|
||||
// Find build_pN function end
|
||||
const fnRegex = new RegExp(
|
||||
`(function\\s+build_${pid}\\(\\)\\s*\\{[\\s\\S]*?wireReadBtn\\('${pid}'\\);)\\s*\\}`,
|
||||
'm'
|
||||
);
|
||||
const match = ch4.match(fnRegex);
|
||||
if (!match) {
|
||||
console.warn(`build_${pid}: not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build new function body: append ptab HTML + setup call
|
||||
const injectedBlock = `
|
||||
// === Задачи §${n} — task panel (auto-injected from monolith) ===
|
||||
const tasksBlock = document.createElement('div');
|
||||
tasksBlock.className = 'wg';
|
||||
tasksBlock.style.marginTop = '20px';
|
||||
tasksBlock.innerHTML = '<div class="wg-header"><span class="wg-badge">Задачи</span><div class="wg-title">Тренажёр §${n}</div></div>' + \`${esc}\`;
|
||||
box.appendChild(tasksBlock);
|
||||
// Auto-render first task
|
||||
setTimeout(() => {
|
||||
try { if (typeof goToTask === 'function') goToTask('${pid}', 0); }
|
||||
catch(e) { console.warn('${pid} goToTask:', e.message); }
|
||||
}, 80);`;
|
||||
|
||||
const newBody = match[1] + injectedBlock + '\n}';
|
||||
ch4 = ch4.replace(fnRegex, () => newBody);
|
||||
console.log(` build_${pid}: injected ptab (${esc.length} bytes)`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(DST, ch4);
|
||||
console.log('ch4 size:', ch4.length);
|
||||
|
||||
// Sanity parse inline scripts
|
||||
const scripts = [...ch4.matchAll(/<script>([\s\S]*?)<\/script>/g)];
|
||||
for (const m of scripts) {
|
||||
try { new Function(m[1]); }
|
||||
catch(e) { console.error('JS PARSE FAIL:', e.message); process.exit(1); }
|
||||
}
|
||||
console.log('inline JS parses OK');
|
||||
Reference in New Issue
Block a user