'use strict'; /* Admin shared helpers — referenced by admin.js orchestrator + every section module. * Exposed on window.AdminCtx (filled by admin.js after LS.initPage()) and * on window directly for utility functions used by HTML onclicks. */ (function () { 'use strict'; /* ─── Constants ─── */ const MODES = { exam:'Экзамен', practice:'Тренировка', repeat:'Обычный', ct:'ЦТ/ЦЭ', topic:'По теме', random:'Случайный' }; const DIFFS = { 1:'Лёгкий', 2:'Средний', 3:'Сложный' }; const DIFF_LABELS = DIFFS; const TYPE_LABELS = { single:'Один', multi:'Несколько', true_false:'Верно/Нет', short_answer:'Краткий', matching:'Сопоставление' }; /* ─── Generic formatters ─── */ function pctClass(p) { return p === null ? '' : p >= 75 ? 'pct-hi' : p >= 50 ? 'pct-mid' : 'pct-lo'; } function fmtDate(d) { return new Date(d).toLocaleDateString('ru', { day:'numeric', month:'short', year:'numeric' }); } function fmtTime(sec) { if (!sec || sec < 0) return '—'; const m = Math.floor(sec / 60), s = sec % 60; return m ? `${m} мин ${s} сек` : `${s} сек`; } function fmtDuration(sec) { if (!sec || sec < 0) return '—'; const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60; if (h) return `${h}ч ${m}м`; if (m) return `${m} мин ${s} сек`; return `${s} сек`; } /* ─── KaTeX rendering ─── */ const KATEX_OPTS = { delimiters: [ { left: '\\(', right: '\\)', display: false }, { left: '\\[', right: '\\]', display: true }, ], throwOnError: false, }; function renderMath(el) { if (!el) return; const run = () => { if (window.renderMathInElement) renderMathInElement(el, KATEX_OPTS); }; if (window._katexReady) run(); else window._katexCb = run; } /* ─── Question type badges (used by tests + subjects sections) ─── */ function qTypeBadge(type) { const MAP = { single:'Один', multi:'Несколько', true_false:'Верно/Нет', short_answer:'Ответ', matching:'Сопост.' }; const CLR = { single:'rgba(155,93,229,0.12)', multi:'rgba(6,214,224,0.12)', true_false:'rgba(255,179,71,0.14)', short_answer:'rgba(6,214,100,0.12)', matching:'rgba(241,91,181,0.10)' }; const TXT = { single:'var(--violet)', multi:'#05aab3', true_false:'var(--amber)', short_answer:'var(--green)', matching:'var(--pink)' }; return `${MAP[type]||type}`; } function qOptsPreview(q) { if (q.type === 'short_answer') return q.correct_text ? `Ответ: ${esc(q.correct_text)}` : ''; if (!q.options?.length) return ''; const correct = q.options.filter(o => o.is_correct).map(o => esc(o.text)).join(', '); return ` ${correct}`; } /* ─── Pagination controls (users + future tables) ─── */ function ensurePgnStyles() { if (document.getElementById('pgn-bar-style')) return; const s = document.createElement('style'); s.id = 'pgn-bar-style'; s.textContent = ` .pgn-bar { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:14px 4px 4px; font-size:0.85rem; color:var(--text-3); } .pgn-info { font-weight:600; } .pgn-ctrls { display:flex; align-items:center; gap:4px; } .pgn-btn { min-width:32px; height:32px; padding:0 10px; border:1px solid var(--border); background:var(--surface); border-radius:8px; cursor:pointer; font-weight:600; font-family:inherit; font-size:0.85rem; color:var(--text-2); transition:background .12s, color .12s, border-color .12s; } .pgn-btn:hover:not(:disabled) { background:rgba(155,93,229,.08); color:var(--violet); border-color:rgba(155,93,229,.3); } .pgn-btn.active { background:var(--violet); color:#fff; border-color:var(--violet); } .pgn-btn:disabled { opacity:.4; cursor:not-allowed; } .pgn-ellip { padding:0 6px; color:var(--text-3); } `; document.head.appendChild(s); } function renderPgnControls(elId, page, total, perPage, gotoFn) { const bar = document.getElementById(elId); if (!bar) return; const pages = Math.max(1, Math.ceil(total / perPage)); if (pages <= 1) { bar.style.display = 'none'; return; } ensurePgnStyles(); const from = (page - 1) * perPage + 1; const to = Math.min(page * perPage, total); const nums = new Set([1, pages, page, page - 1, page + 1, page - 2, page + 2]); const sorted = [...nums].filter(n => n >= 1 && n <= pages).sort((a, b) => a - b); const numHtml = sorted.map((n, i) => { const prev = sorted[i - 1]; const gap = prev && n - prev > 1 ? '' : ''; return `${gap}`; }).join(''); bar.innerHTML = `
${from}–${to} из ${total}
${numHtml}
`; bar.style.display = ''; } /* ─── Export ─── */ window.AdminCtx = window.AdminCtx || { // filled by admin.js after LS.initPage(): user: null, isTeacher: false, isAdmin: false, // constants: MODES, DIFFS, DIFF_LABELS, TYPE_LABELS, // formatters: pctClass, fmtDate, fmtTime, fmtDuration, // rendering: renderMath, qTypeBadge, qOptsPreview, // pagination: renderPgnControls, ensurePgnStyles, }; window.AdminSections = window.AdminSections || {}; })();