refactor: ещё 6 модалок → LS.modal (dashboard, theory, course)

dashboard.html: 2 → 0 
  - join-modal — вступить в класс
  - qs-modal — быстрый тест с выбором предмета + режим + кол-во

theory.html: 1 → 0 
  - new-course-modal — создание нового курса учителем

course.html: 4 → 0 
  - add-section-modal — новый раздел курса
  - edit-course-modal — редактирование курса
  - add-lesson-modal — новый урок
  - save-course-tpl-modal — сохранить курс как шаблон

Везде:
  - Inline <div class=\"modal-overlay\">...</div> → удалён
  - openX(): создаёт modal через LS.modal({content, actions})
  - closeX() удалена — _xModal.close()
  - Глобальный selectQsSubject() inline'нут как listener на body модалки
  - Enter-handler на главных inputs сохранён

Не трогаю:
  - biochem.html#lib-modal — кастомная тёмная тема, не подходит под
    светлый LS.modal без редизайна
  - library.html — 3 сложные модалки (folder-access, assign, upload)
    с tabs и dynamic state — отдельный заход
  - classes.html — modal-assign (128 строк, complex) + review-modal
  - flashcards.html — fc-modal (не modal-overlay, своя CSS)

Прогресс миграции: 12 простых модалок → LS.modal за серию (4 ранее
+ 2 ранее + 6 сейчас). 4 страницы полностью очищены от
modal-overlay. Унифицированы:
  - ESC/backdrop/focus поведение
  - z-index (9000)
  - Анимация (scale .22s)
  - Адаптив на мобилке
