lazy loading of chapters on book page
This commit is contained in:
@@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) {
|
|||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const source = req.query.source || 'anilist';
|
const source = req.query.source || 'anilist';
|
||||||
|
const provider = req.query.provider;
|
||||||
|
|
||||||
const isExternal = source !== 'anilist';
|
const isExternal = source !== 'anilist';
|
||||||
return await booksService.getChaptersForBook(id, isExternal);
|
return await booksService.getChaptersForBook(id, isExternal, provider);
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
return { chapters: [] };
|
return { chapters: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getChapterContent(req: any, reply: FastifyReply) {
|
export async function getChapterContent(req: any, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { bookId, chapter, provider } = req.params;
|
const { bookId, chapter, provider } = req.params;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ let bookSlug = null;
|
|||||||
let allChapters = [];
|
let allChapters = [];
|
||||||
let filteredChapters = [];
|
let filteredChapters = [];
|
||||||
|
|
||||||
|
let availableExtensions = [];
|
||||||
|
|
||||||
const chapterPagination = Object.create(PaginationManager);
|
const chapterPagination = Object.create(PaginationManager);
|
||||||
chapterPagination.init(12, () => renderChapterTable());
|
chapterPagination.init(12, () => renderChapterTable());
|
||||||
|
|
||||||
@@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const urlData = URLUtils.parseEntityPath('book');
|
const urlData = URLUtils.parseEntityPath('book');
|
||||||
if (!urlData) {
|
if (!urlData) {
|
||||||
showError("Book Not Found");
|
showError("Book Not Found");
|
||||||
@@ -29,6 +30,7 @@ async function init() {
|
|||||||
|
|
||||||
await loadBookMetadata();
|
await loadBookMetadata();
|
||||||
|
|
||||||
|
await loadAvailableExtensions();
|
||||||
await loadChapters();
|
await loadChapters();
|
||||||
|
|
||||||
await setupAddToListButton();
|
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() {
|
async function loadBookMetadata() {
|
||||||
const source = extensionName || 'anilist';
|
const source = extensionName || 'anilist';
|
||||||
const fetchUrl = `/api/book/${bookId}?source=${source}`;
|
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();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error || !data) {
|
if (data.error || !data) {
|
||||||
@@ -154,17 +168,27 @@ function updateCustomAddButton() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChapters() {
|
async function loadChapters(targetProvider = null) {
|
||||||
const tbody = document.getElementById('chapters-body');
|
const tbody = document.getElementById('chapters-body');
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
|
|
||||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extensions for chapters...</td></tr>';
|
// 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 = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extension for chapters...</td></tr>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const source = extensionName || 'anilist';
|
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();
|
const data = await res.json();
|
||||||
|
|
||||||
allChapters = data.chapters || [];
|
allChapters = data.chapters || [];
|
||||||
@@ -175,18 +199,17 @@ async function loadChapters() {
|
|||||||
const totalEl = document.getElementById('total-chapters');
|
const totalEl = document.getElementById('total-chapters');
|
||||||
|
|
||||||
if (allChapters.length === 0) {
|
if (allChapters.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters found on loaded extensions.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters found.</td></tr>';
|
||||||
if (totalEl) totalEl.innerText = "0 Found";
|
if (totalEl) totalEl.innerText = "0 Found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
|
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
|
||||||
|
|
||||||
setupProviderFilter();
|
|
||||||
|
|
||||||
setupReadButton();
|
setupReadButton();
|
||||||
|
|
||||||
chapterPagination.setTotalItems(filteredChapters.length);
|
chapterPagination.setTotalItems(filteredChapters.length);
|
||||||
|
chapterPagination.reset();
|
||||||
renderChapterTable();
|
renderChapterTable();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -211,44 +234,31 @@ function applyChapterFilter() {
|
|||||||
|
|
||||||
function setupProviderFilter() {
|
function setupProviderFilter() {
|
||||||
const select = document.getElementById('provider-filter');
|
const select = document.getElementById('provider-filter');
|
||||||
if (!select) return;
|
if (!select || availableExtensions.length === 0) return;
|
||||||
|
|
||||||
const providers = [...new Set(allChapters.map(ch => ch.provider))];
|
|
||||||
|
|
||||||
if (providers.length === 0) return;
|
|
||||||
|
|
||||||
select.style.display = 'inline-block';
|
select.style.display = 'inline-block';
|
||||||
select.innerHTML = '<option value="all">All Providers</option>';
|
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');
|
const opt = document.createElement('option');
|
||||||
opt.value = prov;
|
opt.value = ext;
|
||||||
opt.innerText = prov;
|
opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1);
|
||||||
select.appendChild(opt);
|
select.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (extensionName) {
|
if (extensionName && availableExtensions.includes(extensionName)) {
|
||||||
const extensionProvider = providers.find(
|
select.value = extensionName;
|
||||||
p => p.toLowerCase() === extensionName.toLowerCase()
|
} else if (availableExtensions.length > 0) {
|
||||||
);
|
select.value = availableExtensions[0];
|
||||||
|
|
||||||
if (extensionProvider) {
|
|
||||||
select.value = extensionProvider;
|
|
||||||
filteredChapters = allChapters.filter(ch => ch.provider === extensionProvider);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select.onchange = (e) => {
|
select.onchange = () => {
|
||||||
const selected = e.target.value;
|
loadChapters(select.value);
|
||||||
if (selected === 'all') {
|
|
||||||
filteredChapters = [...allChapters];
|
|
||||||
} else {
|
|
||||||
filteredChapters = allChapters.filter(ch => ch.provider === selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
chapterPagination.reset();
|
|
||||||
chapterPagination.setTotalItems(filteredChapters.length);
|
|
||||||
renderChapterTable();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) {
|
|||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const source = req.query.source || 'anilist';
|
const source = req.query.source || 'anilist';
|
||||||
|
const provider = req.query.provider;
|
||||||
|
|
||||||
const isExternal = source !== 'anilist';
|
const isExternal = source !== 'anilist';
|
||||||
return await booksService.getChaptersForBook(id, isExternal);
|
return await booksService.getChaptersForBook(id, isExternal, provider);
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
return { chapters: [] };
|
return { chapters: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getChapterContent(req: any, reply: FastifyReply) {
|
export async function getChapterContent(req: any, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { bookId, chapter, provider } = req.params;
|
const { bookId, chapter, provider } = req.params;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ let bookSlug = null;
|
|||||||
let allChapters = [];
|
let allChapters = [];
|
||||||
let filteredChapters = [];
|
let filteredChapters = [];
|
||||||
|
|
||||||
|
let availableExtensions = [];
|
||||||
|
|
||||||
const chapterPagination = Object.create(PaginationManager);
|
const chapterPagination = Object.create(PaginationManager);
|
||||||
chapterPagination.init(12, () => renderChapterTable());
|
chapterPagination.init(12, () => renderChapterTable());
|
||||||
|
|
||||||
@@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const urlData = URLUtils.parseEntityPath('book');
|
const urlData = URLUtils.parseEntityPath('book');
|
||||||
if (!urlData) {
|
if (!urlData) {
|
||||||
showError("Book Not Found");
|
showError("Book Not Found");
|
||||||
@@ -29,6 +30,7 @@ async function init() {
|
|||||||
|
|
||||||
await loadBookMetadata();
|
await loadBookMetadata();
|
||||||
|
|
||||||
|
await loadAvailableExtensions();
|
||||||
await loadChapters();
|
await loadChapters();
|
||||||
|
|
||||||
await setupAddToListButton();
|
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() {
|
async function loadBookMetadata() {
|
||||||
const source = extensionName || 'anilist';
|
const source = extensionName || 'anilist';
|
||||||
const fetchUrl = `/api/book/${bookId}?source=${source}`;
|
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();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error || !data) {
|
if (data.error || !data) {
|
||||||
@@ -154,17 +168,27 @@ function updateCustomAddButton() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChapters() {
|
async function loadChapters(targetProvider = null) {
|
||||||
const tbody = document.getElementById('chapters-body');
|
const tbody = document.getElementById('chapters-body');
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
|
|
||||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extensions for chapters...</td></tr>';
|
// 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 = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extension for chapters...</td></tr>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const source = extensionName || 'anilist';
|
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();
|
const data = await res.json();
|
||||||
|
|
||||||
allChapters = data.chapters || [];
|
allChapters = data.chapters || [];
|
||||||
@@ -175,18 +199,17 @@ async function loadChapters() {
|
|||||||
const totalEl = document.getElementById('total-chapters');
|
const totalEl = document.getElementById('total-chapters');
|
||||||
|
|
||||||
if (allChapters.length === 0) {
|
if (allChapters.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters found on loaded extensions.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">No chapters found.</td></tr>';
|
||||||
if (totalEl) totalEl.innerText = "0 Found";
|
if (totalEl) totalEl.innerText = "0 Found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
|
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
|
||||||
|
|
||||||
setupProviderFilter();
|
|
||||||
|
|
||||||
setupReadButton();
|
setupReadButton();
|
||||||
|
|
||||||
chapterPagination.setTotalItems(filteredChapters.length);
|
chapterPagination.setTotalItems(filteredChapters.length);
|
||||||
|
chapterPagination.reset();
|
||||||
renderChapterTable();
|
renderChapterTable();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -211,44 +234,31 @@ function applyChapterFilter() {
|
|||||||
|
|
||||||
function setupProviderFilter() {
|
function setupProviderFilter() {
|
||||||
const select = document.getElementById('provider-filter');
|
const select = document.getElementById('provider-filter');
|
||||||
if (!select) return;
|
if (!select || availableExtensions.length === 0) return;
|
||||||
|
|
||||||
const providers = [...new Set(allChapters.map(ch => ch.provider))];
|
|
||||||
|
|
||||||
if (providers.length === 0) return;
|
|
||||||
|
|
||||||
select.style.display = 'inline-block';
|
select.style.display = 'inline-block';
|
||||||
select.innerHTML = '<option value="all">All Providers</option>';
|
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');
|
const opt = document.createElement('option');
|
||||||
opt.value = prov;
|
opt.value = ext;
|
||||||
opt.innerText = prov;
|
opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1);
|
||||||
select.appendChild(opt);
|
select.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (extensionName) {
|
if (extensionName && availableExtensions.includes(extensionName)) {
|
||||||
const extensionProvider = providers.find(
|
select.value = extensionName;
|
||||||
p => p.toLowerCase() === extensionName.toLowerCase()
|
} else if (availableExtensions.length > 0) {
|
||||||
);
|
select.value = availableExtensions[0];
|
||||||
|
|
||||||
if (extensionProvider) {
|
|
||||||
select.value = extensionProvider;
|
|
||||||
filteredChapters = allChapters.filter(ch => ch.provider === extensionProvider);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select.onchange = (e) => {
|
select.onchange = () => {
|
||||||
const selected = e.target.value;
|
loadChapters(select.value);
|
||||||
if (selected === 'all') {
|
|
||||||
filteredChapters = [...allChapters];
|
|
||||||
} else {
|
|
||||||
filteredChapters = allChapters.filter(ch => ch.provider === selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
chapterPagination.reset();
|
|
||||||
chapterPagination.setTotalItems(filteredChapters.length);
|
|
||||||
renderChapterTable();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user