From 8142fc814fcd811e7261bcb268f8cd1ae4643ddb Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 09:21:35 +0300 Subject: [PATCH] =?UTF-8?q?feat(textbooks):=20=D0=B8=D0=BD=D0=B6=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=20task-=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=20=C2=A731-36=20=D0=B2=20physics=5F9=5Fch4.html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Жалоба: 'каждому параграфу там [в монолите] есть задачи, тут нет'. В монолите 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) - Внутри каждого build_pN в ch4 после theory body добавляет
с заголовком 'Задачи §N · Тренажёр §N' и вставляет ptab HTML - Через 80 мс после render вызывает goToTask('pN', 0) → рендерит первую задачу из TASKS_PN Не делаю это для §1-30: TASKS_P1..P30 не определены в монолите (там было решение делать тренажёры только для главы 'Законы сохранения'). --- backend/scripts/migrate_phys9_tasks.cjs | 94 +++++++++++ frontend/textbooks/physics_9_ch4.html | 198 ++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 backend/scripts/migrate_phys9_tasks.cjs diff --git a/backend/scripts/migrate_phys9_tasks.cjs b/backend/scripts/migrate_phys9_tasks.cjs new file mode 100644 index 0000000..cdad699 --- /dev/null +++ b/backend/scripts/migrate_phys9_tasks.cjs @@ -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(/]*>\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(' непосредственно перед следующим ptab + let closingDiv = src.lastIndexOf('
', 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 = '
Задачи
Тренажёр §${n}
' + \`${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(/