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() {
|
(function() {
|
||||||
// Add loading effects to image and title
|
// Add loading effects to image and title
|
||||||
function addLoadingEffects(imgElement) {
|
function addLoadingEffects(imgElement) {
|
||||||
|
@ -37,7 +36,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rest of your existing code with minor additions
|
|
||||||
const imageStatusInterval = 500;
|
const imageStatusInterval = 500;
|
||||||
const scrollThreshold = 500;
|
const scrollThreshold = 500;
|
||||||
const loadingIndicator = document.getElementById('message-bottom-right'); let loadingTimer;
|
const loadingIndicator = document.getElementById('message-bottom-right'); let loadingTimer;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// Track all favicon/image elements and their IDs
|
// Track all favicon/image elements and their IDs
|
||||||
let allMediaElements = [];
|
let allMediaElements = [];
|
||||||
let allMediaIds = [];
|
let allMediaIds = [];
|
||||||
let statusCheckTimeout = null;
|
const mediaMap = new Map();
|
||||||
|
|
||||||
// Add loading effects to image/favicon and associated text
|
// Add loading effects to image/favicon and associated text
|
||||||
function addLoadingEffects(imgElement) {
|
function addLoadingEffects(imgElement) {
|
||||||
|
@ -34,18 +34,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle image/favicon loading errors
|
// 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 container = imgElement.closest(type === 'image' ? '.image' : '.result_item');
|
||||||
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
|
const titleSelector = type === 'image' ? '.img_title' : '.result-url';
|
||||||
const title = container?.querySelector(titleSelector);
|
const title = container?.querySelector(titleSelector);
|
||||||
|
|
||||||
imgElement.closest('.favicon-wrapper')?.classList.remove('loading');
|
if (retryCount > 0) {
|
||||||
if (title) title.classList.remove('title-loading');
|
setTimeout(() => {
|
||||||
|
imgElement.src = imgElement.getAttribute('data-full');
|
||||||
if (type === 'image') {
|
imgElement.onerror = () => handleImageError(imgElement, retryCount - 1, retryDelay);
|
||||||
container.style.display = 'none';
|
}, retryDelay);
|
||||||
} else {
|
} 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,86 +79,78 @@
|
||||||
// Register a new media element for tracking
|
// Register a new media element for tracking
|
||||||
function registerMediaElement(imgElement) {
|
function registerMediaElement(imgElement) {
|
||||||
const id = imgElement.getAttribute('data-id');
|
const id = imgElement.getAttribute('data-id');
|
||||||
if (!id || allMediaIds.includes(id)) return;
|
if (!id) return;
|
||||||
|
|
||||||
// Wrap the image in a .favicon-wrapper if not already
|
let wrapper = imgElement.closest('.favicon-wrapper');
|
||||||
if (!imgElement.parentElement.classList.contains('favicon-wrapper')) {
|
if (!wrapper) {
|
||||||
const wrapper = document.createElement('span');
|
wrapper = document.createElement('span');
|
||||||
wrapper.classList.add('favicon-wrapper');
|
wrapper.classList.add('favicon-wrapper');
|
||||||
imgElement.parentElement.replaceChild(wrapper, imgElement);
|
imgElement.replaceWith(wrapper);
|
||||||
wrapper.appendChild(imgElement);
|
wrapper.appendChild(imgElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track and style
|
|
||||||
allMediaElements.push(imgElement);
|
|
||||||
allMediaIds.push(id);
|
|
||||||
addLoadingEffects(imgElement);
|
addLoadingEffects(imgElement);
|
||||||
|
|
||||||
|
if (hardCacheEnabled) {
|
||||||
if (!hardCacheEnabled) {
|
imgElement.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||||
imgElement.src = ''; // don't show anything until actual URL arrives
|
imgElement.onerror = () => handleImageError(imgElement, 3, 1000);
|
||||||
|
} else {
|
||||||
|
imgElement.src = imgElement.getAttribute('data-full');
|
||||||
|
imgElement.onload = () => removeLoadingEffects(imgElement);
|
||||||
|
imgElement.onerror = () => handleImageError(imgElement, 3, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule a status check if not already pending
|
// Track it
|
||||||
if (!statusCheckTimeout) {
|
if (!mediaMap.has(id)) {
|
||||||
statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval);
|
mediaMap.set(id, []);
|
||||||
}
|
}
|
||||||
|
mediaMap.get(id).push(imgElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check status of all tracked media elements
|
// Check status of all tracked media elements
|
||||||
function checkMediaStatus() {
|
function checkMediaStatus() {
|
||||||
statusCheckTimeout = null;
|
const allIds = Array.from(mediaMap.keys());
|
||||||
|
if (allIds.length === 0) return;
|
||||||
|
|
||||||
if (allMediaIds.length === 0) return;
|
|
||||||
|
|
||||||
// Group IDs to avoid very long URLs
|
|
||||||
const idGroups = [];
|
const idGroups = [];
|
||||||
for (let i = 0; i < allMediaIds.length; i += 50) {
|
for (let i = 0; i < allIds.length; i += 50) {
|
||||||
idGroups.push(allMediaIds.slice(i, 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 processGroups = async () => {
|
||||||
|
const stillPending = new Map();
|
||||||
|
|
||||||
for (const group of idGroups) {
|
for (const group of idGroups) {
|
||||||
try {
|
try {
|
||||||
await checkGroup(group);
|
const response = await fetch(`/image_status?image_ids=${group.join(',')}`);
|
||||||
} catch (error) {
|
const statusMap = await response.json();
|
||||||
console.error('Status check error:', error);
|
|
||||||
|
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
|
mediaMap.clear();
|
||||||
if (allMediaIds.length > 0) {
|
for (const [id, imgs] of stillPending) {
|
||||||
statusCheckTimeout = setTimeout(checkMediaStatus, statusCheckInterval);
|
mediaMap.set(id, imgs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -215,18 +209,21 @@
|
||||||
document.querySelectorAll('img[data-id]').forEach(img => {
|
document.querySelectorAll('img[data-id]').forEach(img => {
|
||||||
registerMediaElement(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') {
|
if (document.readyState === 'complete') {
|
||||||
initializeMediaElements();
|
initializeMediaElements();
|
||||||
|
if (hardCacheEnabled) startStatusPolling();
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener('load', initializeMediaElements);
|
window.addEventListener('load', () => {
|
||||||
|
initializeMediaElements();
|
||||||
|
if (hardCacheEnabled) startStatusPolling();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infinite scroll handler
|
// Infinite scroll handler
|
||||||
|
@ -237,10 +234,10 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up on page unload
|
// // Clean up on page unload
|
||||||
window.addEventListener('beforeunload', () => {
|
// window.addEventListener('beforeunload', () => {
|
||||||
if (statusCheckTimeout) {
|
// if (statusCheckTimeout) {
|
||||||
clearTimeout(statusCheckTimeout);
|
// clearTimeout(statusCheckTimeout);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
})();
|
})();
|
|
@ -193,7 +193,7 @@
|
||||||
</noscript>
|
</noscript>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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/dynamicscrollingtext.js"></script>
|
||||||
<script defer src="/static/js/autocomplete.js"></script>
|
<script defer src="/static/js/autocomplete.js"></script>
|
||||||
<script defer src="/static/js/minimenu.js"></script>
|
<script defer src="/static/js/minimenu.js"></script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue