Add HF model auto-download with configurable AI env, and share link visibility toggles
This commit is contained in:
+87
-5
@@ -2,6 +2,81 @@ function esc(s){return String(s==null?'':s).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||'')}
|
||||
|
||||
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 = `Ссылка доступна ещё <strong>${text}</strong>`;
|
||||
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';
|
||||
@@ -98,6 +173,9 @@ function showError(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) {
|
||||
@@ -109,6 +187,8 @@ function renderShare(data, password) {
|
||||
if (data.date_from || data.date_to) chips.push(`Период: <b>${fmt(data.date_from)}</b> — <b>${fmt(data.date_to)}</b>`);
|
||||
document.getElementById('chips').innerHTML = chips.map(c => `<span class="chip">${c}</span>`).join('');
|
||||
|
||||
startCountdown(data.expires_at);
|
||||
|
||||
const msgEl = document.getElementById('shareMessage');
|
||||
if (data.message) {
|
||||
msgEl.textContent = data.message;
|
||||
@@ -133,7 +213,7 @@ function renderShare(data, password) {
|
||||
}
|
||||
|
||||
const pb = document.getElementById('photosBlock');
|
||||
if (data.photos && data.photos.length) {
|
||||
if (data.show_group_photos && data.photos && data.photos.length) {
|
||||
document.getElementById('photosGrid').innerHTML = data.photos.map(p => `
|
||||
<a class="gphoto" href="#" data-img="${esc(p.photo_path)}">
|
||||
<img src="${p.photo_path}" alt="${esc(p.caption || '')}" loading="lazy">
|
||||
@@ -150,7 +230,9 @@ function renderShare(data, password) {
|
||||
grid.innerHTML = '<div class="empty"><div class="big">🎈</div>Записей пока нет</div>';
|
||||
return;
|
||||
}
|
||||
const isAnonymized = data.anonymize_names;
|
||||
const isAnonymized = !data.show_student_names;
|
||||
const showStudentMessage = !!data.show_student_message;
|
||||
const showEntryDate = !!data.show_entry_date;
|
||||
grid.innerHTML = data.entries.map(e => `
|
||||
<div class="card">
|
||||
<div class="photo">
|
||||
@@ -161,10 +243,10 @@ function renderShare(data, password) {
|
||||
<div class="body">
|
||||
${!isAnonymized ? `<div class="name">${esc(e.student_name)}</div>` : ''}
|
||||
${!isAnonymized && e.group_name ? `<div><span class="group">${esc(e.group_name)}</span></div>` : ''}
|
||||
<div class="desc">${esc(e.description)}</div>
|
||||
${showStudentMessage && e.description ? `<div class="desc">${esc(e.description)}</div>` : ''}
|
||||
${filesHTML(e.files, password, currentToken)}
|
||||
</div>
|
||||
<div class="foot">${new Date(e.created_at).toLocaleString('ru',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}</div>
|
||||
${showEntryDate ? `<div class="foot">${new Date(e.created_at).toLocaleString('ru',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}</div>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
@@ -179,4 +261,4 @@ document.addEventListener('click', e => {
|
||||
|
||||
(async()=>{
|
||||
await loadShare();
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user