fix(features): доска уходит из сайдбара при отключении + тумблер «Теория»
1) Доска при feature_board_enabled=0 не пропадала у учителя/админа: showBoardIfAllowed() зовётся напрямую на ~20 страницах и показывала доску БЕЗ проверки флага, перекрывая hideDisabledFeatures(). Теперь функция сперва грузит features и при board===false держит ссылку скрытой (для всех ролей). 2) Добавлен фич-флаг theory: ключ в вайтлист updateFeatures (backend), тумблер «Теория» в admin → games (GAME_FEATURES), и /theory,/theory.html в map hideDisabledFeatures (скрытие из сайдбара + редирект с /theory при выключении). По умолчанию ВКЛ (opt-in disable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
|
|||||||
function updateFeatures(req, res) {
|
function updateFeatures(req, res) {
|
||||||
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
||||||
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
||||||
'gamification', 'assistant', 'sim_builder', 'quantik'];
|
'gamification', 'assistant', 'sim_builder', 'quantik', 'theory'];
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
||||||
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для запоминания терминов и понятий методом интервальных повторений', icon: 'square-stack' },
|
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для запоминания терминов и понятий методом интервальных повторений', icon: 'square-stack' },
|
||||||
{ key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце, аватаре, доске', icon: 'image' },
|
{ key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце, аватаре, доске', icon: 'image' },
|
||||||
{ key: 'knowledge_map', label: 'Карта знаний', desc: 'Визуальная карта тем и связей между биологическими понятиями', icon: 'share-2' },
|
{ key: 'knowledge_map', label: 'Карта знаний', desc: 'Визуальная карта тем и связей между биологическими понятиями', icon: 'share-2' },
|
||||||
|
{ key: 'theory', label: 'Теория', desc: 'Раздел «Теория»: учебные курсы и уроки для учеников', icon: 'brain' },
|
||||||
{ key: 'board', label: 'Доска', desc: 'Классная доска с объявлениями, постами и обсуждениями', icon: 'layout-dashboard'},
|
{ key: 'board', label: 'Доска', desc: 'Классная доска с объявлениями, постами и обсуждениями', icon: 'layout-dashboard'},
|
||||||
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
||||||
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
||||||
|
|||||||
@@ -839,9 +839,13 @@ async function showBoardIfAllowed() {
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
const user = getUser();
|
const user = getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
const feats = await loadFeatures();
|
||||||
|
// Фича выключена (глобально или для класса) → доску не показываем, даже учителю/админу.
|
||||||
|
// Эта функция зовётся напрямую на многих страницах, поэтому проверка ОБЯЗАТЕЛЬНА,
|
||||||
|
// иначе она перекрывает скрытие из hideDisabledFeatures().
|
||||||
|
if (feats.board === false) { el.style.display = 'none'; return; }
|
||||||
if (user.role === 'teacher' || user.role === 'admin') { el.style.display = ''; return; }
|
if (user.role === 'teacher' || user.role === 'admin') { el.style.display = ''; return; }
|
||||||
// Student: check if in a class
|
// Student: check if in a class
|
||||||
const feats = await loadFeatures();
|
|
||||||
if (!feats._no_class) el.style.display = '';
|
if (!feats._no_class) el.style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,6 +868,7 @@ async function hideDisabledFeatures() {
|
|||||||
exam9: ['/exam9', '/exam9.html'],
|
exam9: ['/exam9', '/exam9.html'],
|
||||||
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
|
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
|
||||||
quantik: ['/quantik', '/quantik.html'],
|
quantik: ['/quantik', '/quantik.html'],
|
||||||
|
theory: ['/theory', '/theory.html'],
|
||||||
};
|
};
|
||||||
for (const [key, hrefs] of Object.entries(map)) {
|
for (const [key, hrefs] of Object.entries(map)) {
|
||||||
if (feats[key] === false) {
|
if (feats[key] === false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user