feat: textbooks — модуль учебников + чтение как ДЗ (3 фазы)
Фаза 1 — структура и каталог:
- frontend/textbooks/chemistry_9.html (Шиманович, 60 §) + physics_9.html (Исаченкова, 38 §)
- frontend/textbooks.html — каталог в стиле LearnSpace (карточки с обложками)
- Маршруты: /textbooks (каталог), /textbook/<slug> (полноэкранный учебник)
- Сайдбар: пункт «Учебники» (book-open-text)
- Feature flag feature_textbooks_enabled, hideDisabledFeatures map
Фаза 2 — прогресс в localStorage + UI чтения:
- frontend/js/textbook-tracker.js — инжектится в каждый учебник:
- «← Учебники» overlay-кнопка (top-left, semi-transparent)
- «Прочитано» чекбокс рядом с каждым §-заголовком
- Зелёный dot на pill уже прочитанных параграфов
- Авто-открытие последнего параграфа при возврате
- Каталог показывает прогресс-бар «X из Y прочитано» + кнопку «Продолжить»
Фаза 3 — серверный прогресс + назначение чтения как ДЗ:
- Таблица textbooks (slug, subject, grade, title, author, color, ...)
- Таблица textbook_progress (user_id, textbook_id, JSON read[], last_para)
- Колонки assignments.textbook_id + textbook_paragraphs
- API: GET /api/textbooks (с прогрессом), GET /:slug, POST /:slug/progress,
GET /:slug/class-progress (учитель)
- tracker.js синхронизирует прогресс через POST /progress (если залогинен)
- На каталоге у учителей кнопка «Назначить чтение» — модалка с выбором
классов + параграфы («1-5» или «1,3,5») + deadline
- bulkCreateAssignment расширен: принимает textbook_slug, резолвит в id
Миграция 004 идемпотентная; сиды двух учебников включены.
This commit is contained in:
@@ -51,6 +51,7 @@ const collectionRoutes = require('./routes/collection');
|
||||
const redBookRoutes = require('./routes/red-book');
|
||||
const parentRoutes = require('./routes/parent');
|
||||
const exam9Routes = require('./routes/exam9');
|
||||
const textbookRoutes = require('./routes/textbooks');
|
||||
|
||||
const { requestId, errorHandler } = require('./middleware/errorHandler');
|
||||
|
||||
@@ -168,6 +169,7 @@ app.use('/api/red-book', redBookRoutes);
|
||||
app.use('/api/biochem', require('./routes/biochem'));
|
||||
app.use('/api/parent', parentRoutes);
|
||||
app.use('/api/exam9', exam9Routes);
|
||||
app.use('/api/textbooks', textbookRoutes);
|
||||
|
||||
/* ── Public features endpoint (merges global + per-class for authenticated students) ── */
|
||||
const _featDb = require('./db/db');
|
||||
@@ -318,6 +320,23 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Clean URL for textbooks: /textbook/<slug> → frontend/textbooks/<html_path>
|
||||
const _textbookDb = require('./db/db');
|
||||
const _stmtTextbookPath = _textbookDb.prepare('SELECT html_path FROM textbooks WHERE slug=? AND is_active=1');
|
||||
app.get('/textbook/:slug', (req, res, next) => {
|
||||
const row = _stmtTextbookPath.get(req.params.slug);
|
||||
if (!row) return next();
|
||||
const filePath = path.join(frontendDir, 'textbooks', row.html_path);
|
||||
if (!isProd) res.setHeader('Cache-Control', 'no-store');
|
||||
res.sendFile(filePath, err => { if (err) next(); });
|
||||
});
|
||||
|
||||
// Catalog: /textbooks → frontend/textbooks.html (explicit to avoid conflict with /textbooks/ directory)
|
||||
app.get('/textbooks', (_req, res) => {
|
||||
if (!isProd) res.setHeader('Cache-Control', 'no-store');
|
||||
res.sendFile(path.join(frontendDir, 'textbooks.html'));
|
||||
});
|
||||
|
||||
// Serve HTML files without extension (/dashboard → dashboard.html)
|
||||
// In dev: disable cache so edits are always picked up immediately
|
||||
const htmlCacheOpts = isProd ? { extensions: ['html'] } : {
|
||||
|
||||
Reference in New Issue
Block a user