// ── CONFIG (直接將 Worker 網址寫死,學生手機就不會迷路了) ──
const API = 'https://misty-bonus-b5baclassroom-qa.lingochen.workers.dev';
let pollTimer = null;
// ── API (加入時間戳記防快取,解決手機畫面不同步的問題) ──
async function apiGet() {
// 加上 ?t=時間戳記,強迫手機每次都抓取最新狀態,不使用舊暫存
const r = await fetch(API + '/state?t=' + Date.now());
if (!r.ok) throw new Error('fetch failed');
return r.json();
}
async function apiPost(path, body) {
const r = await fetch(API + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!r.ok) throw new Error('post failed');
return r.json();
}
// ── NAV ──
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(id).classList.add('active');
}
function goHome() { stopPolling(); showScreen('home'); }
function goTeacher() { showScreen('teacher'); poll('teacher'); startPolling('teacher'); buildSharePanel(); }
function goStudent() { studentSubmitted = false; showScreen('student'); poll('student'); startPolling('student'); }
function startPolling(mode) {
stopPolling();
pollTimer = setInterval(() => poll(mode), 2000);
}
function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
async function poll(mode) {
try {
const state = await apiGet();
if (mode === 'teacher') renderTeacher(state);
else renderStudent(state);
} catch(e) { console.error(e); }
}
// ── TEACHER ──
function renderTeacher(state) {
const badge = document.getElementById('statusBadge');
if (state.status === 'open')
badge.innerHTML = `作答開放中`;
else if (state.status === 'closed')
badge.innerHTML = `作答已關閉`;
else badge.innerHTML = '';
document.getElementById('publishBtn').style.display = state.status === 'open' ? 'none' : '';
document.getElementById('closeBtn').style.display = state.status === 'open' ? '' : 'none';
document.getElementById('sharePanel').classList.toggle('hidden', state.status !== 'open');
const qi = document.getElementById('questionInput');
if (state.question && !qi.value) qi.value = state.question;
const answers = state.answers || [];
document.getElementById('countChip').textContent = answers.length + ' 份';
const grid = document.getElementById('answersGrid');
if (!answers.length) {
grid.innerHTML = `
`;
return;
}
grid.innerHTML = answers.map(a => `
${a.name ? esc(a.name) : '匿名'}
${esc(a.text)}
`).join('');
}
async function publishQuestion() {
const q = document.getElementById('questionInput').value.trim();
if (!q) { alert('請先輸入題目!'); return; }
try { await apiPost('/publish', { question: q }); } catch { alert('發佈失敗,請確認 Worker 網址正確'); }
}
async function closeQuestion() {
try { await apiPost('/close', {}); } catch { alert('操作失敗'); }
}
async function clearAll() {
if (!confirm('確定要清除題目與所有答案嗎?')) return;
try {
await apiPost('/clear', {});
document.getElementById('questionInput').value = '';
} catch { alert('操作失敗'); }
}
function buildSharePanel() {
const url = window.location.href.split('?')[0] + '?role=student';
document.getElementById('shareUrl').textContent = url;
const qrEl = document.getElementById('qrcode');
qrEl.innerHTML = '';
new QRCode(qrEl, { text: url, width: 72, height: 72, correctLevel: QRCode.CorrectLevel.L });
}
// ── STUDENT ──
let studentSubmitted = false;
function renderStudent(state) {
if (studentSubmitted) return;
const w = document.getElementById('waitingView');
const a = document.getElementById('answerView');
const c = document.getElementById('closedView');
const s = document.getElementById('successView');
[w,a,c,s].forEach(v => v.style.display = 'none');
if (!state.question || state.status === 'idle') w.style.display = '';
else if (state.status === 'open') { document.getElementById('studentQuestion').textContent = state.question; a.style.display = ''; }
else c.style.display = '';
}
async function submitAnswer() {
const name = document.getElementById('studentName').value.trim();
const text = document.getElementById('studentAnswer').value.trim();
if (!text) { alert('請輸入答案再提交!'); return; }
const btn = document.getElementById('submitBtn');
btn.disabled = true; btn.textContent = '提交中…';
try {
const res = await apiPost('/answer', { name, text });
if (res.error === 'closed') { alert('作答時間已結束!'); btn.disabled=false; btn.textContent='提交答案'; return; }
studentSubmitted = true;
stopPolling();
document.getElementById('answerView').style.display = 'none';
document.getElementById('successView').style.display = '';
} catch { alert('提交失敗,請重試!'); btn.disabled=false; btn.textContent='提交答案'; }
}
// ── PRESENTATION ──
let slides = [], currentSlide = 0;
async function startPresentation() {
try {
const state = await apiGet();
if (!state.question) { alert('請先發佈題目!'); return; }
if (!(state.answers||[]).length){ alert('還沒有任何答案可以展示!'); return; }
slides = [{ type:'title', question:state.question, count:state.answers.length }];
state.answers.forEach((a,i) => slides.push({ type:'answer', name:a.name, text:a.text, idx:i+1 }));
currentSlide = 0;
stopPolling();
showScreen('presentation');
renderSlide();
document.addEventListener('keydown', pptKey);
} catch { alert('載入失敗,請重試'); }
}
function exitPresentation() { document.removeEventListener('keydown', pptKey); goTeacher(); }
function pptKey(e) {
if (e.key==='ArrowRight'||e.key===' '){e.preventDefault();pptNext();}
if (e.key==='ArrowLeft'){e.preventDefault();pptPrev();}
if (e.key==='Escape') exitPresentation();
}
function renderSlide() {
const s = slides[currentSlide], total = slides.length;
const el = document.getElementById('pptContent');
if (s.type==='title') {
el.innerHTML = `本次題目
${esc(s.question)}
共 ${s.count} 份答案
`;
} else {
el.innerHTML = `第 ${s.idx} / ${total-1} 份
${s.name?esc(s.name):'匿名'}
${esc(s.text)}
`;
}
el.style.animation='none'; el.offsetHeight; el.style.animation='';
document.getElementById('pptCounter').textContent = `${currentSlide+1} / ${total}`;
document.getElementById('pptBar').style.width = ((currentSlide+1)/total*100)+'%';
document.getElementById('prevBtn').disabled = currentSlide===0;
document.getElementById('nextBtn').disabled = currentSlide===total-1;
}
function pptNext(){if(currentSlide0){currentSlide--;renderSlide();}}
function esc(s){return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');}
// ── INIT (自動跳轉邏輯) ──
(function(){
// 如果網址帶有 role=student (也就是掃描 QR Code 進來的),直接進入學生畫面
if (location.search.includes('role=student')) {
showScreen('student');
goStudent();
} else {
showScreen('home');
}
})();