222 lines
7.6 KiB
JavaScript
222 lines
7.6 KiB
JavaScript
/**
|
|
* Sunday Comics - Archive Lazy Loading
|
|
* Implements infinite scroll for the archive page
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
let currentPage = 1;
|
|
let isLoading = false;
|
|
let hasMore = true;
|
|
const perPage = 24;
|
|
|
|
// Get elements
|
|
const archiveContent = document.querySelector('.archive-content');
|
|
if (!archiveContent) return; // Not on archive page
|
|
|
|
const totalComics = parseInt(archiveContent.dataset.totalComics || '0');
|
|
const initialBatch = parseInt(archiveContent.dataset.initialBatch || '24');
|
|
|
|
// Calculate if there are more comics to load
|
|
hasMore = totalComics > initialBatch;
|
|
|
|
// Create loading indicator
|
|
const loadingIndicator = document.createElement('div');
|
|
loadingIndicator.className = 'archive-loading';
|
|
loadingIndicator.innerHTML = '<p>Loading more comics...</p>';
|
|
loadingIndicator.style.display = 'none';
|
|
loadingIndicator.style.textAlign = 'center';
|
|
loadingIndicator.style.padding = '2rem';
|
|
archiveContent.parentNode.insertBefore(loadingIndicator, archiveContent.nextSibling);
|
|
|
|
/**
|
|
* Load more comics from the API
|
|
*/
|
|
async function loadMoreComics() {
|
|
if (isLoading || !hasMore) return;
|
|
|
|
isLoading = true;
|
|
loadingIndicator.style.display = 'block';
|
|
|
|
try {
|
|
currentPage++;
|
|
const response = await fetch(`/api/comics?page=${currentPage}&per_page=${perPage}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Add new comics to the DOM
|
|
appendComics(data.sections);
|
|
|
|
// Update state
|
|
hasMore = data.has_more;
|
|
|
|
if (!hasMore) {
|
|
loadingIndicator.innerHTML = '<p>End of archive</p>';
|
|
setTimeout(() => {
|
|
loadingIndicator.style.display = 'none';
|
|
}, 2000);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading more comics:', error);
|
|
loadingIndicator.innerHTML = '<p>Error loading comics. Please try again.</p>';
|
|
setTimeout(() => {
|
|
loadingIndicator.style.display = 'none';
|
|
isLoading = false;
|
|
}, 3000);
|
|
return;
|
|
}
|
|
|
|
isLoading = false;
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
|
|
/**
|
|
* Append comics to the archive
|
|
* @param {Array} sections - Array of section objects with title and comics
|
|
*/
|
|
function appendComics(sections) {
|
|
const archiveFullWidth = document.querySelector('.archive-content-fullwidth') !== null;
|
|
const sectionsEnabled = document.querySelector('.section-header') !== null;
|
|
|
|
sections.forEach(section => {
|
|
const sectionTitle = section.section_title;
|
|
const comics = section.comics;
|
|
|
|
// Check if we need to create a new section or append to existing
|
|
let targetGrid;
|
|
|
|
if (sectionsEnabled && sectionTitle) {
|
|
// Check if section already exists
|
|
const existingSection = findSectionByTitle(sectionTitle);
|
|
|
|
if (existingSection) {
|
|
// Append to existing section grid
|
|
targetGrid = existingSection.querySelector('.archive-grid');
|
|
} else {
|
|
// Create new section
|
|
const sectionHeader = document.createElement('div');
|
|
sectionHeader.className = 'section-header';
|
|
sectionHeader.innerHTML = `<h2>${sectionTitle}</h2>`;
|
|
archiveContent.appendChild(sectionHeader);
|
|
|
|
targetGrid = document.createElement('div');
|
|
targetGrid.className = 'archive-grid' + (archiveFullWidth ? ' archive-grid-fullwidth' : '');
|
|
archiveContent.appendChild(targetGrid);
|
|
}
|
|
} else {
|
|
// No sections or no title - use the last grid or create one
|
|
targetGrid = archiveContent.querySelector('.archive-grid:last-of-type');
|
|
|
|
if (!targetGrid) {
|
|
targetGrid = document.createElement('div');
|
|
targetGrid.className = 'archive-grid' + (archiveFullWidth ? ' archive-grid-fullwidth' : '');
|
|
archiveContent.appendChild(targetGrid);
|
|
}
|
|
}
|
|
|
|
// Add each comic to the grid
|
|
comics.forEach(comic => {
|
|
const item = createArchiveItem(comic, archiveFullWidth);
|
|
targetGrid.appendChild(item);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find an existing section by title
|
|
* @param {string} title - Section title to find
|
|
* @returns {Element|null} - The section element or null
|
|
*/
|
|
function findSectionByTitle(title) {
|
|
const sectionHeaders = archiveContent.querySelectorAll('.section-header h2');
|
|
for (const header of sectionHeaders) {
|
|
if (header.textContent.trim() === title) {
|
|
// Return the grid following this header
|
|
let nextEl = header.parentElement.nextElementSibling;
|
|
while (nextEl && !nextEl.classList.contains('archive-grid')) {
|
|
nextEl = nextEl.nextElementSibling;
|
|
}
|
|
return nextEl ? nextEl.parentElement : null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Create an archive item element
|
|
* @param {Object} comic - Comic data
|
|
* @param {boolean} fullWidth - Whether using full width layout
|
|
* @returns {Element} - The archive item element
|
|
*/
|
|
function createArchiveItem(comic, fullWidth) {
|
|
const item = document.createElement('div');
|
|
item.className = 'archive-item' + (fullWidth ? ' archive-item-fullwidth' : '');
|
|
|
|
const link = document.createElement('a');
|
|
link.href = `/comic/${comic.number}`;
|
|
|
|
const img = document.createElement('img');
|
|
img.src = `/static/images/thumbs/${comic.filename}`;
|
|
img.alt = comic.title || `#${comic.number}`;
|
|
img.loading = 'lazy';
|
|
img.onerror = function() {
|
|
this.onerror = null;
|
|
this.src = '/static/images/thumbs/default.jpg';
|
|
};
|
|
|
|
const info = document.createElement('div');
|
|
info.className = 'archive-info';
|
|
|
|
if (!fullWidth) {
|
|
const title = document.createElement('h3');
|
|
title.textContent = `#${comic.number}${comic.title ? ': ' + comic.title : ''}`;
|
|
info.appendChild(title);
|
|
}
|
|
|
|
const date = document.createElement('p');
|
|
date.className = 'archive-date';
|
|
date.textContent = comic.date;
|
|
info.appendChild(date);
|
|
|
|
link.appendChild(img);
|
|
link.appendChild(info);
|
|
item.appendChild(link);
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Check if user has scrolled near the bottom
|
|
*/
|
|
function checkScrollPosition() {
|
|
if (isLoading || !hasMore) return;
|
|
|
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
const windowHeight = window.innerHeight;
|
|
const documentHeight = document.documentElement.scrollHeight;
|
|
|
|
// Trigger when user is within 1000px of the bottom
|
|
if (scrollTop + windowHeight >= documentHeight - 1000) {
|
|
loadMoreComics();
|
|
}
|
|
}
|
|
|
|
// Set up scroll listener
|
|
let scrollTimeout;
|
|
window.addEventListener('scroll', function() {
|
|
if (scrollTimeout) {
|
|
clearTimeout(scrollTimeout);
|
|
}
|
|
scrollTimeout = setTimeout(checkScrollPosition, 100);
|
|
});
|
|
|
|
// Check initial scroll position (in case page is short)
|
|
setTimeout(checkScrollPosition, 500);
|
|
|
|
})();
|