let bookData = null; let extensionName = null; let bookId = null; let bookSlug = null; let allChapters = []; let filteredChapters = []; let availableExtensions = []; let isLocal = false; const chapterPagination = Object.create(PaginationManager); chapterPagination.init(12, () => renderChapterTable()); document.addEventListener('DOMContentLoaded', () => { init(); setupModalClickOutside(); }); async function checkLocalLibraryEntry() { try { const res = await fetch(`/api/library/manga/${bookId}`); if (!res.ok) return; const data = await res.json(); if (data.matched) { isLocal = true; const pill = document.getElementById('local-pill'); if (pill) { pill.textContent = 'Local'; pill.style.display = 'inline-flex'; pill.style.background = 'rgba(34, 197, 94, 0.2)'; pill.style.color = '#22c55e'; pill.style.borderColor = 'rgba(34, 197, 94, 0.3)'; } } } catch (e) { console.error("Error checking local status:", e); } } function markAsLocal() { isLocal = true; const pill = document.getElementById('local-pill'); if (pill) { pill.textContent = 'Local'; pill.style.display = 'inline-flex'; pill.style.background = 'rgba(34, 197, 94, 0.2)'; pill.style.color = '#22c55e'; pill.style.borderColor = 'rgba(34, 197, 94, 0.3)'; } } 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 checkLocalLibraryEntry(); await loadBookMetadata(); await loadAvailableExtensions(); await loadChapters(); await setupAddToListButton(); } catch (err) { console.error("Metadata Error:", err); showError("Error loading book"); } } async function loadAvailableExtensions() { try { const res = await fetch('/api/extensions/book'); const data = await res.json(); availableExtensions = data.extensions || []; setupProviderFilter(); } catch (err) { console.error("Error fetching extensions:", err); } } async function loadBookMetadata() { const source = extensionName || 'anilist'; const fetchUrl = `/api/book/${bookId}?source=${source}`; const res = await fetch(fetchUrl); 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); bookData.entry_type = metadata.format === 'MANGA' ? 'MANGA' : 'NOVEL'; 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 = ` 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(targetProvider = null) { const tbody = document.getElementById('chapters-body'); if (!tbody) return; if (!targetProvider) { const select = document.getElementById('provider-filter'); targetProvider = select ? select.value : (availableExtensions[0] || 'all'); } tbody.innerHTML = 'Loading chapters...'; try { let fetchUrl; let isLocalRequest = targetProvider === 'local'; if (isLocalRequest) { // Nuevo endpoint para archivos locales fetchUrl = `/api/library/manga/${bookId}/units`; } else { const source = extensionName || 'anilist'; fetchUrl = `/api/book/${bookId}/chapters?source=${source}`; if (targetProvider !== 'all') fetchUrl += `&provider=${targetProvider}`; } const res = await fetch(fetchUrl); const data = await res.json(); // Mapeo de datos: Si es local usamos 'units', si no, usamos 'chapters' if (isLocalRequest) { allChapters = (data.units || []).map((unit, idx) => ({ number: unit.number, title: unit.name, provider: 'local', index: idx, // ✅ índice (0,1,2…) format: unit.format })); } else { allChapters = data.chapters || []; } filteredChapters = [...allChapters]; applyChapterFilter(); const totalEl = document.getElementById('total-chapters'); if (allChapters.length === 0) { tbody.innerHTML = 'No chapters found.'; if (totalEl) totalEl.innerText = "0 Found"; return; } if (totalEl) totalEl.innerText = `${allChapters.length} Found`; setupReadButton(); chapterPagination.setTotalItems(filteredChapters.length); chapterPagination.reset(); renderChapterTable(); } catch (err) { tbody.innerHTML = 'Error loading chapters.'; 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; select.style.display = 'inline-block'; select.innerHTML = ''; // Opción para cargar todo const allOpt = document.createElement('option'); allOpt.value = 'all'; allOpt.innerText = 'Load All (Slower)'; select.appendChild(allOpt); // NUEVO: Si es local, añadimos la opción 'local' al principio if (isLocal) { const localOpt = document.createElement('option'); localOpt.value = 'local'; localOpt.innerText = 'Local'; select.appendChild(localOpt); } // Añadir extensiones normales availableExtensions.forEach(ext => { const opt = document.createElement('option'); opt.value = ext; opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1); select.appendChild(opt); }); // Lógica de selección automática if (isLocal) { select.value = 'local'; // Prioridad si es local } else if (extensionName && availableExtensions.includes(extensionName)) { select.value = extensionName; } else if (availableExtensions.length > 0) { select.value = availableExtensions[0]; } select.onchange = () => { loadChapters(select.value); }; } 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 = 'No chapters match this filter.'; 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 = ` ${ch.number} ${ch.title || 'Chapter ' + ch.number} ${ch.provider} `; tbody.appendChild(row); }); chapterPagination.renderControls( 'pagination', 'page-info', 'prev-page', 'next-page' ); } function openReader(chapterId, provider) { const effectiveExtension = extensionName || 'anilist'; window.location.href = URLUtils.buildReadUrl( bookId, // SIEMPRE anilist chapterId, // número normal provider, // 'local' o extensión extensionName || 'anilist' ); } 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;