feat(cefr): CEFR level testing and automated gap ingestion engine (Task 15)
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user