First dev release of v2.0.0
This commit is contained in:
210
public/book.js
Normal file
210
public/book.js
Normal file
@@ -0,0 +1,210 @@
|
||||
const bookId = window.location.pathname.split('/').pop();
|
||||
let allChapters = []; // Stores all fetched chapters
|
||||
let filteredChapters = []; // Stores currently displayed chapters (filtered)
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 12;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
// 1. Load Metadata
|
||||
const res = await fetch(`/api/book/${bookId}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
const titleEl = document.getElementById('title');
|
||||
if (titleEl) titleEl.innerText = "Book Not Found";
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate Hero Elements
|
||||
const title = data.title.english || data.title.romaji;
|
||||
document.title = `${title} | StreamFlow Books`;
|
||||
|
||||
const titleEl = document.getElementById('title');
|
||||
if (titleEl) titleEl.innerText = title;
|
||||
|
||||
const descEl = document.getElementById('description');
|
||||
if (descEl) descEl.innerHTML = data.description || "No description available.";
|
||||
|
||||
const scoreEl = document.getElementById('score');
|
||||
if (scoreEl) scoreEl.innerText = (data.averageScore || '?') + '% Score';
|
||||
|
||||
const pubEl = document.getElementById('published-date');
|
||||
if (pubEl) {
|
||||
if (data.startDate && data.startDate.year) {
|
||||
const y = data.startDate.year;
|
||||
const m = data.startDate.month ? `-${data.startDate.month.toString().padStart(2, '0')}` : '';
|
||||
const d = data.startDate.day ? `-${data.startDate.day.toString().padStart(2, '0')}` : '';
|
||||
pubEl.innerText = `${y}${m}${d}`;
|
||||
} else {
|
||||
pubEl.innerText = '????';
|
||||
}
|
||||
}
|
||||
|
||||
const statusEl = document.getElementById('status');
|
||||
if (statusEl) statusEl.innerText = data.status || 'Unknown';
|
||||
|
||||
const formatEl = document.getElementById('format');
|
||||
if (formatEl) formatEl.innerText = data.format || 'MANGA';
|
||||
|
||||
const chaptersEl = document.getElementById('chapters');
|
||||
if (chaptersEl) chaptersEl.innerText = data.chapters || '?';
|
||||
|
||||
const genresEl = document.getElementById('genres');
|
||||
if(genresEl && data.genres) {
|
||||
genresEl.innerText = data.genres.slice(0, 3).join(' • ');
|
||||
}
|
||||
|
||||
const img = data.coverImage.extraLarge || data.coverImage.large;
|
||||
|
||||
const posterEl = document.getElementById('poster');
|
||||
if (posterEl) posterEl.src = img;
|
||||
|
||||
const heroBgEl = document.getElementById('hero-bg');
|
||||
if (heroBgEl) heroBgEl.src = data.bannerImage || img;
|
||||
|
||||
// 2. Load Chapters
|
||||
loadChapters();
|
||||
|
||||
} catch (err) {
|
||||
console.error("Metadata Error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChapters() {
|
||||
const tbody = document.getElementById('chapters-body');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extensions for chapters...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/book/${bookId}/chapters`);
|
||||
const data = await res.json();
|
||||
|
||||
allChapters = data.chapters || [];
|
||||
filteredChapters = [...allChapters]; // Initially, show all
|
||||
|
||||
const totalEl = document.getElementById('total-chapters');
|
||||
|
||||
if (allChapters.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters found on loaded extensions.</td></tr>';
|
||||
if (totalEl) totalEl.innerText = "0 Found";
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
|
||||
|
||||
// Populate Provider Filter
|
||||
populateProviderFilter();
|
||||
|
||||
// Read Button Action (Start at filtered Ch 1)
|
||||
const readBtn = document.getElementById('read-start-btn');
|
||||
if (readBtn && filteredChapters.length > 0) {
|
||||
readBtn.onclick = () => openReader(filteredChapters[0].id);
|
||||
}
|
||||
|
||||
renderTable();
|
||||
|
||||
} catch (err) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; color: #ef4444;">Error loading chapters.</td></tr>';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function populateProviderFilter() {
|
||||
const select = document.getElementById('provider-filter');
|
||||
if (!select) return;
|
||||
|
||||
// Extract unique providers
|
||||
const providers = [...new Set(allChapters.map(ch => ch.provider))];
|
||||
|
||||
// Only show filter if there are actual providers found
|
||||
if (providers.length > 0) {
|
||||
select.style.display = 'inline-block';
|
||||
|
||||
// Clear existing options except "All"
|
||||
select.innerHTML = '<option value="all">All Providers</option>';
|
||||
|
||||
providers.forEach(prov => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = prov;
|
||||
opt.innerText = prov;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
// Attach Event Listener
|
||||
select.onchange = (e) => {
|
||||
const selected = e.target.value;
|
||||
if (selected === 'all') {
|
||||
filteredChapters = [...allChapters];
|
||||
} else {
|
||||
filteredChapters = allChapters.filter(ch => ch.provider === selected);
|
||||
}
|
||||
currentPage = 1; // Reset to page 1 on filter change
|
||||
renderTable();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById('chapters-body');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (filteredChapters.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters match this filter.</td></tr>';
|
||||
updatePagination(); // Update to hide buttons
|
||||
return;
|
||||
}
|
||||
|
||||
const start = (currentPage - 1) * itemsPerPage;
|
||||
const end = start + itemsPerPage;
|
||||
const pageItems = filteredChapters.slice(start, end);
|
||||
|
||||
pageItems.forEach(ch => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${ch.number}</td>
|
||||
<td>${ch.title || `Chapter ${ch.number}`}</td>
|
||||
<td><span class="pill" style="font-size:0.75rem;">${ch.provider}</span></td>
|
||||
<td>
|
||||
<button class="read-btn-small" onclick="openReader('${ch.id}')">Read</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePagination();
|
||||
}
|
||||
|
||||
function updatePagination() {
|
||||
const totalPages = Math.ceil(filteredChapters.length / itemsPerPage);
|
||||
const pagination = document.getElementById('pagination');
|
||||
|
||||
if (!pagination) return;
|
||||
|
||||
if (totalPages <= 1) {
|
||||
pagination.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
pagination.style.display = 'flex';
|
||||
document.getElementById('page-info').innerText = `Page ${currentPage} of ${totalPages}`;
|
||||
|
||||
const prevBtn = document.getElementById('prev-page');
|
||||
const nextBtn = document.getElementById('next-page');
|
||||
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
nextBtn.disabled = currentPage >= totalPages;
|
||||
|
||||
prevBtn.onclick = () => { currentPage--; renderTable(); };
|
||||
nextBtn.onclick = () => { currentPage++; renderTable(); };
|
||||
}
|
||||
|
||||
function openReader(chapterId) {
|
||||
alert("Opening Reader for Chapter ID: " + chapterId);
|
||||
// window.location.href = `/read/${bookId}/${chapterId}`;
|
||||
}
|
||||
|
||||
init();
|
||||
Reference in New Issue
Block a user