feat(cefr): CEFR level testing and automated gap ingestion engine (Task 15)

This commit is contained in:
Antigravity committed 2026-10-06 15:24:33 +03:00
1 parent 1c3f03a64c
commit 3fae321322
11 files changed
+2926 -10

No files matched your search

+75
View File
@@ -8,6 +8,7 @@ import { initTheme, openModal, closeModal, showToast } from './modules/ui.js';
import { initDictionary, loadInitialData } from './modules/dictionary.js';
import { initSrs, startSrsTrainingSession, openStatsDashboard } from './modules/srs.js';
import { initReader, loadBookshelf, openBook } from './modules/reader.js';
import { initCefr, loadCefrOverview, startCefrQuiz } from './modules/cefr.js';
let currentUser = null;
let isRouting = false;
@@ -19,6 +20,7 @@ export function updateMobileActiveTab(tabName) {
document.querySelectorAll('.mobile-tab-bar .tab-item').forEach(btn => {
btn.classList.remove('active');
});
if (!tabName) return;
const target = document.getElementById(`tab-nav-${tabName}`);
if (target) target.classList.add('active');
}
@@ -36,6 +38,8 @@ export function applyInitialViewPreflight() {
hash = lastBookId ? `#reader/book/${lastBookId}` : '#reader';
} else if (lastView === 'srs-view') {
hash = '#srs';
} else if (lastView === 'cefr-view') {
hash = '#cefr';
}
} catch (e) {}
}
@@ -43,6 +47,7 @@ export function applyInitialViewPreflight() {
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
const bookshelfContainer = document.getElementById('bookshelf-container');
const readerInterface = document.getElementById('reader-interface');
const readerBookTitle = document.getElementById('reader-book-title');
@@ -50,6 +55,7 @@ export function applyInitialViewPreflight() {
if (hash.startsWith('#reader') || hash === '#bookshelf') {
if (dictionaryView) dictionaryView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (readerView) readerView.classList.remove('hidden');
updateMobileActiveTab('reader');
if (hash.includes('/book/')) {
@@ -60,8 +66,15 @@ export function applyInitialViewPreflight() {
} else if (hash.startsWith('#srs')) {
if (dictionaryView) dictionaryView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (srsView) srsView.classList.remove('hidden');
updateMobileActiveTab('srs');
} else if (hash.startsWith('#cefr')) {
if (dictionaryView) dictionaryView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.remove('hidden');
updateMobileActiveTab('');
}
}
@@ -76,9 +89,11 @@ export function openDictionaryView(updateHash = true) {
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
if (srsView) srsView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (dictionaryView) dictionaryView.classList.remove('hidden');
updateMobileActiveTab('dict');
try { localStorage.setItem('notes_active_view', 'dictionary-view'); } catch (e) {}
@@ -94,9 +109,11 @@ export function openSrsView(tagFilter = null, updateHash = true) {
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
if (dictionaryView) dictionaryView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (srsView) srsView.classList.remove('hidden');
updateMobileActiveTab('srs');
try { localStorage.setItem('notes_active_view', 'srs-view'); } catch (e) {}
@@ -115,11 +132,13 @@ export function openBookshelfView(updateHash = true) {
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
const bookshelfContainer = document.getElementById('bookshelf-container');
const readerInterface = document.getElementById('reader-interface');
if (dictionaryView) dictionaryView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (readerView) readerView.classList.remove('hidden');
if (bookshelfContainer) bookshelfContainer.classList.remove('hidden');
if (readerInterface) readerInterface.classList.add('hidden');
@@ -140,6 +159,36 @@ export function openReaderView(updateHash = true) {
}
}
export function openCefrView(level = null, mode = 'all', updateHash = true) {
const targetHash = level
? (mode === 'mistakes' ? `#cefr/${encodeURIComponent(level)}/mistakes` : `#cefr/${encodeURIComponent(level)}`)
: '#cefr';
if (updateHash && window.location.hash !== targetHash) {
window.location.hash = targetHash;
return;
}
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
if (dictionaryView) dictionaryView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.remove('hidden');
updateMobileActiveTab('');
try { localStorage.setItem('notes_active_view', 'cefr-view'); } catch (e) {}
if (level) {
startCefrQuiz(level, mode);
} else {
loadCefrOverview();
}
}
// -----------------------------------------------------------------------
// Route Handler
// -----------------------------------------------------------------------
@@ -160,6 +209,10 @@ export async function handleRouting() {
hash = '#srs';
window.location.hash = hash;
return;
} else if (lastView === 'cefr-view') {
hash = '#cefr';
window.location.hash = hash;
return;
} else {
hash = '#dictionary';
}
@@ -176,6 +229,11 @@ export async function handleRouting() {
const parts = cleanHash.split('/');
const tag = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('/')) : null;
openSrsView(tag, false);
} else if (cleanHash.startsWith('cefr')) {
const parts = cleanHash.split('/');
const level = parts[1] || null;
const mode = parts[2] === 'mistakes' ? 'mistakes' : 'all';
openCefrView(level, mode, false);
} else if (cleanHash.startsWith('reader') || cleanHash === 'bookshelf') {
const parts = cleanHash.split('/');
if (parts[1] === 'book' && parts[2]) {
@@ -297,6 +355,14 @@ function initMobileNavigation() {
});
}
const mobileCefrBtn = document.getElementById('mobile-cefr-btn');
if (mobileCefrBtn) {
mobileCefrBtn.addEventListener('click', () => {
closeMobileMenu();
openCefrView(null, 'all', true);
});
}
if (mobileStatsBtn) {
mobileStatsBtn.addEventListener('click', () => {
closeMobileMenu();
@@ -432,11 +498,14 @@ document.addEventListener('DOMContentLoaded', () => {
initDictionary();
initSrs();
initReader();
initCefr();
// 5. Global Navigation & Header Actions
const navBrandHome = document.getElementById('nav-brand-home');
const navReaderBtn = document.getElementById('nav-reader-btn');
const navCefrBtn = document.getElementById('nav-cefr-btn');
const srsTrainerBtn = document.getElementById('srs-trainer-btn');
const srsJumpCefrBtn = document.getElementById('srs-jump-cefr-btn');
const loginForm = document.getElementById('login-form');
const loginUsername = document.getElementById('login-username');
const loginPassword = document.getElementById('login-password');
@@ -449,9 +518,15 @@ document.addEventListener('DOMContentLoaded', () => {
if (navReaderBtn) {
navReaderBtn.addEventListener('click', () => { window.location.hash = '#reader'; });
}
if (navCefrBtn) {
navCefrBtn.addEventListener('click', () => { window.location.hash = '#cefr'; });
}
if (srsTrainerBtn) {
srsTrainerBtn.addEventListener('click', () => { window.location.hash = '#srs'; });
}
if (srsJumpCefrBtn) {
srsJumpCefrBtn.addEventListener('click', () => { window.location.hash = '#cefr'; });
}
// 6. Mobile Tab Bar & Drawer
initMobileNavigation();