This commit is contained in:
Maxim Dolgolyov
2026-05-16 19:33:39 +03:00
parent d3b16f55c8
commit 5c66105fc2
3 changed files with 183 additions and 241 deletions
+52 -57
View File
@@ -1526,45 +1526,8 @@
</div>
<!-- Join modal -->
<div class="modal-overlay" id="join-modal" onclick="if(event.target===this)closeJoinModal()">
<div class="modal">
<div class="modal-title">Вступить в класс</div>
<input class="form-input" id="join-code" placeholder="Код приглашения" />
<div style="font-size:0.78rem;color:var(--text-3);margin-top:8px">Попросите учителя поделиться кодом или ссылкой</div>
<div class="modal-footer">
<button class="btn-cancel" onclick="closeJoinModal()">Отмена</button>
<button class="btn-join" id="btn-do-join" onclick="doJoin()">Вступить</button>
</div>
</div>
</div>
<!-- Quick-start test modal -->
<div class="modal-overlay" id="qs-modal" onclick="if(event.target===this)closeQuickStart()">
<div class="modal" style="max-width:480px">
<div class="modal-title">Быстрый тест</div>
<div class="qs-subjects" id="qs-subjects"></div>
<div class="qs-options">
<div class="qs-row">
<span class="qs-label">Режим</span>
<select class="qs-select" id="qs-mode">
<option value="exam">Экзамен</option>
<option value="practice">Тренировка</option>
<option value="random">Случайный</option>
</select>
</div>
<div class="qs-row">
<span class="qs-label">Вопросов</span>
<input class="qs-input" id="qs-count" type="number" min="5" max="50" value="25">
</div>
</div>
<div class="modal-footer">
<button class="btn-cancel" onclick="closeQuickStart()">Отмена</button>
<button class="btn-join" onclick="doQuickStart()">Начать</button>
</div>
</div>
</div>
<script src="/js/api.js"></script>
<!-- Quick-start test modal -->
<script src="/js/api.js"></script>
<script src="/js/sound.js"></script>
<script src="/js/sidebar.js"></script>
<script src="/js/notifications.js"></script>
@@ -2801,12 +2764,19 @@
/* ══ УВЕДОМЛЕНИЯ — handled by notifications.js ══ */
/* ══ JOIN MODAL ════════════════════════════════════════════════════════ */
let _joinModal = null;
function openJoinModal(code) {
document.getElementById('join-code').value = code || '';
document.getElementById('join-modal').classList.add('open');
setTimeout(() => document.getElementById('join-code').focus(), 50);
const body = `
<input class="form-input" id="join-code" placeholder="Код приглашения" value="${LS.esc(code || '')}" />
<div style="font-size:0.78rem;color:var(--text-3);margin-top:8px">Попросите учителя поделиться кодом или ссылкой</div>`;
_joinModal = LS.modal({
title: 'Вступить в класс', content: body, size: 'sm',
actions: [
{ label: 'Отмена', onClick: () => _joinModal.close() },
{ label: 'Вступить', primary: true, id: 'btn-do-join', onClick: doJoin },
],
});
}
function closeJoinModal() { document.getElementById('join-modal').classList.remove('open'); }
async function doJoin() {
const code = document.getElementById('join-code').value.trim();
if (!code) return;
@@ -2814,11 +2784,10 @@
btn.disabled = true;
try {
const r = await LS.joinClass(code);
closeJoinModal();
_joinModal?.close();
LS.toast(`Вы вступили в класс «${r.class_name}»!`, 'success');
loadAssignments();
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); }
finally { btn.disabled = false; }
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); btn.disabled = false; }
}
/* ══ SUBMISSIONS (student) ════════════════════════════════════════════ */
@@ -3439,33 +3408,59 @@
/* ══ B1: QUICK-START MODAL ═════════════════════════════════════════ */
let _qsSlug = null;
let _qsModal = null;
async function openQuickStart(slug) {
_qsSlug = slug || null;
let subjectsHtml = '';
try {
const subjects = await LS.getSubjects();
document.getElementById('qs-subjects').innerHTML = subjects.map(s => {
subjectsHtml = subjects.map(s => {
const color = SUBJ_COLORS[s.slug] || '#9B5DE5';
const active = s.slug === _qsSlug ? ' active' : '';
return `<button class="qs-subj-btn${active}" onclick="selectQsSubject('${s.slug}',this)">
return `<button class="qs-subj-btn${active}" data-slug="${s.slug}">
<div class="qs-subj-icon" style="background:${color}">${lci(ICONS[s.slug]||'book-open')}</div>
${esc(s.name)}
</button>`;
}).join('');
reIcons();
} catch {}
document.getElementById('qs-modal').classList.add('open');
const body = `
<div class="qs-subjects" id="qs-subjects">${subjectsHtml}</div>
<div class="qs-options">
<div class="qs-row">
<span class="qs-label">Режим</span>
<select class="qs-select" id="qs-mode">
<option value="exam">Экзамен</option>
<option value="practice">Тренировка</option>
<option value="random">Случайный</option>
</select>
</div>
<div class="qs-row">
<span class="qs-label">Вопросов</span>
<input class="qs-input" id="qs-count" type="number" min="5" max="50" value="25">
</div>
</div>`;
_qsModal = LS.modal({
title: 'Быстрый тест', content: body, size: 'sm',
actions: [
{ label: 'Отмена', onClick: () => _qsModal.close() },
{ label: 'Начать', primary: true, onClick: doQuickStart },
],
});
reIcons();
// Wire subject selection within modal
_qsModal.body.querySelectorAll('.qs-subj-btn').forEach(btn => {
btn.addEventListener('click', () => {
_qsSlug = btn.dataset.slug;
_qsModal.body.querySelectorAll('.qs-subj-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
}
function selectQsSubject(slug, btn) {
_qsSlug = slug;
document.querySelectorAll('.qs-subj-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
}
function closeQuickStart() { document.getElementById('qs-modal').classList.remove('open'); }
function doQuickStart() {
if (!_qsSlug) { LS.toast('Выберите предмет', 'warn'); return; }
const mode = document.getElementById('qs-mode').value;
const count = parseInt(document.getElementById('qs-count').value) || 25;
closeQuickStart();
_qsModal?.close();
window.location.href = `/test-run?subject=${_qsSlug}&mode=${mode}&count=${count}`;
}