From 0d5184503d52b6f1db21d162f3c5b42ee86faa15 Mon Sep 17 00:00:00 2001 From: partisan Date: Fri, 9 May 2025 11:29:34 +0200 Subject: [PATCH] fix: handle duplicate favicons correctly in search results --- static/js/dynamicscrollingimages.js | 2 - static/js/dynamicscrollingtext.js | 175 ++++++++++++++-------------- templates/text.html | 2 +- 3 files changed, 87 insertions(+), 92 deletions(-) diff --git a/static/js/dynamicscrollingimages.js b/static/js/dynamicscrollingimages.js index a15b253..ef0da92 100644 --- a/static/js/dynamicscrollingimages.js +++ b/static/js/dynamicscrollingimages.js @@ -1,4 +1,3 @@ -// dynamicscrollingimages.js (function() { // Add loading effects to image and title function addLoadingEffects(imgElement) { @@ -37,7 +36,6 @@ } } - // Rest of your existing code with minor additions const imageStatusInterval = 500; const scrollThreshold = 500; const loadingIndicator = document.getElementById('message-bottom-right'); let loadingTimer; diff --git a/static/js/dynamicscrollingtext.js b/static/js/dynamicscrollingtext.js index 36a5e8a..8181978 100644 --- a/static/js/dynamicscrollingtext.js +++ b/static/js/dynamicscrollingtext.js @@ -7,7 +7,7 @@ // Track all favicon/image elements and their IDs let allMediaElements = []; let allMediaIds = []; - let statusCheckTimeout = null; + const mediaMap = new Map(); // Add loading effects to image/favicon and associated text function addLoadingEffects(imgElement) { @@ -34,18 +34,20 @@ } // Handle image/favicon loading errors - function handleImageError(imgElement) { + 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); - imgElement.closest('.favicon-wrapper')?.classList.remove('loading'); - if (title) title.classList.remove('title-loading'); - - if (type === 'image') { - container.style.display = 'none'; + if (retryCount > 0) { + setTimeout(() => { + imgElement.src = imgElement.getAttribute('data-full'); + imgElement.onerror = () => handleImageError(imgElement, retryCount - 1, retryDelay); + }, retryDelay); } else { - imgElement.src = '/static/images/missing.svg'; + imgElement.closest('.favicon-wrapper')?.classList.remove('loading'); + if (title) title.classList.remove('title-loading'); + if (type === 'image') container.style.display = 'none'; } } @@ -77,91 +79,83 @@ // 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'); + if (!id) return; + + let wrapper = imgElement.closest('.favicon-wrapper'); + if (!wrapper) { + wrapper = document.createElement('span'); wrapper.classList.add('favicon-wrapper'); - imgElement.parentElement.replaceChild(wrapper, imgElement); + imgElement.replaceWith(wrapper); wrapper.appendChild(imgElement); } - - // Track and style - allMediaElements.push(imgElement); - allMediaIds.push(id); + addLoadingEffects(imgElement); - - - if (!hardCacheEnabled) { - imgElement.src = ''; // don't show anything until actual URL arrives - } - - // Schedule a status check if not already pending - if (!statusCheckTimeout) { - statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval); + + 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() { - statusCheckTimeout = null; - - if (allMediaIds.length === 0) return; - - // Group IDs to avoid very long URLs + const allIds = Array.from(mediaMap.keys()); + if (allIds.length === 0) return; + const idGroups = []; - for (let i = 0; i < allMediaIds.length; i += 50) { - idGroups.push(allMediaIds.slice(i, i + 50)); + for (let i = 0; i < allIds.length; i += 50) { + idGroups.push(allIds.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 () => { + const stillPending = new Map(); + for (const group of idGroups) { try { - await checkGroup(group); - } catch (error) { - console.error('Status check error:', error); + 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)); + } + }); } } - - // If we still have pending items, schedule another check - if (allMediaIds.length > 0) { - statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval); + + mediaMap.clear(); + for (const [id, imgs] of stillPending) { + mediaMap.set(id, imgs); } }; - + processGroups(); - } + } function fetchNextPage() { if (isFetching || noMoreImages) return; @@ -215,19 +209,22 @@ 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 + function startStatusPolling() { + checkMediaStatus(); + setInterval(checkMediaStatus, statusCheckInterval); + } + if (document.readyState === 'complete') { initializeMediaElements(); + if (hardCacheEnabled) startStatusPolling(); } else { - window.addEventListener('load', initializeMediaElements); - } + window.addEventListener('load', () => { + initializeMediaElements(); + if (hardCacheEnabled) startStatusPolling(); + }); + } // Infinite scroll handler window.addEventListener('scroll', () => { @@ -237,10 +234,10 @@ } }); - // Clean up on page unload - window.addEventListener('beforeunload', () => { - if (statusCheckTimeout) { - clearTimeout(statusCheckTimeout); - } - }); + // // Clean up on page unload + // window.addEventListener('beforeunload', () => { + // if (statusCheckTimeout) { + // clearTimeout(statusCheckTimeout); + // } + // }); })(); \ No newline at end of file diff --git a/templates/text.html b/templates/text.html index cca6630..8acd48b 100755 --- a/templates/text.html +++ b/templates/text.html @@ -193,7 +193,7 @@ -
+