diff --git a/desktop/src/api/books/books.controller.ts b/desktop/src/api/books/books.controller.ts index 5d07549..d8f7a8a 100644 --- a/desktop/src/api/books/books.controller.ts +++ b/desktop/src/api/books/books.controller.ts @@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) { try { const { id } = req.params; const source = req.query.source || 'anilist'; + const provider = req.query.provider; const isExternal = source !== 'anilist'; - return await booksService.getChaptersForBook(id, isExternal); - } catch { + return await booksService.getChaptersForBook(id, isExternal, provider); + } catch (err) { + console.error(err); return { chapters: [] }; } } - export async function getChapterContent(req: any, reply: FastifyReply) { try { const { bookId, chapter, provider } = req.params; diff --git a/desktop/src/scripts/books/book.js b/desktop/src/scripts/books/book.js index c649e84..22f2cb6 100644 --- a/desktop/src/scripts/books/book.js +++ b/desktop/src/scripts/books/book.js @@ -6,6 +6,8 @@ let bookSlug = null; let allChapters = []; let filteredChapters = []; +let availableExtensions = []; + const chapterPagination = Object.create(PaginationManager); chapterPagination.init(12, () => renderChapterTable()); @@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => { async function init() { try { - const urlData = URLUtils.parseEntityPath('book'); if (!urlData) { showError("Book Not Found"); @@ -29,6 +30,7 @@ async function init() { await loadBookMetadata(); + await loadAvailableExtensions(); await loadChapters(); await setupAddToListButton(); @@ -39,11 +41,23 @@ async function init() { } } +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, { headers: AuthUtils.getSimpleAuthHeaders() }); + const res = await fetch(fetchUrl); const data = await res.json(); if (data.error || !data) { @@ -154,17 +168,27 @@ function updateCustomAddButton() { } } -async function loadChapters() { +async function loadChapters(targetProvider = null) { const tbody = document.getElementById('chapters-body'); if (!tbody) return; - tbody.innerHTML = 'Searching extensions for chapters...'; + // Si no se pasa provider, intentamos pillar el del select o el primero disponible + if (!targetProvider) { + const select = document.getElementById('provider-filter'); + targetProvider = select ? select.value : (availableExtensions[0] || 'all'); + } + + tbody.innerHTML = 'Searching extension for chapters...'; try { const source = extensionName || 'anilist'; - const fetchUrl = `/api/book/${bookId}/chapters?source=${source}`; + // AƱadimos el query param 'provider' para que el backend filtre + let fetchUrl = `/api/book/${bookId}/chapters?source=${source}`; + if (targetProvider !== 'all') { + fetchUrl += `&provider=${targetProvider}`; + } - const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() }); + const res = await fetch(fetchUrl); const data = await res.json(); allChapters = data.chapters || []; @@ -175,18 +199,17 @@ async function loadChapters() { const totalEl = document.getElementById('total-chapters'); if (allChapters.length === 0) { - tbody.innerHTML = 'No chapters found on loaded extensions.'; + tbody.innerHTML = 'No chapters found.'; if (totalEl) totalEl.innerText = "0 Found"; return; } if (totalEl) totalEl.innerText = `${allChapters.length} Found`; - setupProviderFilter(); - setupReadButton(); chapterPagination.setTotalItems(filteredChapters.length); + chapterPagination.reset(); renderChapterTable(); } catch (err) { @@ -211,44 +234,31 @@ function applyChapterFilter() { 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; + if (!select || availableExtensions.length === 0) return; select.style.display = 'inline-block'; - select.innerHTML = ''; + select.innerHTML = ''; - providers.forEach(prov => { + const allOpt = document.createElement('option'); + allOpt.value = 'all'; + allOpt.innerText = 'Load All (Slower)'; + select.appendChild(allOpt); + + availableExtensions.forEach(ext => { const opt = document.createElement('option'); - opt.value = prov; - opt.innerText = prov; + opt.value = ext; + opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1); 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); - } + if (extensionName && availableExtensions.includes(extensionName)) { + select.value = extensionName; + } else if (availableExtensions.length > 0) { + select.value = availableExtensions[0]; } - 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(); + select.onchange = () => { + loadChapters(select.value); }; } diff --git a/docker/src/api/books/books.controller.ts b/docker/src/api/books/books.controller.ts index 5d07549..d8f7a8a 100644 --- a/docker/src/api/books/books.controller.ts +++ b/docker/src/api/books/books.controller.ts @@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) { try { const { id } = req.params; const source = req.query.source || 'anilist'; + const provider = req.query.provider; const isExternal = source !== 'anilist'; - return await booksService.getChaptersForBook(id, isExternal); - } catch { + return await booksService.getChaptersForBook(id, isExternal, provider); + } catch (err) { + console.error(err); return { chapters: [] }; } } - export async function getChapterContent(req: any, reply: FastifyReply) { try { const { bookId, chapter, provider } = req.params; diff --git a/docker/src/scripts/books/book.js b/docker/src/scripts/books/book.js index c649e84..22f2cb6 100644 --- a/docker/src/scripts/books/book.js +++ b/docker/src/scripts/books/book.js @@ -6,6 +6,8 @@ let bookSlug = null; let allChapters = []; let filteredChapters = []; +let availableExtensions = []; + const chapterPagination = Object.create(PaginationManager); chapterPagination.init(12, () => renderChapterTable()); @@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => { async function init() { try { - const urlData = URLUtils.parseEntityPath('book'); if (!urlData) { showError("Book Not Found"); @@ -29,6 +30,7 @@ async function init() { await loadBookMetadata(); + await loadAvailableExtensions(); await loadChapters(); await setupAddToListButton(); @@ -39,11 +41,23 @@ async function init() { } } +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, { headers: AuthUtils.getSimpleAuthHeaders() }); + const res = await fetch(fetchUrl); const data = await res.json(); if (data.error || !data) { @@ -154,17 +168,27 @@ function updateCustomAddButton() { } } -async function loadChapters() { +async function loadChapters(targetProvider = null) { const tbody = document.getElementById('chapters-body'); if (!tbody) return; - tbody.innerHTML = 'Searching extensions for chapters...'; + // Si no se pasa provider, intentamos pillar el del select o el primero disponible + if (!targetProvider) { + const select = document.getElementById('provider-filter'); + targetProvider = select ? select.value : (availableExtensions[0] || 'all'); + } + + tbody.innerHTML = 'Searching extension for chapters...'; try { const source = extensionName || 'anilist'; - const fetchUrl = `/api/book/${bookId}/chapters?source=${source}`; + // AƱadimos el query param 'provider' para que el backend filtre + let fetchUrl = `/api/book/${bookId}/chapters?source=${source}`; + if (targetProvider !== 'all') { + fetchUrl += `&provider=${targetProvider}`; + } - const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() }); + const res = await fetch(fetchUrl); const data = await res.json(); allChapters = data.chapters || []; @@ -175,18 +199,17 @@ async function loadChapters() { const totalEl = document.getElementById('total-chapters'); if (allChapters.length === 0) { - tbody.innerHTML = 'No chapters found on loaded extensions.'; + tbody.innerHTML = 'No chapters found.'; if (totalEl) totalEl.innerText = "0 Found"; return; } if (totalEl) totalEl.innerText = `${allChapters.length} Found`; - setupProviderFilter(); - setupReadButton(); chapterPagination.setTotalItems(filteredChapters.length); + chapterPagination.reset(); renderChapterTable(); } catch (err) { @@ -211,44 +234,31 @@ function applyChapterFilter() { 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; + if (!select || availableExtensions.length === 0) return; select.style.display = 'inline-block'; - select.innerHTML = ''; + select.innerHTML = ''; - providers.forEach(prov => { + const allOpt = document.createElement('option'); + allOpt.value = 'all'; + allOpt.innerText = 'Load All (Slower)'; + select.appendChild(allOpt); + + availableExtensions.forEach(ext => { const opt = document.createElement('option'); - opt.value = prov; - opt.innerText = prov; + opt.value = ext; + opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1); 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); - } + if (extensionName && availableExtensions.includes(extensionName)) { + select.value = extensionName; + } else if (availableExtensions.length > 0) { + select.value = availableExtensions[0]; } - 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(); + select.onchange = () => { + loadChapters(select.value); }; }