// ======================================================================= // Murphy Grammar Engine & Handwritten Journaling Module (Native ES Module) // Essential Grammar in Use (A1-A2) & English Grammar in Use (B1-B2) // Evening Deep Work Mode, Contrast Method & Paper Notebook Journaling // ======================================================================= import { api } from './api.js'; import { showToast, escapeHtml, renderMarkdownTable, renderMarkdownNotes } from './ui.js'; let allUnits = []; let currentFilter = 'all'; let currentUnit = null; let currentExerciseIdx = 0; let isExerciseAnswered = false; let sessionMistakes = 0; let sessionCorrect = 0; let sessionIngestedCards = []; // ----------------------------------------------------------------------- // Initializer // ----------------------------------------------------------------------- export function initGrammar() { const backBtn = document.getElementById('grammar-back-btn'); const tabTheoryBtn = document.getElementById('grammar-tab-theory-btn'); const tabPracticeBtn = document.getElementById('grammar-tab-practice-btn'); const toPracticeBtn = document.getElementById('grammar-to-practice-btn'); const exNextBtn = document.getElementById('grammar-ex-next-btn'); const sumRepeatBtn = document.getElementById('grammar-sum-repeat-btn'); const sumUnitsBtn = document.getElementById('grammar-sum-units-btn'); const sumSrsBtn = document.getElementById('grammar-sum-srs-btn'); const handwrittenToggle = document.getElementById('grammar-handwritten-toggle'); const cheatsheetToggle = document.getElementById('grammar-practice-cheatsheet-toggle'); const cheatsheetDrawer = document.getElementById('grammar-practice-cheatsheet-drawer'); if (cheatsheetToggle && cheatsheetDrawer) { cheatsheetToggle.addEventListener('click', () => { const isNowHidden = !cheatsheetDrawer.classList.contains('hidden'); cheatsheetDrawer.classList.toggle('hidden', isNowHidden); cheatsheetToggle.innerHTML = isNowHidden ? '📋 Шпаргалка по теме' : '📋 Скрыть шпаргалку'; }); } if (backBtn) { backBtn.addEventListener('click', () => { showGrammarScreen('directory'); window.location.hash = '#grammar'; loadGrammarUnits(); }); } if (tabTheoryBtn && tabPracticeBtn) { tabTheoryBtn.addEventListener('click', () => switchLessonTab('theory')); tabPracticeBtn.addEventListener('click', () => switchLessonTab('practice')); } if (toPracticeBtn) { toPracticeBtn.addEventListener('click', () => switchLessonTab('practice')); } if (exNextBtn) { exNextBtn.addEventListener('click', () => advanceExercise()); } if (sumRepeatBtn) { sumRepeatBtn.addEventListener('click', () => { if (currentUnit) { startUnitPractice(); } }); } if (sumUnitsBtn) { sumUnitsBtn.addEventListener('click', () => { showGrammarScreen('directory'); window.location.hash = '#grammar'; loadGrammarUnits(); }); } if (sumSrsBtn) { sumSrsBtn.addEventListener('click', () => { window.location.hash = '#srs/grammar'; }); } if (handwrittenToggle) { handwrittenToggle.addEventListener('change', async (e) => { if (!currentUnit) return; const isChecked = e.target.checked; try { await api.grammar.toggleHandwritten(currentUnit.id, isChecked); currentUnit.handwritten_logged = isChecked; if (isChecked) { showToast('✍️ Отметка сохранена: записано от руки в тетрадь!', 'success'); } else { showToast('Отметка тетради снята', 'info'); } } catch (err) { console.error(err); showToast('Ошибка сохранения статуса', 'error'); e.target.checked = !isChecked; } }); } // Filter pills in directory const filterPills = document.querySelectorAll('.grammar-filter-pill'); filterPills.forEach(pill => { pill.addEventListener('click', () => { filterPills.forEach(p => p.classList.remove('active')); pill.classList.add('active'); currentFilter = pill.dataset.filter || 'all'; renderUnitsGrid(); }); }); // Keyboard shortcuts (1-4 for exercise choices, Enter/Space for next) window.addEventListener('keydown', (e) => { const grammarView = document.getElementById('grammar-view'); const lessonScreen = document.getElementById('grammar-lesson-screen'); const practiceTab = document.getElementById('grammar-tab-practice-content'); if (!grammarView || grammarView.classList.contains('hidden')) return; if (!lessonScreen || lessonScreen.classList.contains('hidden')) return; if (!practiceTab || practiceTab.classList.contains('hidden')) return; if (['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName)) return; if (!isExerciseAnswered) { if (['1', '2', '3', '4'].includes(e.key)) { e.preventDefault(); const idx = parseInt(e.key, 10) - 1; const btns = document.querySelectorAll('.grammar-opt-btn'); if (btns[idx]) btns[idx].click(); } } else { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); advanceExercise(); } } }); } // ----------------------------------------------------------------------- // Screen Switcher inside #grammar-view // ----------------------------------------------------------------------- export function showGrammarScreen(screenName) { const dir = document.getElementById('grammar-directory-screen'); const lesson = document.getElementById('grammar-lesson-screen'); if (dir) dir.classList.toggle('hidden', screenName !== 'directory'); if (lesson) lesson.classList.toggle('hidden', screenName !== 'lesson'); } // ----------------------------------------------------------------------- // Units Directory Loader // ----------------------------------------------------------------------- export async function loadGrammarUnits() { showGrammarScreen('directory'); const grid = document.getElementById('grammar-units-grid'); if (grid) { grid.innerHTML = '

