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:
2025-12-16 21:50:22 -05:00
parent b86f14a8f2
commit 28ff6ccc68
193 changed files with 23188 additions and 5 deletions

View 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;