Add deployment reading windows and progress-aware quiz unlock
This commit is contained in:
1 parent
edbc9c6056
commit
d8eba9169f
15 files changed
+471
-5
No files matched your search
+57
-1
@@ -12,9 +12,65 @@
|
||||
const P = new URLSearchParams(location.search);
|
||||
const stage = $('#stage'), canvas = $('#canvas'), slider = $('#slider'), ver = $('#ver');
|
||||
let me, D;
|
||||
try { [me, D] = await Promise.all([api('api/me'), api('api/chapter?' + qsa({ m: P.get('m'), c: P.get('c'), v: P.get('v') }))]); }
|
||||
try {
|
||||
me = await api('api/me');
|
||||
const response = await fetch('api/chapter?' + qsa({ m: P.get('m'), c: P.get('c'), v: P.get('v') }));
|
||||
if (response.status === 423) {
|
||||
const access = await api('api/reading-access?' + qsa({ m: P.get('m') }));
|
||||
canvas.textContent = '';
|
||||
const card = h('section', { class: 'reading-gate' },
|
||||
h('h1', {}, 'Reading is closed right now'),
|
||||
h('p', {}, `The reading hours are set by this server's deployment (${access.timezone}). The library and your progress remain available.`),
|
||||
h('p', {}, `Answer five questions drawn from manga you have already read to unlock ${access.unlockMinutes} minutes of reading. There is no answer timer or retry limit.`));
|
||||
const message = h('p', { class: 'reading-gate-message', role: 'status' });
|
||||
const question = h('div', { class: 'reading-gate-question' });
|
||||
const start = h('button', { class: 'btn', type: 'button' }, 'Start five-question challenge');
|
||||
const back = h('a', { class: 'btn', href: mangaUrl(P.get('m')) }, 'Back to manga');
|
||||
card.append(message, question, h('div', { class: 'reading-gate-actions' }, start, back));
|
||||
canvas.append(card);
|
||||
let challenge = null;
|
||||
const showQuestion = data => {
|
||||
challenge = data.challenge;
|
||||
question.textContent = '';
|
||||
const input = h('input', { type: 'text', autocomplete: 'off', 'aria-label': 'Your answer', maxlength: '120', required: true });
|
||||
const form = h('form', {}, h('p', {}, `Question ${data.number} of ${data.total}`),
|
||||
h('p', { class: 'muted' }, data.mangaTitle), h('h2', {}, data.prompt), input, h('button', { class: 'btn', type: 'submit' }, 'Check answer'));
|
||||
form.addEventListener('submit', async event => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const result = await api('api/reading-quiz/answer', { m: P.get('m'), challenge, answer: input.value });
|
||||
if (!result.correct) { message.textContent = 'Not quite. Try another spelling or answer—there is no attempt limit.'; input.select(); return; }
|
||||
if (result.complete) { message.textContent = 'Unlocked. Opening your chapter…'; location.reload(); return; }
|
||||
message.textContent = 'Correct.';
|
||||
showQuestion(result);
|
||||
} catch (error) { message.textContent = error.message; }
|
||||
});
|
||||
question.append(form); input.focus();
|
||||
};
|
||||
start.addEventListener('click', async () => {
|
||||
try { showQuestion(await api('api/reading-quiz/start', { m: P.get('m') })); start.hidden = true; message.textContent = ''; }
|
||||
catch (error) { message.textContent = error.message; }
|
||||
});
|
||||
if (!access.quizAvailable) { start.hidden = true; message.textContent = 'Fewer than five questions match your completed chapters. Please return during reading hours.'; }
|
||||
window.addEventListener('pagehide', () => {
|
||||
if (challenge) navigator.sendBeacon('api/reading-quiz/abandon', new Blob([JSON.stringify({ challenge })], { type: 'application/json' }));
|
||||
});
|
||||
setInterval(async () => {
|
||||
try { if ((await api('api/reading-access?' + qsa({ m: P.get('m') }))).allowed) location.reload(); } catch { /* keep the gate visible */ }
|
||||
}, 15000);
|
||||
return;
|
||||
}
|
||||
const payload = await response.json();
|
||||
if (!response.ok) throw new Error(payload.error || response.statusText);
|
||||
D = payload;
|
||||
}
|
||||
catch (e) { canvas.textContent = ''; canvas.append(h('p', { class: 'loading' }, e.message + ' · ', h('a', { href: './' }, 'Library'))); return; }
|
||||
|
||||
// An already-open chapter also closes when a window ends or an unlock expires.
|
||||
setInterval(async () => {
|
||||
try { if (!(await api('api/reading-access?' + qsa({ m: P.get('m') }))).allowed) location.reload(); } catch { /* transient check failure */ }
|
||||
}, 15000);
|
||||
|
||||
const S = me.settings, rtl = D.direction === 'rtl', ttb = D.direction === 'ttb';
|
||||
const GAP = ttb || S.flowPageGap === false ? 0 : 8, END_H = 280;
|
||||
document.title = `${D.chapter.label} · ${D.manga.title}`;
|
||||
|
||||
Reference in new issue
Block a user