2025-05-09 08:26:14 +02:00
|
|
|
(function() {
|
|
|
|
// Get template data and configuration
|
|
|
|
const templateData = document.getElementById('template-data');
|
|
|
|
const type = templateData.getAttribute('data-type');
|
|
|
|
const hardCacheEnabled = templateData.getAttribute('data-hard-cache-enabled') === 'true';
|
|
|
|
|
|
|
|
// Track all favicon/image elements and their IDs
|
2025-05-09 08:42:08 +02:00
|
|
|
let allMediaElements = [];
|
|
|
|
let allMediaIds = [];
|
2025-05-09 08:26:14 +02:00
|
|
|
let statusCheckTimeout = null;
|
|
|
|
|
|
|
|
// Add loading effects to image/favicon and associated text
|
|
|
|
function addLoadingEffects(imgElement) {
|
|
|
|
const container = imgElement.closest(type === 'image' ? '.image' : '.result_item');
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
|
|
|
|
const title = container.querySelector(titleSelector);
|
|
|
|
imgElement.closest('.favicon-wrapper')?.classList.add('loading');
|
|
|
|
// if (title) title.classList.add('title-loading');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove loading effects when image/favicon loads
|
|
|
|
function removeLoadingEffects(imgElement) {
|
|
|
|
const container = imgElement.closest(type === 'image' ? '.image' : '.result_item');
|
|
|
|
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
|
|
|
|
const title = container?.querySelector(titleSelector);
|
|
|
|
imgElement.closest('.favicon-wrapper')?.classList.remove('loading');
|
|
|
|
if (title) title.classList.remove('title-loading');
|
|
|
|
|
|
|
|
if (type === 'image' && imgElement.src.endsWith('/images/missing.svg')) {
|
|
|
|
container.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle image/favicon loading errors
|
|
|
|
function handleImageError(imgElement) {
|
|
|
|
const container = imgElement.closest(type === 'image' ? '.image' : '.result_item');
|
|
|
|
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
|
|
|
|
const title = container?.querySelector(titleSelector);
|
|
|
|
|
|
|
|
imgElement.closest('.favicon-wrapper')?.classList.remove('loading');
|
|
|
|
if (title) title.classList.remove('title-loading');
|
|
|
|
|
|
|
|
if (type === 'image') {
|
|
|
|
container.style.display = 'none';
|
|
|
|
} else {
|
|
|
|
imgElement.src = '/static/images/missing.svg';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shared configuration
|
|
|
|
const statusCheckInterval = 500;
|
|
|
|
const scrollThreshold = 500;
|
|
|
|
const loadingIndicator = document.getElementById('message-bottom-right');
|
|
|
|
let loadingTimer;
|
|
|
|
let isFetching = false;
|
|
|
|
let page = parseInt(templateData.getAttribute('data-page')) || 1;
|
|
|
|
let query = templateData.getAttribute('data-query');
|
|
|
|
let noMoreImages = false;
|
|
|
|
|
|
|
|
function showLoadingMessage() {
|
|
|
|
loadingIndicator.classList.add('visible');
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideLoadingMessage() {
|
|
|
|
loadingIndicator.classList.remove('visible');
|
|
|
|
}
|
|
|
|
|
|
|
|
function ensureScrollable() {
|
|
|
|
if (noMoreImages) return;
|
|
|
|
if (document.body.scrollHeight <= window.innerHeight) {
|
|
|
|
fetchNextPage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register a new media element for tracking
|
|
|
|
function registerMediaElement(imgElement) {
|
|
|
|
const id = imgElement.getAttribute('data-id');
|
|
|
|
if (!id || allMediaIds.includes(id)) return;
|
|
|
|
|
|
|
|
// Wrap the image in a .favicon-wrapper if not already
|
|
|
|
if (!imgElement.parentElement.classList.contains('favicon-wrapper')) {
|
|
|
|
const wrapper = document.createElement('span');
|
|
|
|
wrapper.classList.add('favicon-wrapper');
|
|
|
|
imgElement.parentElement.replaceChild(wrapper, imgElement);
|
|
|
|
wrapper.appendChild(imgElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Track and style
|
|
|
|
allMediaElements.push(imgElement);
|
|
|
|
allMediaIds.push(id);
|
|
|
|
addLoadingEffects(imgElement);
|
|
|
|
|
|
|
|
|
2025-05-09 08:42:08 +02:00
|
|
|
if (!hardCacheEnabled) {
|
|
|
|
imgElement.src = ''; // don't show anything until actual URL arrives
|
|
|
|
}
|
2025-05-09 08:26:14 +02:00
|
|
|
|
|
|
|
// Schedule a status check if not already pending
|
|
|
|
if (!statusCheckTimeout) {
|
|
|
|
statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check status of all tracked media elements
|
|
|
|
function checkMediaStatus() {
|
|
|
|
statusCheckTimeout = null;
|
|
|
|
|
|
|
|
if (allMediaIds.length === 0) return;
|
|
|
|
|
|
|
|
// Group IDs to avoid very long URLs
|
|
|
|
const idGroups = [];
|
|
|
|
for (let i = 0; i < allMediaIds.length; i += 50) {
|
|
|
|
idGroups.push(allMediaIds.slice(i, i + 50));
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkGroup = (group) => {
|
|
|
|
return fetch(`/image_status?image_ids=${group.join(',')}`)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(statusMap => {
|
|
|
|
const pendingElements = [];
|
|
|
|
const pendingIds = [];
|
|
|
|
|
|
|
|
allMediaElements.forEach((imgElement, index) => {
|
|
|
|
const id = allMediaIds[index];
|
|
|
|
if (group.includes(id)) {
|
|
|
|
if (statusMap[id]) {
|
|
|
|
if (imgElement.src !== statusMap[id]) {
|
|
|
|
imgElement.src = statusMap[id];
|
|
|
|
imgElement.onload = () => removeLoadingEffects(imgElement);
|
|
|
|
imgElement.onerror = () => handleImageError(imgElement);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pendingElements.push(imgElement);
|
|
|
|
pendingIds.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update global arrays with remaining pending items
|
|
|
|
allMediaElements = pendingElements;
|
|
|
|
allMediaIds = pendingIds;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Process all groups sequentially
|
|
|
|
const processGroups = async () => {
|
|
|
|
for (const group of idGroups) {
|
|
|
|
try {
|
|
|
|
await checkGroup(group);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Status check error:', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we still have pending items, schedule another check
|
|
|
|
if (allMediaIds.length > 0) {
|
|
|
|
statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
processGroups();
|
|
|
|
}
|
|
|
|
|
|
|
|
function fetchNextPage() {
|
|
|
|
if (isFetching || noMoreImages) return;
|
|
|
|
|
|
|
|
loadingTimer = setTimeout(() => {
|
|
|
|
showLoadingMessage();
|
|
|
|
}, 150);
|
|
|
|
|
|
|
|
isFetching = true;
|
|
|
|
page += 1;
|
|
|
|
|
|
|
|
fetch(`/search?q=${encodeURIComponent(query)}&t=${type}&p=${page}&ajax=true`)
|
|
|
|
.then(response => response.text())
|
|
|
|
.then(html => {
|
|
|
|
clearTimeout(loadingTimer);
|
|
|
|
hideLoadingMessage();
|
|
|
|
|
|
|
|
let tempDiv = document.createElement('div');
|
|
|
|
tempDiv.innerHTML = html;
|
|
|
|
let newItems = tempDiv.querySelectorAll(type === 'image' ? '.image' : '.result_item');
|
|
|
|
|
|
|
|
if (newItems.length > 0) {
|
|
|
|
let resultsContainer = document.querySelector(type === 'image' ? '.images' : '.results');
|
|
|
|
newItems.forEach(item => {
|
|
|
|
let clonedItem = item.cloneNode(true);
|
|
|
|
resultsContainer.appendChild(clonedItem);
|
|
|
|
|
|
|
|
// Register any new media elements
|
|
|
|
const img = clonedItem.querySelector('img[data-id]');
|
|
|
|
if (img) {
|
|
|
|
registerMediaElement(img);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ensureScrollable();
|
|
|
|
} else {
|
|
|
|
noMoreImages = true;
|
|
|
|
}
|
|
|
|
isFetching = false;
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
clearTimeout(loadingTimer);
|
|
|
|
hideLoadingMessage();
|
|
|
|
console.error('Fetch error:', error);
|
|
|
|
isFetching = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize all existing media elements
|
|
|
|
function initializeMediaElements() {
|
|
|
|
document.querySelectorAll('img[data-id]').forEach(img => {
|
|
|
|
registerMediaElement(img);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Start periodic checks if hard cache is enabled
|
|
|
|
if (hardCacheEnabled && allMediaIds.length > 0) {
|
|
|
|
statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize when DOM is ready
|
|
|
|
if (document.readyState === 'complete') {
|
|
|
|
initializeMediaElements();
|
|
|
|
} else {
|
|
|
|
window.addEventListener('load', initializeMediaElements);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Infinite scroll handler
|
|
|
|
window.addEventListener('scroll', () => {
|
|
|
|
if (isFetching || noMoreImages) return;
|
|
|
|
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
|
|
|
|
fetchNextPage();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Clean up on page unload
|
|
|
|
window.addEventListener('beforeunload', () => {
|
|
|
|
if (statusCheckTimeout) {
|
|
|
|
clearTimeout(statusCheckTimeout);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|