fix: handle duplicate favicons correctly in search results
All checks were successful
Run Integration Tests / test (push) Successful in 31s
All checks were successful
Run Integration Tests / test (push) Successful in 31s
This commit is contained in:
parent
00bbb5c015
commit
0d5184503d
3 changed files with 87 additions and 92 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// });
|
||||
})();
|
|
@ -193,7 +193,7 @@
|
|||
</noscript>
|
||||
</form>
|
||||
</div>
|
||||
<div id="template-data" data-page="{{ .Page }}" data-query="{{ .Query }}" data-type="text"></div>
|
||||
<div id="template-data" data-page="{{ .Page }}" data-query="{{ .Query }}" data-type="text" data-hard-cache-enabled="{{ .HardCacheEnabled }}"></div>
|
||||
<script defer src="/static/js/dynamicscrollingtext.js"></script>
|
||||
<script defer src="/static/js/autocomplete.js"></script>
|
||||
<script defer src="/static/js/minimenu.js"></script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue