Organized the differences between server and docker versions.
We are launching a docker version (server version) today so we want to just organize the repo so its easier to navigate.
This commit is contained in:
342
docker/src/scripts/books/book.js
Normal file
342
docker/src/scripts/books/book.js
Normal file
@@ -0,0 +1,342 @@
|
||||
let bookData = null;
|
||||
let extensionName = null;
|
||||
let bookId = null;
|
||||
let bookSlug = null;
|
||||
|
||||
let allChapters = [];
|
||||
let filteredChapters = [];
|
||||
|
||||
const chapterPagination = Object.create(PaginationManager);
|
||||
chapterPagination.init(12, () => renderChapterTable());
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
setupModalClickOutside();
|
||||
});
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
|
||||
const urlData = URLUtils.parseEntityPath('book');
|
||||
if (!urlData) {
|
||||
showError("Book Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
extensionName = urlData.extensionName;
|
||||
bookId = urlData.entityId;
|
||||
bookSlug = urlData.slug;
|
||||
|
||||
await loadBookMetadata();
|
||||
|
||||
await loadChapters();
|
||||
|
||||
await setupAddToListButton();
|
||||
|
||||
} catch (err) {
|
||||
console.error("Metadata Error:", err);
|
||||
showError("Error loading book");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBookMetadata() {
|
||||
const source = extensionName || 'anilist';
|
||||
const fetchUrl = `/api/book/${bookId}?source=${source}`;
|
||||
|
||||
const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error || !data) {
|
||||
showError("Book Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
const raw = Array.isArray(data) ? data[0] : data;
|
||||
bookData = raw;
|
||||
|
||||
const metadata = MediaMetadataUtils.formatBookData(raw, !!extensionName);
|
||||
|
||||
updatePageTitle(metadata.title);
|
||||
updateMetadata(metadata);
|
||||
updateExtensionPill();
|
||||
}
|
||||
|
||||
function updatePageTitle(title) {
|
||||
document.title = `${title} | WaifuBoard Books`;
|
||||
const titleEl = document.getElementById('title');
|
||||
if (titleEl) titleEl.innerText = title;
|
||||
}
|
||||
|
||||
function updateMetadata(metadata) {
|
||||
|
||||
const descEl = document.getElementById('description');
|
||||
if (descEl) descEl.innerHTML = metadata.description;
|
||||
|
||||
const scoreEl = document.getElementById('score');
|
||||
if (scoreEl) {
|
||||
scoreEl.innerText = extensionName
|
||||
? `${metadata.score}`
|
||||
: `${metadata.score}% Score`;
|
||||
}
|
||||
|
||||
const pubEl = document.getElementById('published-date');
|
||||
if (pubEl) pubEl.innerText = metadata.year;
|
||||
|
||||
const statusEl = document.getElementById('status');
|
||||
if (statusEl) statusEl.innerText = metadata.status;
|
||||
|
||||
const formatEl = document.getElementById('format');
|
||||
if (formatEl) formatEl.innerText = metadata.format;
|
||||
|
||||
const chaptersEl = document.getElementById('chapters');
|
||||
if (chaptersEl) chaptersEl.innerText = metadata.chapters;
|
||||
|
||||
const genresEl = document.getElementById('genres');
|
||||
if (genresEl) genresEl.innerText = metadata.genres;
|
||||
|
||||
const posterEl = document.getElementById('poster');
|
||||
if (posterEl) posterEl.src = metadata.poster;
|
||||
|
||||
const heroBgEl = document.getElementById('hero-bg');
|
||||
if (heroBgEl) heroBgEl.src = metadata.banner;
|
||||
}
|
||||
|
||||
function updateExtensionPill() {
|
||||
const pill = document.getElementById('extension-pill');
|
||||
if (!pill) return;
|
||||
|
||||
if (extensionName) {
|
||||
pill.textContent = extensionName.charAt(0).toUpperCase() + extensionName.slice(1).toLowerCase();
|
||||
pill.style.display = 'inline-flex';
|
||||
} else {
|
||||
pill.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function setupAddToListButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (!btn || !bookData) return;
|
||||
|
||||
ListModalManager.currentData = bookData;
|
||||
const entryType = ListModalManager.getEntryType(bookData);
|
||||
const idForCheck = extensionName ? bookSlug : bookId;
|
||||
|
||||
await ListModalManager.checkIfInList(
|
||||
idForCheck,
|
||||
extensionName || 'anilist',
|
||||
entryType
|
||||
);
|
||||
|
||||
updateCustomAddButton();
|
||||
|
||||
btn.onclick = () => ListModalManager.open(bookData, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function updateCustomAddButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (!btn) return;
|
||||
|
||||
if (ListModalManager.isInList) {
|
||||
btn.innerHTML = `
|
||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
In Your Library
|
||||
`;
|
||||
btn.style.background = 'rgba(34, 197, 94, 0.2)';
|
||||
btn.style.color = '#22c55e';
|
||||
btn.style.borderColor = 'rgba(34, 197, 94, 0.3)';
|
||||
} else {
|
||||
btn.innerHTML = '+ Add to Library';
|
||||
btn.style.background = null;
|
||||
btn.style.color = null;
|
||||
btn.style.borderColor = null;
|
||||
}
|
||||
}
|
||||
|
||||
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 source = extensionName || 'anilist';
|
||||
const fetchUrl = `/api/book/${bookId}/chapters?source=${source}`;
|
||||
|
||||
const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
|
||||
const data = await res.json();
|
||||
|
||||
allChapters = data.chapters || [];
|
||||
filteredChapters = [...allChapters];
|
||||
|
||||
applyChapterFilter();
|
||||
|
||||
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`;
|
||||
|
||||
setupProviderFilter();
|
||||
|
||||
setupReadButton();
|
||||
|
||||
chapterPagination.setTotalItems(filteredChapters.length);
|
||||
renderChapterTable();
|
||||
|
||||
} catch (err) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; color: #ef4444;">Error loading chapters.</td></tr>';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function applyChapterFilter() {
|
||||
const chapterParam = URLUtils.getQueryParam('chapter');
|
||||
if (!chapterParam) return;
|
||||
|
||||
const chapterNumber = parseFloat(chapterParam);
|
||||
if (isNaN(chapterNumber)) return;
|
||||
|
||||
filteredChapters = allChapters.filter(
|
||||
ch => parseFloat(ch.number) === chapterNumber
|
||||
);
|
||||
|
||||
chapterPagination.reset();
|
||||
}
|
||||
|
||||
function setupProviderFilter() {
|
||||
const select = document.getElementById('provider-filter');
|
||||
if (!select) return;
|
||||
|
||||
const providers = [...new Set(allChapters.map(ch => ch.provider))];
|
||||
|
||||
if (providers.length === 0) return;
|
||||
|
||||
select.style.display = 'inline-block';
|
||||
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);
|
||||
});
|
||||
|
||||
if (extensionName) {
|
||||
const extensionProvider = providers.find(
|
||||
p => p.toLowerCase() === extensionName.toLowerCase()
|
||||
);
|
||||
|
||||
if (extensionProvider) {
|
||||
select.value = extensionProvider;
|
||||
filteredChapters = allChapters.filter(ch => ch.provider === extensionProvider);
|
||||
}
|
||||
}
|
||||
|
||||
select.onchange = (e) => {
|
||||
const selected = e.target.value;
|
||||
if (selected === 'all') {
|
||||
filteredChapters = [...allChapters];
|
||||
} else {
|
||||
filteredChapters = allChapters.filter(ch => ch.provider === selected);
|
||||
}
|
||||
|
||||
chapterPagination.reset();
|
||||
chapterPagination.setTotalItems(filteredChapters.length);
|
||||
renderChapterTable();
|
||||
};
|
||||
}
|
||||
|
||||
function setupReadButton() {
|
||||
const readBtn = document.getElementById('read-start-btn');
|
||||
if (!readBtn || filteredChapters.length === 0) return;
|
||||
|
||||
const firstChapter = filteredChapters[0];
|
||||
readBtn.onclick = () => openReader(0, firstChapter.provider);
|
||||
}
|
||||
|
||||
function renderChapterTable() {
|
||||
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>';
|
||||
chapterPagination.renderControls(
|
||||
'pagination',
|
||||
'page-info',
|
||||
'prev-page',
|
||||
'next-page'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const pageItems = chapterPagination.getCurrentPageItems(filteredChapters);
|
||||
|
||||
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.index}', '${ch.provider}')">
|
||||
Read
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
chapterPagination.renderControls(
|
||||
'pagination',
|
||||
'page-info',
|
||||
'prev-page',
|
||||
'next-page'
|
||||
);
|
||||
}
|
||||
|
||||
function openReader(chapterId, provider) {
|
||||
window.location.href = URLUtils.buildReadUrl(bookId, chapterId, provider, extensionName);
|
||||
}
|
||||
|
||||
function setupModalClickOutside() {
|
||||
const modal = document.getElementById('add-list-modal');
|
||||
if (!modal) return;
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'add-list-modal') {
|
||||
ListModalManager.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const titleEl = document.getElementById('title');
|
||||
if (titleEl) titleEl.innerText = message;
|
||||
}
|
||||
|
||||
function saveToList() {
|
||||
const idToSave = extensionName ? bookSlug : bookId;
|
||||
ListModalManager.save(idToSave, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function deleteFromList() {
|
||||
const idToDelete = extensionName ? bookSlug : bookId;
|
||||
ListModalManager.delete(idToDelete, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function closeAddToListModal() {
|
||||
ListModalManager.close();
|
||||
}
|
||||
|
||||
window.openReader = openReader;
|
||||
window.saveToList = saveToList;
|
||||
window.deleteFromList = deleteFromList;
|
||||
window.closeAddToListModal = closeAddToListModal;
|
||||
Reference in New Issue
Block a user