Загрузка каталога грамматики Мерфи...

'; } try { const res = await api.grammar.getUnits(); allUnits = res.units || []; updateDirectoryStats(); renderUnitsGrid(); } catch (err) { console.error('Error loading grammar units:', err); if (grid) { grid.innerHTML = `

Ошибка загрузки юнитов: ${escapeHtml(err.message)}

`; } } } function updateDirectoryStats() { const totalCount = allUnits.length; const completedCount = allUnits.filter(u => u.is_completed).length; const handwrittenCount = allUnits.filter(u => u.handwritten_logged).length; const totalEl = document.getElementById('grammar-stat-total'); const completedEl = document.getElementById('grammar-stat-completed'); const handwrittenEl = document.getElementById('grammar-stat-handwritten'); if (totalEl) totalEl.textContent = totalCount; if (completedEl) completedEl.textContent = completedCount; if (handwrittenEl) handwrittenEl.textContent = handwrittenCount; } function renderUnitsGrid() { const grid = document.getElementById('grammar-units-grid'); if (!grid) return; let filtered = allUnits; if (currentFilter === 'A1-A2') { filtered = allUnits.filter(u => u.level === 'A1-A2'); } else if (currentFilter === 'B1-B2') { filtered = allUnits.filter(u => u.level === 'B1-B2'); } else if (currentFilter === 'completed') { filtered = allUnits.filter(u => u.is_completed); } else if (currentFilter === 'handwritten') { filtered = allUnits.filter(u => u.handwritten_logged); } else if (currentFilter !== 'all') { filtered = allUnits.filter(u => u.category.toLowerCase().includes(currentFilter.toLowerCase())); } if (filtered.length === 0) { grid.innerHTML = '

Нет юнитов в этой категории

'; return; } grid.innerHTML = filtered.map(unit => { const levelBadgeClass = unit.level === 'A1-A2' ? 'badge-a1' : 'badge-b1'; const isComp = unit.is_completed; const isHw = unit.handwritten_logged; return `
Юнит ${unit.unit_num} ${escapeHtml(unit.level)}

${escapeHtml(unit.title)}

${escapeHtml(unit.subtitle)}

