From d8f2a7f98dff846c4f03efa4b10bef71e98cf4f9 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Mon, 22 Jun 2026 16:56:22 +0300 Subject: [PATCH] =?UTF-8?q?fix(features):=20=D0=B4=D0=BE=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D1=83=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=B8=D0=B7=20=D1=81?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B1=D0=B0=D1=80=D0=B0=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B8?= =?UTF-8?q?=20+=20=D1=82=D1=83=D0=BC=D0=B1=D0=BB=D0=B5=D1=80=20=C2=AB?= =?UTF-8?q?=D0=A2=D0=B5=D0=BE=D1=80=D0=B8=D1=8F=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/src/controllers/adminController.js | 2 +- frontend/js/admin/sections/games.js | 1 + js/api.js | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/adminController.js b/backend/src/controllers/adminController.js index 9d4fa68..f6daa45 100644 --- a/backend/src/controllers/adminController.js +++ b/backend/src/controllers/adminController.js @@ -525,7 +525,7 @@ function getFeatures(_req, res) { function updateFeatures(req, res) { const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection', 'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom', - 'gamification', 'assistant', 'sim_builder', 'quantik']; + 'gamification', 'assistant', 'sim_builder', 'quantik', 'theory']; const updates = req.body; const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)"); const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?"); diff --git a/frontend/js/admin/sections/games.js b/frontend/js/admin/sections/games.js index c5ec61f..9821ee1 100644 --- a/frontend/js/admin/sections/games.js +++ b/frontend/js/admin/sections/games.js @@ -13,6 +13,7 @@ { key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для запоминания терминов и понятий методом интервальных повторений', icon: 'square-stack' }, { key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце, аватаре, доске', icon: 'image' }, { key: 'knowledge_map', label: 'Карта знаний', desc: 'Визуальная карта тем и связей между биологическими понятиями', icon: 'share-2' }, + { key: 'theory', label: 'Теория', desc: 'Раздел «Теория»: учебные курсы и уроки для учеников', icon: 'brain' }, { key: 'board', label: 'Доска', desc: 'Классная доска с объявлениями, постами и обсуждениями', icon: 'layout-dashboard'}, { key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' }, { key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' }, diff --git a/js/api.js b/js/api.js index 6b64293..020bc02 100644 --- a/js/api.js +++ b/js/api.js @@ -839,9 +839,13 @@ async function showBoardIfAllowed() { if (!el) return; const user = getUser(); 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; } // Student: check if in a class - const feats = await loadFeatures(); if (!feats._no_class) el.style.display = ''; } @@ -864,6 +868,7 @@ async function hideDisabledFeatures() { exam9: ['/exam9', '/exam9.html'], textbooks: ['/textbooks', '/textbooks.html', '/textbook'], quantik: ['/quantik', '/quantik.html'], + theory: ['/theory', '/theory.html'], }; for (const [key, hrefs] of Object.entries(map)) { if (feats[key] === false) {