96 lines
5.2 KiB
JavaScript
96 lines
5.2 KiB
JavaScript
'use strict';
|
|
/* Manga page: info, follow toggle and the chapter list (books are just separators). */
|
|
(async function () {
|
|
const id = new URLSearchParams(location.search).get('m') || '';
|
|
let me, M;
|
|
try { [me, M] = await Promise.all([api('api/me'), api('api/manga?' + qsa({ m: id }))]); } catch (e) { return showError(e); }
|
|
topNav(me);
|
|
document.title = M.title + ' · MangaCircus';
|
|
const meta = M.meta || {}, main = $('main');
|
|
main.textContent = '';
|
|
|
|
const TYPE = { rtl: 'Manga · right to left', ltr: 'Comic · left to right', ttb: 'Webtoon · vertical strip' };
|
|
const arr = x => (Array.isArray(x) ? x : x ? [x] : []);
|
|
const follow = h('button', { class: 'btn' }, '');
|
|
const drawFollow = () => { follow.textContent = M.following ? '🔔 Notifying you' : 'Notify me about new chapters'; follow.classList.toggle('on', M.following); };
|
|
follow.onclick = async () => { M.following = (await api('api/follow', { m: M.id, on: !M.following })).following; drawFollow(); };
|
|
drawFollow();
|
|
|
|
const sources = arr(meta.sources).filter(s => s && s.name);
|
|
main.append(h('section', { class: 'hero' },
|
|
M.cover ? h('img', { src: M.cover, alt: '' }) : null,
|
|
h('div', { class: 'info' },
|
|
h('h1', {}, M.title),
|
|
arr(meta.altTitles).length ? h('div', { class: 'muted' }, arr(meta.altTitles).join(' · ')) : null,
|
|
h('div', { class: 'muted' }, [arr(meta.authors).join(', '), meta.status, meta.year, TYPE[M.direction]].filter(Boolean).join(' · ')),
|
|
h('div', { class: 'chips' }, arr(meta.tags).map(t => h('span', { class: 'chip' }, t))),
|
|
meta.description ? h('p', { class: 'desc' }, meta.description) : null,
|
|
sources.length ? h('div', { class: 'sources small' }, h('span', { class: 'muted' }, 'Sources: '),
|
|
sources.map(s => (safeHref(s.url) ? h('a', { href: safeHref(s.url), target: '_blank', rel: 'noopener noreferrer' }, s.name) : h('span', {}, s.name + ' ')))) : null,
|
|
h('div', { class: 'actions' },
|
|
M.continue ? h('a', { class: 'btn primary', href: readUrl(M.id, M.continue.c, M.continue.v) }, M.continue.text) : null,
|
|
follow))));
|
|
|
|
const books = {};
|
|
for (const b of M.books) books[b.key] = b;
|
|
const list = h('section', { class: 'chapters' });
|
|
main.append(list);
|
|
let lastBook = null;
|
|
for (const ch of M.chapters) {
|
|
if (ch.book !== lastBook && M.books.length > 1) {
|
|
const b = books[ch.book];
|
|
list.append(h('div', { class: 'book-sep' }, b.label + (b.title ? ' · ' + b.title : '')));
|
|
}
|
|
lastBook = ch.book;
|
|
list.append(chapterRow(ch));
|
|
}
|
|
|
|
function chapterRow(ch) {
|
|
const wrap = h('div', {});
|
|
const panel = h('div', { class: 'versions', hidden: true });
|
|
function draw() {
|
|
const p = ch.progress, done = p && p.done;
|
|
const state = h('button', { class: 'state' + (done ? ' done' : ''), title: done ? 'Mark as unread' : 'Mark as read' },
|
|
done ? '✓ read' : p ? `page ${p.p + 1} / ${p.n}` : `${ch.versions[0].pages} p.`);
|
|
state.onclick = async () => {
|
|
if (done) { await api('api/unread', { m: M.id, c: ch.id }); ch.progress = null; }
|
|
else {
|
|
const v = ch.versions[0];
|
|
await api('api/progress', { m: M.id, c: ch.id, v: v.id, p: v.pages - 1, n: v.pages, done: true });
|
|
ch.progress = { v: v.id, p: v.pages - 1, n: v.pages, done: true };
|
|
}
|
|
draw();
|
|
};
|
|
const verBtn = ch.versions.length > 1
|
|
? h('button', { class: 'btn small', onclick: () => { panel.hidden = !panel.hidden; } }, ch.versions.length + ' versions')
|
|
: null;
|
|
wrap.textContent = '';
|
|
wrap.append(h('div', { class: 'ch' + (done ? ' read' : '') },
|
|
h('a', { class: 'ch-main', href: readUrl(M.id, ch.id) },
|
|
h('span', { class: 'ch-label' }, ch.label), h('span', { class: 'ch-title' }, ch.title || ''),
|
|
ch.extra ? h('span', { class: 'tag' }, 'extra') : null),
|
|
verBtn, state), panel);
|
|
drawVersions();
|
|
}
|
|
function drawVersions() {
|
|
panel.textContent = '';
|
|
panel.append(h('div', { class: 'muted small', style: 'padding-top:6px' }, 'The best rated version opens by default. Vote to change that for everyone.'));
|
|
ch.versions.forEach((v, i) => {
|
|
const vote = async value => { ch.versions = (await api('api/vote', { m: M.id, c: ch.id, v: v.id, value: v.my === value ? 0 : value })).versions; drawVersions(); };
|
|
panel.append(h('div', { class: 'ver' },
|
|
h('div', { class: 'src' },
|
|
safeHref(v.sourceUrl) ? h('a', { href: safeHref(v.sourceUrl), target: '_blank', rel: 'noopener noreferrer' }, v.source || v.id) : h('span', {}, v.source || v.id),
|
|
i === 0 ? h('span', { class: 'tag', style: 'margin-left:8px' }, 'default') : null,
|
|
h('div', { class: 'muted small' }, `${v.pages} pages · added ${new Date(v.added).toLocaleDateString()}`)),
|
|
h('span', { class: 'vote' },
|
|
h('button', { class: v.my > 0 ? 'on' : '', title: 'Better', onclick: () => vote(1) }, '▲'),
|
|
h('b', {}, v.score),
|
|
h('button', { class: v.my < 0 ? 'on' : '', title: 'Worse', onclick: () => vote(-1) }, '▼')),
|
|
h('a', { class: 'btn small', href: readUrl(M.id, ch.id, v.id) }, 'Read')));
|
|
});
|
|
}
|
|
draw();
|
|
return wrap;
|
|
}
|
|
})();
|