feat(materials): Фаза 6b — раздатка материала ученикам/классу
- POST /api/materials/:id/share {classId|userId} (teacher/admin): создаёт независимую КОПИЮ
материала каждому ученику класса (source_title «Раздатка: <учитель>») + уведомление через SSE.
- /my-materials: кнопка «Раздать» на карточках (видна учителю/админу) → выбор класса.
- Хелпер LS.shareMaterial. На этом план «Мои материалы» (6 фаз) завершён.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,8 @@
|
||||
<script src="/js/svg-sanitize.js"></script>
|
||||
<script src="/js/svg-draw.js"></script>
|
||||
<script>
|
||||
LS.initPage();
|
||||
const _ip = LS.initPage() || {};
|
||||
const _canShare = !!(_ip.isTeacher || _ip.isAdmin);
|
||||
|
||||
function esc(s) { return LS.escapeHtml(String(s || '')); }
|
||||
function fmtDate(s) {
|
||||
@@ -110,6 +111,8 @@
|
||||
? `<button class="mm-btn" onclick="annotate(${m.id})" title="Аннотировать (рисовать поверх)"><i data-lucide="pencil-ruler"></i></button>` : '';
|
||||
const fc = (m.kind === 'note')
|
||||
? `<button class="mm-btn" onclick="toFlashcard(${m.id})" title="В флешкарты"><i data-lucide="copy"></i></button>` : '';
|
||||
const sh = _canShare
|
||||
? `<button class="mm-btn" onclick="openShareModal(${m.id})" title="Раздать ученикам"><i data-lucide="send"></i></button>` : '';
|
||||
const mv = moveSelect(m);
|
||||
if (m.kind === 'board' || m.kind === 'image') {
|
||||
return `<div class="mm-card">
|
||||
@@ -122,7 +125,7 @@
|
||||
${mv}
|
||||
<a class="mm-btn" href="${esc(m.url)}" target="_blank" rel="noopener" title="Открыть"><i data-lucide="external-link"></i></a>
|
||||
<a class="mm-btn" href="${esc(m.url)}" download title="Скачать"><i data-lucide="download"></i></a>
|
||||
${ann}${edit}${del}
|
||||
${ann}${sh}${edit}${del}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -134,7 +137,7 @@
|
||||
<div class="mm-card-body">
|
||||
<div class="mm-card-title">${esc(m.title || kind)}</div>
|
||||
<div class="mm-card-meta">${meta}</div>
|
||||
<div class="mm-card-actions">${mv}${fc}${edit}${del}</div>
|
||||
<div class="mm-card-actions">${mv}${fc}${sh}${edit}${del}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -385,6 +388,29 @@
|
||||
}
|
||||
window.toFlashcard = toFlashcard;
|
||||
|
||||
/* ── Раздатка: учитель → класс (копия ученикам) ── */
|
||||
async function openShareModal(id) {
|
||||
let classes = [];
|
||||
try { classes = await LS.getClasses(); } catch (e) {}
|
||||
if (!Array.isArray(classes) || !classes.length) { LS.toast('Нет классов для раздачи', 'warn'); return; }
|
||||
const opts = classes.map(c => `<option value="${c.id}">${esc(c.name)}</option>`).join('');
|
||||
const content = `<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<label style="font-size:.8rem;color:var(--text-3)">Класс</label>
|
||||
<select id="sh-class" style="${FLD}">${opts}</select>
|
||||
<div style="font-size:.78rem;color:var(--text-3)">Копия материала появится у всех учеников класса (с уведомлением).</div>
|
||||
</div>`;
|
||||
const m = LS.modal({ title: 'Раздать материал', content, size: 'sm', actions: [
|
||||
{ label: 'Отмена', onClick: () => m.close() },
|
||||
{ label: 'Раздать', primary: true, onClick: async () => {
|
||||
try {
|
||||
const r = await LS.shareMaterial(id, { classId: Number(m.body.querySelector('#sh-class').value) });
|
||||
m.close(); LS.toast('Отправлено ученикам: ' + (r.sent || 0), 'success');
|
||||
} catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
|
||||
} },
|
||||
] });
|
||||
}
|
||||
window.openShareModal = openShareModal;
|
||||
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user