feat(lessons): implement comprehensive lesson configurator, session runner and AI debrief (Beta)
This commit is contained in:
1 parent
31e105b2c2
commit
718e9797eb
12 files changed
+4093
-1
No files matched your search
@@ -10,6 +10,7 @@ import { initSrs, startSrsTrainingSession, openStatsDashboard } from './modules/
|
||||
import { initReader, loadBookshelf, openBook } from './modules/reader.js';
|
||||
import { initCefr, loadCefrOverview, startCefrQuiz } from './modules/cefr.js';
|
||||
import { initGrammar, loadGrammarUnits, openGrammarUnit } from './modules/grammar.js';
|
||||
import { initLessonModule, openLessonConfigurator } from './modules/lesson.js';
|
||||
|
||||
let currentUser = null;
|
||||
let isRouting = false;
|
||||
@@ -44,6 +45,7 @@ export function updateActiveNavState(viewName) {
|
||||
'reader': 'drawer-nav-reader',
|
||||
'cefr': 'drawer-nav-cefr',
|
||||
'grammar': 'drawer-nav-grammar',
|
||||
'lesson': 'drawer-nav-lesson',
|
||||
};
|
||||
const targetId = drawerMap[viewName];
|
||||
if (targetId) {
|
||||
@@ -73,6 +75,8 @@ export function applyInitialViewPreflight() {
|
||||
hash = '#cefr';
|
||||
} else if (lastView === 'grammar-view') {
|
||||
hash = '#grammar';
|
||||
} else if (lastView === 'lesson-view') {
|
||||
hash = '#lesson';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -82,6 +86,8 @@ export function applyInitialViewPreflight() {
|
||||
const readerView = document.getElementById('reader-view');
|
||||
const cefrView = document.getElementById('cefr-view');
|
||||
const grammarView = document.getElementById('grammar-view');
|
||||
const lessonView = document.getElementById('lesson-view');
|
||||
const lessonRunnerView = document.getElementById('lesson-runner-view');
|
||||
const bookshelfContainer = document.getElementById('bookshelf-container');
|
||||
const readerInterface = document.getElementById('reader-interface');
|
||||
const readerBookTitle = document.getElementById('reader-book-title');
|
||||
@@ -91,6 +97,8 @@ export function applyInitialViewPreflight() {
|
||||
if (srsView) srsView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (readerView) readerView.classList.remove('hidden');
|
||||
updateActiveNavState('reader');
|
||||
if (hash.includes('/book/')) {
|
||||
@@ -103,6 +111,8 @@ export function applyInitialViewPreflight() {
|
||||
if (readerView) readerView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (srsView) srsView.classList.remove('hidden');
|
||||
updateActiveNavState('srs');
|
||||
} else if (hash.startsWith('#cefr')) {
|
||||
@@ -110,6 +120,8 @@ export function applyInitialViewPreflight() {
|
||||
if (readerView) readerView.classList.add('hidden');
|
||||
if (srsView) srsView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.remove('hidden');
|
||||
updateActiveNavState('cefr');
|
||||
} else if (hash.startsWith('#grammar')) {
|
||||
@@ -117,8 +129,19 @@ export function applyInitialViewPreflight() {
|
||||
if (readerView) readerView.classList.add('hidden');
|
||||
if (srsView) srsView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.remove('hidden');
|
||||
updateActiveNavState('grammar');
|
||||
} else if (hash.startsWith('#lesson')) {
|
||||
if (dictionaryView) dictionaryView.classList.add('hidden');
|
||||
if (readerView) readerView.classList.add('hidden');
|
||||
if (srsView) srsView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.remove('hidden');
|
||||
updateActiveNavState('lesson');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +294,36 @@ export function openGrammarView(unitId = null, updateHash = true) {
|
||||
}
|
||||
}
|
||||
|
||||
export function openLessonView(presetId = null, updateHash = true) {
|
||||
const targetHash = presetId ? `#lesson/${presetId}` : '#lesson';
|
||||
if (updateHash && !window.location.hash.startsWith('#lesson')) {
|
||||
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');
|
||||
const grammarView = document.getElementById('grammar-view');
|
||||
const lessonView = document.getElementById('lesson-view');
|
||||
const lessonRunnerView = document.getElementById('lesson-runner-view');
|
||||
|
||||
if (dictionaryView) dictionaryView.classList.add('hidden');
|
||||
if (srsView) srsView.classList.add('hidden');
|
||||
if (readerView) readerView.classList.add('hidden');
|
||||
if (cefrView) cefrView.classList.add('hidden');
|
||||
if (grammarView) grammarView.classList.add('hidden');
|
||||
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
|
||||
if (lessonView) lessonView.classList.remove('hidden');
|
||||
|
||||
updateActiveNavState('lesson');
|
||||
try { localStorage.setItem('notes_active_view', 'lesson-view'); } catch (e) {}
|
||||
|
||||
openLessonConfigurator(presetId);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Route Handler
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -337,6 +390,10 @@ export async function handleRouting() {
|
||||
} else {
|
||||
openBookshelfView(false);
|
||||
}
|
||||
} else if (cleanHash.startsWith('lesson')) {
|
||||
const parts = cleanHash.split('/');
|
||||
const presetId = parts[1] || null;
|
||||
openLessonView(presetId, false);
|
||||
} else {
|
||||
openDictionaryView(false);
|
||||
}
|
||||
@@ -523,6 +580,13 @@ export function initNavigationDrawer() {
|
||||
window.location.hash = '#grammar';
|
||||
});
|
||||
}
|
||||
const drawerNavLesson = document.getElementById('drawer-nav-lesson');
|
||||
if (drawerNavLesson) {
|
||||
drawerNavLesson.addEventListener('click', () => {
|
||||
closeDrawer();
|
||||
window.location.hash = '#lesson';
|
||||
});
|
||||
}
|
||||
if (drawerNavStats) {
|
||||
drawerNavStats.addEventListener('click', () => {
|
||||
closeDrawer();
|
||||
@@ -721,6 +785,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initReader();
|
||||
initCefr();
|
||||
initGrammar();
|
||||
initLessonModule();
|
||||
|
||||
// 5. Global Navigation & Header Actions
|
||||
const navBrandHome = document.getElementById('nav-brand-home');
|
||||
|
||||
@@ -422,5 +422,57 @@ export const api = {
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to cancel task: ${resp.status}`);
|
||||
}
|
||||
},
|
||||
|
||||
// Comprehensive Lessons Configurator & Runner (Beta)
|
||||
lessons: {
|
||||
async getPresets() {
|
||||
const resp = await fetch('/api/lessons/presets');
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to get presets: ${resp.status}`);
|
||||
},
|
||||
async savePreset(payload) {
|
||||
const resp = await fetch('/api/lessons/presets', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to save preset: ${resp.status}`);
|
||||
},
|
||||
async deletePreset(presetId) {
|
||||
const resp = await fetch(`/api/lessons/presets/${presetId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to delete preset: ${resp.status}`);
|
||||
},
|
||||
async getGrammarRecommendations() {
|
||||
const resp = await fetch('/api/lessons/grammar-recommendations');
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to get grammar recommendations: ${resp.status}`);
|
||||
},
|
||||
async getArticles(topic = null) {
|
||||
let url = '/api/lessons/articles';
|
||||
if (topic) url += `?topic=${encodeURIComponent(topic)}`;
|
||||
const resp = await fetch(url);
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to get articles: ${resp.status}`);
|
||||
},
|
||||
async submitDebrief(payload) {
|
||||
const resp = await fetch('/api/lessons/debrief', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to submit debrief: ${resp.status}`);
|
||||
},
|
||||
async getHistory(limit = 15) {
|
||||
const resp = await fetch(`/api/lessons/history?limit=${limit}`);
|
||||
if (resp.ok) return await resp.json();
|
||||
throw new Error(`Failed to get lesson history: ${resp.status}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user