function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"')} function fmt(d){return d?d.split('-').reverse().join('.'):'весь период'} function showImg(src){document.getElementById('lightboxImg').src=src;document.getElementById('lightbox').classList.add('open')} function isImageFile(name){return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')} function thumbSrc(p){ if(!p)return p; if(p.startsWith('/uploads/'))return '/uploads/thumb/'+p.slice('/uploads/'.length); if(p.includes('/api/files/'))return p+(p.includes('?')?'&':'?')+'thumb=1'; return p; } let countdownInterval = null; let countdownTarget = null; let countdownScrollHandler = null; function formatRemaining(ms) { if (ms <= 0) return null; const totalSec = Math.floor(ms / 1000); const days = Math.floor(totalSec / 86400); const hours = Math.floor((totalSec % 86400) / 3600); const minutes = Math.floor((totalSec % 3600) / 60); const seconds = totalSec % 60; const parts = []; if (days > 0) parts.push(`${days} ${declension(days, 'день', 'дня', 'дней')}`); if (hours > 0) parts.push(`${hours} ${declension(hours, 'час', 'часа', 'часов')}`); if (minutes > 0) parts.push(`${minutes} ${declension(minutes, 'минута', 'минуты', 'минут')}`); if (days === 0) parts.push(`${seconds} ${declension(seconds, 'секунда', 'секунды', 'секунд')}`); return parts.join(' '); } function declension(n, one, few, many) { const mod10 = n % 10; const mod100 = n % 100; if (mod100 >= 11 && mod100 <= 19) return many; if (mod10 === 1) return one; if (mod10 >= 2 && mod10 <= 4) return few; return many; } function shouldShowCountdown() { const hero = document.querySelector('.hero'); if (!hero) return true; return window.scrollY > hero.offsetHeight - 80; } function syncCountdownVisibility() { const el = document.getElementById('countdown'); if (!countdownTarget) { el.classList.remove('visible'); return; } if (countdownTarget - Date.now() <= 0) { el.classList.remove('visible'); return; } if (shouldShowCountdown()) el.classList.add('visible'); else el.classList.remove('visible'); } function updateCountdown() { const el = document.getElementById('countdown'); if (!countdownTarget) { el.classList.remove('visible'); return; } const now = Date.now(); const remaining = countdownTarget - now; if (remaining <= 0) { el.classList.remove('visible'); if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } return; } const text = formatRemaining(remaining); if (!text) { el.classList.remove('visible'); return; } el.classList.remove('warn', 'urgent'); if (remaining < 3600000) el.classList.add('urgent'); else if (remaining < 86400000) el.classList.add('warn'); el.querySelector('.cd-text').innerHTML = `Ссылка доступна ещё ${text}`; syncCountdownVisibility(); } function startCountdown(expiresAt) { if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } if (countdownScrollHandler) { window.removeEventListener('scroll', countdownScrollHandler); countdownScrollHandler = null; } const el = document.getElementById('countdown'); if (!expiresAt) { el.classList.remove('visible'); return; } countdownTarget = new Date(expiresAt).getTime(); countdownScrollHandler = syncCountdownVisibility; window.addEventListener('scroll', countdownScrollHandler, { passive: true }); updateCountdown(); countdownInterval = setInterval(updateCountdown, 1000); } function fileType(name){ if(/\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')) return 'image'; if(/\.(mp3|wav|ogg|flac|m4a|aac|opus)$/i.test(name||'')) return 'music'; if(/\.(pptx?|odp|key)$/i.test(name||'')) return 'presentation'; if(/\.(pdf|docx?|xlsx?|txt|rtf|odt|ods|csv|md)$/i.test(name||'')) return 'document'; return 'other'; } function fileIcon(t){ const icons = {image:'🖼️',music:'🎵',presentation:'📊',document:'📄',other:'📁'}; return icons[t]||icons.other; } function filesHTML(files, password, shareToken){ if(!files||!files.length)return ''; const pw = password ? `?password=${encodeURIComponent(password)}` : ''; return `
Работы резидента
${files.map(f=>{ const t = fileType(f.name); const url = `/api/share/${shareToken}/files/${f.token}${pw}`; return ` ${fileIcon(t)} `; }).join('')}
`; } let currentToken = ''; let currentPassword = null; async function fetchShareData(token, password) { const headers = {}; if (password) headers['x-share-password'] = password; const res = await fetch('/api/share/' + token, { headers }); const data = await res.json().catch(() => ({})); return { ok: res.ok, status: res.status, data }; } async function loadShare() { const token = location.pathname.split('/').filter(Boolean).pop() || ''; currentToken = token; currentPassword = null; const tryLoad = async (pwd) => { const result = await fetchShareData(token, pwd); if (result.ok) { renderShare(result.data, pwd); return true; } if (result.status === 401 && result.data.passwordRequired) { showPasswordModal(); return false; } if (result.status === 410) { showError('Срок действия ссылки истёк'); return true; } showError('Ссылка не найдена или была удалена'); return true; }; await tryLoad(null); } function showPasswordModal() { document.getElementById('passwordInput').value = ''; document.getElementById('passwordModal').classList.add('open'); document.getElementById('passwordInput').focus(); } function hidePasswordModal() { document.getElementById('passwordModal').classList.remove('open'); } async function submitPassword() { const pwd = document.getElementById('passwordInput').value; if (!pwd) return; const result = await fetchShareData(currentToken, pwd); if (result.ok) { hidePasswordModal(); currentPassword = pwd; renderShare(result.data, pwd); } else { alert('Неверный пароль'); document.getElementById('passwordInput').value = ''; document.getElementById('passwordInput').focus(); } } function showError(msg) { document.getElementById('title').textContent = msg; document.getElementById('grid').innerHTML = `
🤔
${esc(msg)}
`; document.getElementById('photosBlock').style.display = 'none'; document.getElementById('shareMessage').style.display = 'none'; document.getElementById('shareLinkBlock').style.display = 'none'; document.getElementById('countdown').classList.remove('visible'); if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } if (countdownScrollHandler) { window.removeEventListener('scroll', countdownScrollHandler); countdownScrollHandler = null; } } function renderShare(data, password) { const heading = data.group_name || data.name || 'Записи занятий'; document.title = heading; document.getElementById('title').textContent = heading; const chips = []; if (data.group_name) chips.push(`Группа: ${esc(data.group_name)}`); if (data.date_from || data.date_to) chips.push(`Период: ${fmt(data.date_from)} — ${fmt(data.date_to)}`); document.getElementById('chips').innerHTML = chips.map(c => `${c}`).join(''); startCountdown(data.expires_at); const msgEl = document.getElementById('shareMessage'); if (data.message) { msgEl.textContent = data.message; msgEl.style.display = 'block'; } else { msgEl.style.display = 'none'; } const linkBlock = document.getElementById('shareLinkBlock'); linkBlock.innerHTML = ''; if (data.link_url && /^https?:\/\//i.test(data.link_url)) { const a = document.createElement('a'); a.className = 'share-link-btn'; a.href = data.link_url; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.textContent = '🔗 Перейти по ссылке'; linkBlock.appendChild(a); linkBlock.style.display = 'block'; } else { linkBlock.style.display = 'none'; } const pb = document.getElementById('photosBlock'); if (data.show_group_photos && data.photos && data.photos.length) { document.getElementById('photosGrid').innerHTML = data.photos.map(p => ` ${esc(p.caption || '')} ${p.caption ? `
${esc(p.caption)}
` : ''}
`).join(''); pb.style.display = 'block'; } else { pb.style.display = 'none'; } const grid = document.getElementById('grid'); if (!data.entries.length) { grid.innerHTML = '
🎈
Записей пока нет
'; return; } const isAnonymized = !data.show_student_names; const showStudentMessage = !!data.show_student_message; const showEntryDate = !!data.show_entry_date; grid.innerHTML = data.entries.map(e => `
${e.photo_path ? `` : `
`}
${!isAnonymized ? `
${esc(e.student_name)}
` : ''} ${!isAnonymized && e.group_name ? `
${esc(e.group_name)}
` : ''} ${showStudentMessage && e.description ? `
${esc(e.description)}
` : ''} ${filesHTML(e.files, password, currentToken)}
${showEntryDate ? `
${new Date(e.created_at).toLocaleString('ru',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
` : ''}
`).join(''); } document.getElementById('lightbox').addEventListener('click', () => document.getElementById('lightbox').classList.remove('open')); document.getElementById('passwordModal').querySelector('button').addEventListener('click', submitPassword); document.addEventListener('click', e => { const imgEl = e.target.closest('[data-img]'); if (imgEl) { e.preventDefault(); showImg(imgEl.dataset.img); } }); (async()=>{ await loadShare(); })();