${escapeHtml(unit.category)}
`; }).join(''); // Attach click handlers to cards grid.querySelectorAll('.grammar-unit-card').forEach(card => { card.addEventListener('click', (e) => { const unitId = parseInt(card.dataset.unitId, 10); window.location.hash = `#grammar/unit/${unitId}`; }); }); } // ----------------------------------------------------------------------- // Unit Lesson Loader // ----------------------------------------------------------------------- export async function openGrammarUnit(unitId) { showGrammarScreen('lesson'); const lessonScreen = document.getElementById('grammar-lesson-screen'); if (!lessonScreen) return; const titleEl = document.getElementById('grammar-lesson-title'); if (titleEl) titleEl.textContent = 'Загрузка юнита...'; try { currentUnit = await api.grammar.getUnit(unitId); renderUnitTheory(); switchLessonTab('theory'); } catch (err) { console.error('Error loading grammar unit:', err); showToast('Не удалось загрузить урок: ' + err.message, 'error'); window.location.hash = '#grammar'; } } function switchLessonTab(tab) { const tabTheoryBtn = document.getElementById('grammar-tab-theory-btn'); const tabPracticeBtn = document.getElementById('grammar-tab-practice-btn'); const tabTheoryContent = document.getElementById('grammar-tab-theory-content'); const tabPracticeContent = document.getElementById('grammar-tab-practice-content'); const summaryCard = document.getElementById('grammar-summary-card'); if (tab === 'theory') { if (tabTheoryBtn) tabTheoryBtn.classList.add('active'); if (tabPracticeBtn) tabPracticeBtn.classList.remove('active'); if (tabTheoryContent) tabTheoryContent.classList.remove('hidden'); if (tabPracticeContent) tabPracticeContent.classList.add('hidden'); if (summaryCard) summaryCard.classList.add('hidden'); } else if (tab === 'practice') { if (tabTheoryBtn) tabTheoryBtn.classList.remove('active'); if (tabPracticeBtn) tabPracticeBtn.classList.add('active'); if (tabTheoryContent) tabTheoryContent.classList.add('hidden'); if (summaryCard) summaryCard.classList.add('hidden'); if (tabPracticeContent) tabPracticeContent.classList.remove('hidden'); startUnitPractice(); } else if (tab === 'summary') { if (tabTheoryBtn) tabTheoryBtn.classList.remove('active'); if (tabPracticeBtn) tabPracticeBtn.classList.remove('active'); if (tabTheoryContent) tabTheoryContent.classList.add('hidden'); if (tabPracticeContent) tabPracticeContent.classList.add('hidden'); if (summaryCard) summaryCard.classList.remove('hidden'); } } function renderUnitTheory() { if (!currentUnit) return; // Header info const numEl = document.getElementById('grammar-lesson-num'); const levelEl = document.getElementById('grammar-lesson-level'); const titleEl = document.getElementById('grammar-lesson-title'); const subEl = document.getElementById('grammar-lesson-sub'); const catEl = document.getElementById('grammar-lesson-cat'); const handwrittenToggle = document.getElementById('grammar-handwritten-toggle'); if (numEl) numEl.textContent = `Юнит ${currentUnit.unit_num}`; if (levelEl) { levelEl.textContent = currentUnit.level; levelEl.className = `grammar-level-pill ${currentUnit.level === 'A1-A2' ? 'badge-a1' : 'badge-b1'}`; } if (titleEl) titleEl.textContent = currentUnit.title; if (subEl) subEl.textContent = currentUnit.subtitle || ''; if (catEl) catEl.textContent = currentUnit.category; if (handwrittenToggle) { handwrittenToggle.checked = Boolean(currentUnit.handwritten_logged); } // Cheatsheet Markdown Table const cheatsheetSec = document.getElementById('grammar-cheatsheet-section'); const cheatsheetBox = document.getElementById('grammar-cheatsheet-content'); if (cheatsheetSec && cheatsheetBox) { if (currentUnit.cheatsheet_table && currentUnit.cheatsheet_table.trim()) { cheatsheetBox.innerHTML = renderMarkdownTable(currentUnit.cheatsheet_table); cheatsheetSec.classList.remove('hidden'); } else { cheatsheetBox.innerHTML = ''; cheatsheetSec.classList.add('hidden'); } } // Detailed Murphy Notes const notesSec = document.getElementById('grammar-detailed-notes-section'); const notesBox = document.getElementById('grammar-detailed-notes-content'); if (notesSec && notesBox) { if (currentUnit.detailed_notes && currentUnit.detailed_notes.trim()) { notesBox.innerHTML = renderMarkdownNotes(currentUnit.detailed_notes); notesSec.classList.remove('hidden'); } else { notesBox.innerHTML = ''; notesSec.classList.add('hidden'); } } // Practice Cheatsheet Drawer content const practiceDrawer = document.getElementById('grammar-practice-cheatsheet-drawer'); const practiceToggleWrap = document.querySelector('.practice-cheatsheet-toggle-wrap'); if (practiceDrawer && practiceToggleWrap) { if (currentUnit.cheatsheet_table && currentUnit.cheatsheet_table.trim()) { practiceDrawer.innerHTML = renderMarkdownTable(currentUnit.cheatsheet_table); practiceToggleWrap.classList.remove('hidden'); } else { practiceDrawer.innerHTML = ''; practiceToggleWrap.classList.add('hidden'); } practiceDrawer.classList.add('hidden'); const practiceToggle = document.getElementById('grammar-practice-cheatsheet-toggle'); if (practiceToggle) { practiceToggle.innerHTML = '📋 Шпаргалка по теме'; } } // Golden formula const formulaEl = document.getElementById('grammar-golden-formula-text'); if (formulaEl) { formulaEl.textContent = currentUnit.golden_formula; } // Journaling prompt const promptEl = document.getElementById('grammar-journaling-prompt-text'); if (promptEl) { promptEl.textContent = currentUnit.journaling_prompt; } // Contrasts const contrastsContainer = document.getElementById('grammar-contrasts-list'); if (contrastsContainer) { const contrasts = currentUnit.contrasts || []; if (contrasts.length === 0) { contrastsContainer.innerHTML = ''; } else { contrastsContainer.innerHTML = contrasts.map(c => `
Ситуация А ${escapeHtml(c.situation_a)}
${escapeHtml(c.example_a)}
${escapeHtml(c.trans_a)}
VS
Ситуация Б ${escapeHtml(c.situation_b)}
${escapeHtml(c.example_b)}
${escapeHtml(c.trans_b)}
💡 Ключевая разница: ${escapeHtml(c.key_difference)}
`).join(''); } } } // ----------------------------------------------------------------------- // Cloze Tap Practice Engine (5 micro-exercises) // ----------------------------------------------------------------------- function startUnitPractice() { if (!currentUnit) return; currentExerciseIdx = 0; isExerciseAnswered = false; sessionMistakes = 0; sessionCorrect = 0; sessionIngestedCards = []; // Ensure cheatsheet drawer is collapsed at practice start const practiceDrawer = document.getElementById('grammar-practice-cheatsheet-drawer'); const practiceToggle = document.getElementById('grammar-practice-cheatsheet-toggle'); if (practiceDrawer) practiceDrawer.classList.add('hidden'); if (practiceToggle) practiceToggle.innerHTML = '📋 Шпаргалка по теме'; renderExercise(); } function renderExercise() { if (!currentUnit || !currentUnit.exercises) return; const exercises = currentUnit.exercises; if (currentExerciseIdx >= exercises.length) { finishUnitPractice(); return; } const ex = exercises[currentExerciseIdx]; isExerciseAnswered = false; // Progress bar & dots const progressText = document.getElementById('grammar-practice-progress'); const dotsContainer = document.getElementById('grammar-practice-dots'); if (progressText) { progressText.textContent = `Задание ${currentExerciseIdx + 1} из ${exercises.length}`; } if (dotsContainer) { dotsContainer.innerHTML = exercises.map((_, i) => { let dotClass = 'dot'; if (i < currentExerciseIdx) dotClass += ' dot-done'; if (i === currentExerciseIdx) dotClass += ' dot-active'; return ``; }).join(''); } // Sentence with cloze blank const sentenceEl = document.getElementById('grammar-cloze-sentence'); const clueEl = document.getElementById('grammar-cloze-clue'); const feedbackBox = document.getElementById('grammar-exercise-feedback'); const nextBtn = document.getElementById('grammar-ex-next-btn'); if (feedbackBox) feedbackBox.classList.add('hidden'); if (nextBtn) nextBtn.classList.add('hidden'); if (sentenceEl) { const parts = ex.sentence.split('___'); sentenceEl.innerHTML = `${escapeHtml(parts[0] || '')}______${escapeHtml(parts[1] || '')}`; } if (clueEl) { clueEl.textContent = ex.clue ? `💡 Подсказка: ${ex.clue}` : ''; } // Tap options const optionsContainer = document.getElementById('grammar-options-container'); if (optionsContainer) { optionsContainer.innerHTML = ex.options.map((opt, i) => ` `).join(''); optionsContainer.querySelectorAll('.grammar-opt-btn').forEach(btn => { btn.addEventListener('click', () => { if (isExerciseAnswered) return; handleOptionSelect(btn.dataset.option); }); }); } } async function handleOptionSelect(selectedOption) { if (isExerciseAnswered || !currentUnit) return; isExerciseAnswered = true; const blank = document.getElementById('current-cloze-blank'); if (blank) blank.textContent = selectedOption; const allBtns = document.querySelectorAll('.grammar-opt-btn'); allBtns.forEach(b => b.disabled = true); try { const result = await api.grammar.submitAnswer(currentUnit.id, currentExerciseIdx, selectedOption); const feedbackBox = document.getElementById('grammar-exercise-feedback'); const nextBtn = document.getElementById('grammar-ex-next-btn'); allBtns.forEach(btn => { if (btn.dataset.option === result.correct_option) { btn.classList.add('btn-correct'); } else if (btn.dataset.option === selectedOption && !result.correct) { btn.classList.add('btn-wrong'); } }); if (blank) { blank.textContent = result.correct_option; blank.classList.add(result.correct ? 'blank-correct' : 'blank-wrong'); } if (result.correct) { sessionCorrect++; if (feedbackBox) { feedbackBox.className = 'grammar-feedback-box feedback-correct'; feedbackBox.innerHTML = `
Верно!
${escapeHtml(result.explanation)}
`; feedbackBox.classList.remove('hidden'); } } else { sessionMistakes++; if (result.card_created || result.card_updated) { sessionIngestedCards.push(result.card_id); } if (feedbackBox) { feedbackBox.className = 'grammar-feedback-box feedback-wrong'; feedbackBox.innerHTML = `
Неверно (правильно: ${escapeHtml(result.correct_option)})
${escapeHtml(result.explanation)}
Грамматический паттерн сохранён в ваши карточки для утреннего интервального повторения (SRS).
`; feedbackBox.classList.remove('hidden'); } } if (nextBtn) { nextBtn.classList.remove('hidden'); nextBtn.focus(); } } catch (err) { console.error('Error checking grammar answer:', err); showToast('Ошибка проверки ответа', 'error'); allBtns.forEach(b => b.disabled = false); isExerciseAnswered = false; } } function advanceExercise() { currentExerciseIdx++; renderExercise(); } async function finishUnitPractice() { if (!currentUnit) return; const total = currentUnit.exercises.length; const score = Math.round((sessionCorrect / total) * 100); // Save completion try { await api.grammar.completeUnit(currentUnit.id, score, Boolean(currentUnit.handwritten_logged)); currentUnit.is_completed = true; currentUnit.score = Math.max(currentUnit.score || 0, score); } catch (err) { console.error('Error saving unit completion:', err); } // Switch to summary tab switchLessonTab('summary'); const scoreValEl = document.getElementById('grammar-sum-score-val'); const scoreTextEl = document.getElementById('grammar-sum-score-text'); const ingestedEl = document.getElementById('grammar-sum-ingested-count'); const srsBtn = document.getElementById('grammar-sum-srs-btn'); const hwCard = document.getElementById('grammar-sum-hw-reminder'); if (scoreValEl) scoreValEl.textContent = `${score}%`; if (scoreTextEl) { if (score === 100) { scoreTextEl.textContent = '🎉 Идеально! Все 5 конструкций освоены без ошибок.'; } else if (score >= 80) { scoreTextEl.textContent = '👍 Отличный результат! Конструкция хорошо понята.'; } else { scoreTextEl.textContent = '💪 Хорошая практика! Ошибки зафиксированы для повторения.'; } } if (ingestedEl) { ingestedEl.textContent = sessionIngestedCards.length; } if (srsBtn) { srsBtn.classList.toggle('hidden', sessionIngestedCards.length === 0); } if (hwCard) { if (!currentUnit.handwritten_logged) { hwCard.classList.remove('hidden'); } else { hwCard.classList.add('hidden'); } } }