Files
MathApp/content/templates/admin/content/browser.html
Zareian 0132235026 take out a comment that was rendering as page text
`{# ... #}` در جنگو فقط تک‌خطی است. کامنتی که دیشب اضافه کردم سه خط بود، پس
جنگو کامنت حسابش نکرد و کلِ متنش بالای صفحه‌ی مرورِ محتوا چاپ شد.

کلاً برداشته شد. برای توضیحِ چندخطی در قالب باید {% comment %} استفاده شود،
ولی اینجا اصلاً توضیحی لازم نبود.

همه‌ی قالب‌های پروژه برای همین الگو بررسی شدند؛ جای دیگری نبود.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:05:18 +03:30

156 lines
8.1 KiB
HTML

{% extends "admin/base_site.html" %}
{% block content %}
<style>
/* رنگ‌ها از متغیرهای خودِ پنلِ ادمین می‌آیند، نه ثابت.
چرا: قبلاً هر پنل background روشن داشت ولی color نداشت. در حالتِ تیره‌ی
مرورگر، ادمین رنگِ متن را روشن می‌کند و نتیجه متنِ سفید روی زمینه‌ی سفید
می‌شد — یعنی صفحه عملاً خوانا نبود. مقدارِ دومِ هر var پشتیبانِ حالتِ روشن
است، پس اگر متغیری نبود هم چیزی خراب نمی‌شود.
چیپ‌های رنگی (badge/choice) عمداً زمینه‌ی روشنِ ثابت دارند و برای همین
رنگِ متنشان هم صریح و تیره ست شده تا در هر دو حالت خوانا بمانند. */
.cb { direction: rtl; color: var(--body-fg, #333); }
.cb-cols { display: flex; gap: 12px; align-items: stretch; flex-wrap: wrap; }
.cb-col { flex: 1 1 190px; min-width: 170px; border: 1px solid var(--border-color, #ddd);
border-radius: 8px; background: var(--body-bg, #fff); color: var(--body-fg, #333);
display: flex; flex-direction: column; max-height: 420px; }
.cb-col h3 { margin: 0; padding: 8px 10px; font-size: 13px;
background: var(--darkened-bg, #f6f6f6); color: var(--body-fg, #333);
border-bottom: 1px solid var(--border-color, #ddd); border-radius: 8px 8px 0 0; }
.cb-list { overflow-y: auto; flex: 1; }
.cb-item { padding: 7px 10px; cursor: pointer;
border-bottom: 1px solid var(--hairline-color, #f0f0f0);
font-size: 13px; display: flex; justify-content: space-between; gap: 8px; }
.cb-item:hover { background: var(--selected-row, #eef5ff); }
.cb-item.sel { background: var(--selected-bg, #dbeafe); font-weight: 600; }
.cb-count { color: var(--body-quiet-color, #888); font-size: 11px; }
.cb-empty { padding: 10px; color: var(--body-quiet-color, #999); font-size: 12px; }
.cb-q { border: 1px solid var(--border-color, #ddd); border-radius: 8px; padding: 12px;
margin-top: 10px; background: var(--body-bg, #fff); color: var(--body-fg, #333); }
.cb-q.flag { border-color: #f0a; border-right: 4px solid #e11d48; }
.cb-q-top { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; }
.cb-badges { display: flex; gap: 6px; flex-wrap: wrap; }
.cb-badge { font-size: 11px; padding: 2px 7px; border-radius: 10px; white-space: nowrap; }
.cb-badge.warn { background: #fee2e2; color: #b91c1c; }
.cb-badge.info { background: #f1f5f9; color: #475569; }
.cb-choices { margin: 8px 0 0; padding: 0; list-style: none; display: flex;
flex-wrap: wrap; gap: 6px; }
.cb-choices li { font-size: 12px; padding: 3px 9px; border-radius: 6px;
background: #f1f5f9; color: #334155; }
.cb-choices li.ok { background: #dcfce7; color: #15803d; font-weight: 600; }
</style>
<div class="cb">
<p style="color:var(--body-quiet-color,#666)">از راست به چپ انتخاب کنید تا به سوال‌ها برسید.</p>
<div class="cb-cols">
<div class="cb-col">
<h3>پایه</h3>
<div class="cb-list" id="col-grade">
{% for grade in grades %}
<div class="cb-item" data-id="{{ grade.id }}">{{ grade.title }}</div>
{% empty %}
<div class="cb-empty">هیچ پایه‌ای ثبت نشده.</div>
{% endfor %}
</div>
</div>
<div class="cb-col"><h3>فصل</h3><div class="cb-list" id="col-unit"><div class="cb-empty">اول پایه را انتخاب کنید.</div></div></div>
<div class="cb-col"><h3>درس</h3><div class="cb-list" id="col-node"><div class="cb-empty">—</div></div></div>
<div class="cb-col"><h3>مبحث</h3><div class="cb-list" id="col-lesson"><div class="cb-empty">—</div></div></div>
</div>
<div id="questions" style="margin-top:18px"></div>
</div>
<script>
(function () {
const el = (id) => document.getElementById(id);
const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
function setEmpty(col, text) { col.innerHTML = '<div class="cb-empty">' + esc(text) + '</div>'; }
async function load(url) {
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
}
function fill(col, rows, countKey, onPick) {
if (!rows.length) { setEmpty(col, 'چیزی اینجا نیست.'); return; }
col.innerHTML = '';
rows.forEach((row) => {
const item = document.createElement('div');
item.className = 'cb-item';
item.innerHTML = '<span>' + esc(row.title) + '</span>'
+ '<span class="cb-count">' + (row[countKey] ?? 0) + '</span>';
item.onclick = () => {
col.querySelectorAll('.cb-item').forEach((n) => n.classList.remove('sel'));
item.classList.add('sel');
onPick(row.id);
};
col.appendChild(item);
});
}
function renderQuestions(data) {
const box = el('questions');
let html = '<a class="addlink" href="' + data.addUrl + '">افزودنِ سوالِ تازه به این مبحث</a>';
if (!data.results.length) {
box.innerHTML = html + '<p style="color:var(--body-quiet-color,#999);margin-top:10px">هنوز سوالی برای این مبحث ساخته نشده.</p>';
return;
}
data.results.forEach((q) => {
const flagged = q.openReports > 0 || (q.wrongPercent !== null && q.wrongPercent >= 70);
let badges = '';
if (q.openReports > 0) {
badges += '<a class="cb-badge warn" href="' + q.reportsUrl + '">'
+ q.openReports + ' گزارشِ باز</a>';
}
if (q.wrongPercent !== null) {
badges += '<span class="cb-badge ' + (q.wrongPercent >= 70 ? 'warn' : 'info') + '">'
+ q.wrongPercent + '٪ غلط از ' + q.totalAnswers + ' پاسخ</span>';
} else if (q.totalAnswers) {
badges += '<span class="cb-badge info">' + q.totalAnswers + ' پاسخ</span>';
}
html += '<div class="cb-q' + (flagged ? ' flag' : '') + '">'
+ '<div class="cb-q-top"><div><b>' + esc(q.prompt) + '</b>'
+ '<div style="color:var(--body-quiet-color,#888);font-size:11px;margin-top:2px">' + esc(q.type) + ' · #' + q.id + '</div></div>'
+ '<div class="cb-badges">' + badges
+ '<a class="cb-badge info" href="' + q.editUrl + '">ویرایش</a></div></div>'
+ '<ul class="cb-choices">'
+ q.choices.map((c) => '<li class="' + (c.isCorrect ? 'ok' : '') + '">' + esc(c.value) + '</li>').join('')
+ '</ul></div>';
});
box.innerHTML = html;
}
function reset(...cols) { cols.forEach((c) => setEmpty(el(c), '—')); el('questions').innerHTML = ''; }
el('col-grade').onclick = null;
el('col-grade').querySelectorAll('.cb-item').forEach((item) => {
item.onclick = async () => {
el('col-grade').querySelectorAll('.cb-item').forEach((n) => n.classList.remove('sel'));
item.classList.add('sel');
reset('col-node', 'col-lesson');
setEmpty(el('col-unit'), 'در حالِ بارگذاری…');
const data = await load('units/' + item.dataset.id + '/');
fill(el('col-unit'), data.results, 'node_count', async (unitId) => {
reset('col-lesson');
setEmpty(el('col-node'), 'در حالِ بارگذاری…');
const nodes = await load('nodes/' + unitId + '/');
fill(el('col-node'), nodes.results, 'lesson_count', async (nodeId) => {
setEmpty(el('col-lesson'), 'در حالِ بارگذاری…');
el('questions').innerHTML = '';
const lessons = await load('lessons/' + nodeId + '/');
fill(el('col-lesson'), lessons.results, 'question_count', async (lessonId) => {
el('questions').innerHTML = '<p style="color:var(--body-quiet-color,#999)">در حالِ بارگذاری…</p>';
renderQuestions(await load('questions/' + lessonId + '/'));
});
});
});
};
});
})();
</script>
{% endblock %}