Search/static/js/dynamicscrollingtext.js
partisan 0d5184503d
All checks were successful
Run Integration Tests / test (push) Successful in 31s
fix: handle duplicate favicons correctly in search results
2025-05-09 11:29:34 +02:00

243 lines
No EOL
8.8 KiB
JavaScript

(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
let allMediaElements = [];
let allMediaIds = [];
const mediaMap = new Map();
// 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, retryCount = 10, retryDelay = 500) {
const container = imgElement.closest(type === 'image' ? '.image' : '.result_item');
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
const title = container?.querySelector(titleSelector);
if (retryCount > 0) {
setTimeout(() => {
imgElement.src = imgElement.getAttribute('data-full');
imgElement.onerror = () => handleImageError(imgElement, retryCount - 1, retryDelay);
}, retryDelay);
} else {
imgElement.closest('.favicon-wrapper')?.classList.remove('loading');
if (title) title.classList.remove('title-loading');
if (type === 'image') container.style.display = 'none';
}
}
// 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) return;
let wrapper = imgElement.closest('.favicon-wrapper');
if (!wrapper) {
wrapper = document.createElement('span');
wrapper.classList.add('favicon-wrapper');
imgElement.replaceWith(wrapper);
wrapper.appendChild(imgElement);
}
addLoadingEffects(imgElement);
if (hardCacheEnabled) {
imgElement.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
imgElement.onerror = () => handleImageError(imgElement, 3, 1000);
} else {
imgElement.src = imgElement.getAttribute('data-full');
imgElement.onload = () => removeLoadingEffects(imgElement);
imgElement.onerror = () => handleImageError(imgElement, 3, 1000);
}
// Track it
if (!mediaMap.has(id)) {
mediaMap.set(id, []);
}
mediaMap.get(id).push(imgElement);
}
// Check status of all tracked media elements
function checkMediaStatus() {
const allIds = Array.from(mediaMap.keys());
if (allIds.length === 0) return;
const idGroups = [];
for (let i = 0; i < allIds.length; i += 50) {
idGroups.push(allIds.slice(i, i + 50));
}
const processGroups = async () => {
const stillPending = new Map();
for (const group of idGroups) {
try {
const response = await fetch(`/image_status?image_ids=${group.join(',')}`);
const statusMap = await response.json();
group.forEach(id => {
const elements = mediaMap.get(id);
const resolved = statusMap[id];
if (resolved && resolved !== 'pending') {
elements.forEach(img => {
img.src = resolved;
img.onload = () => removeLoadingEffects(img);
img.onerror = () => handleImageError(img);
});
} else {
stillPending.set(id, elements);
}
});
} catch (err) {
console.error('Status check failed:', err);
group.forEach(id => {
if (mediaMap.has(id)) {
stillPending.set(id, mediaMap.get(id));
}
});
}
}
mediaMap.clear();
for (const [id, imgs] of stillPending) {
mediaMap.set(id, imgs);
}
};
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);
});
}
function startStatusPolling() {
checkMediaStatus();
setInterval(checkMediaStatus, statusCheckInterval);
}
if (document.readyState === 'complete') {
initializeMediaElements();
if (hardCacheEnabled) startStatusPolling();
} else {
window.addEventListener('load', () => {
initializeMediaElements();
if (hardCacheEnabled) startStatusPolling();
});
}
// 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);
// }
// });
})();