373 lines
No EOL
19 KiB
HTML
Executable file
373 lines
No EOL
19 KiB
HTML
Executable file
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
{{ if .IsThemeDark }}
|
||
<meta name="darkreader-lock">
|
||
{{ end }}
|
||
<title>{{ .Query }} - {{ translate "site_name" }}</title>
|
||
<link rel="stylesheet" href="/static/css/style.css">
|
||
<link rel="stylesheet" href="/static/css/style-fixedwidth.css">
|
||
<link rel="stylesheet" href="/static/css/{{.Theme}}.css">
|
||
<link rel="search" type="application/opensearchdescription+xml" title="{{ translate "site_name" }}" href="/opensearch.xml">
|
||
</head>
|
||
<body>
|
||
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
|
||
<h1 class="logomobile"><a class="no-decoration" href="./">{{ translate "site_name" }}</a></h1>
|
||
<div class="wrapper-results">
|
||
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
|
||
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="image">search</button>
|
||
<div class="autocomplete">
|
||
<ul>
|
||
</ul>
|
||
</div>
|
||
<input type="submit" class="hide" name="t" value="image" />
|
||
</div>
|
||
<div class="sub-search-button-wrapper">
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="text">search</button>
|
||
<button name="t" value="text" class="clickable">{{ translate "web" }}</button>
|
||
</div>
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable search-active" name="t" value="image">image</button>
|
||
<button name="t" value="image" class="clickable search-active">{{ translate "images" }}</button>
|
||
</div>
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="video">movie</button>
|
||
<button name="t" value="video" class="clickable">{{ translate "videos" }}</button>
|
||
</div>
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="forum">forum</button>
|
||
<button name="t" value="forum" class="clickable">{{ translate "forums" }}</button>
|
||
</div>
|
||
<div id="content" class="js-enabled">
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="map">map</button>
|
||
<button name="t" value="map" class="clickable">{{ translate "maps" }}</button>
|
||
</div>
|
||
</div>
|
||
<div class="search-container-results-btn">
|
||
<button id="sub-search-wrapper-ico" class="material-icons-round clickable" name="t" value="file">share</button>
|
||
<button name="t" value="file" class="clickable">{{ translate "torrents" }}</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<form class="results_settings" action="/search" method="get">
|
||
<input type="hidden" name="q" value="{{ .Query }}">
|
||
<select class="results-settings" name="safe" id="safeSearchSelect">
|
||
<option value="disabled" {{if eq .Safe "disabled"}}selected{{end}}>{{ translate "safe_search_off" }}</option>
|
||
<option value="active" {{if eq .Safe "active"}}selected{{end}}>{{ translate "safe_search_on" }}</option>
|
||
</select>
|
||
<select class="results-settings" name="lang" id="languageSelect">
|
||
{{range .LanguageOptions}}
|
||
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option> <!-- this is too wide or too less, fix -->
|
||
{{end}}
|
||
</select>
|
||
<button class="results-save" name="t" value="image">{{ translate "save_settings" }}</button>
|
||
</form>
|
||
<div class="search-results" id="results">
|
||
|
||
<!-- Results go here -->
|
||
{{ if .Results }}
|
||
<div class="images images_viewer_hidden">
|
||
<!-- Images Grid -->
|
||
{{ range $index, $result := .Results }}
|
||
<div class="image">
|
||
<img
|
||
src="/static/images/placeholder.svg"
|
||
data-id="{{ $result.ID }}"
|
||
alt="{{ .Title }}"
|
||
data-full="{{ .ProxyFull }}"
|
||
data-proxy-full="{{ .ProxyThumb }}"
|
||
class="clickable"
|
||
/>
|
||
<div class="resolution">{{ .Width }} × {{ .Height }}</div>
|
||
<div class="details">
|
||
<span class="img_title clickable">{{ .Title }}</span>
|
||
<a href="{{ .Source }}" target="_blank" class="img_source">{{ translate "source" }}</a>
|
||
</div>
|
||
</div>
|
||
{{ end }}
|
||
</div>
|
||
|
||
<noscript>
|
||
<div class="prev-next prev-img">
|
||
<form action="/search" method="get">
|
||
<input type="hidden" name="q" value="{{ .Query }}">
|
||
<input type="hidden" name="t" value="image">
|
||
{{ if .HasPrevPage }}
|
||
<button type="submit" name="p" value="{{ sub .Page 1 }}">{{ translate "previous" }}</button>
|
||
{{ end }}
|
||
{{ if .HasNextPage }}
|
||
<button type="submit" name="p" value="{{ add .Page 1 }}">{{ translate "next" }}</button>
|
||
{{ end }}
|
||
</form>
|
||
</div>
|
||
</noscript>
|
||
{{ else if .NoResults }}
|
||
<div class="no-results">{{ translate "no_results" .Query }}</div>
|
||
{{ else }}
|
||
<div class="no-more-results">{{ translate "no_more_results" }}</div>
|
||
{{ end }}
|
||
</div>
|
||
<div class="message-bottom-left" id="message-bottom-left">
|
||
<span>{{ translate "searching_for_new_results" }}</span>
|
||
</div>
|
||
|
||
<div id="image-viewer-overlay" style="display: none;"></div>
|
||
|
||
<div id="template-data" data-page="{{ .Page }}" data-query="{{ .Query }}" data-type="image" data-hard-cache-enabled="{{ .HardCacheEnabled }}"></div>
|
||
<script defer src="/static/js/dynamicscrolling.js"></script>
|
||
<script defer src="/static/js/autocomplete.js"></script>
|
||
<script defer src="/static/js/imagetitletrim.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
let viewerOpen = false;
|
||
|
||
const viewerOverlay = document.getElementById('image-viewer-overlay');
|
||
viewerOverlay.innerHTML = `
|
||
<div id="image-viewer" class="image_view image_hide">
|
||
<div class="image-view-close">
|
||
<!-- <button class="btn-nostyle">
|
||
<div id="viewer-prev-button" class="material-icons-round icon_visibility clickable image-before">navigate_before</div>
|
||
</button>
|
||
<button class="btn-nostyle">
|
||
<div id="viewer-next-button" class="material-icons-round icon_visibility clickable image-next">navigate_next</div>
|
||
</button> FIX THIS LATER! --!>
|
||
<button class="btn-nostyle">
|
||
<div id="viewer-close-button" class="material-icons-round icon_visibility clickable image-close">close</div>
|
||
</button>
|
||
</div>
|
||
<a class="image-viewer-link">
|
||
<div class="view-image" id="viewer-image-container">
|
||
<img id="viewer-image" class="view-image-img" src="" alt="">
|
||
</div>
|
||
</a>
|
||
<p class="image-alt" id="viewer-title"></p>
|
||
<p>View source: <a id="viewer-source-button" class="image-source" href="" target="_blank"></a></p>
|
||
<p>
|
||
<a class="full-size" href="#">Show source website</a>
|
||
<a class="proxy-size" href="#">Show in fullscreen</a>
|
||
</p>
|
||
</div>
|
||
`;
|
||
|
||
const imageView = document.getElementById('image-viewer');
|
||
|
||
function openImageViewer(element) {
|
||
const parentImageDiv = element.closest('.image');
|
||
if (!parentImageDiv) return;
|
||
|
||
const imgElement = parentImageDiv.querySelector('img.clickable');
|
||
const fullImageUrl = imgElement.dataset.proxyFull; // Use data-proxy-full for ProxyFull
|
||
const thumbnailUrl = imgElement.src; // Use ProxyThumb for the thumbnail
|
||
const title = imgElement.alt;
|
||
const sourceUrl = parentImageDiv.querySelector('.img_source').href; // Source webpage URL
|
||
|
||
if (!fullImageUrl || viewerOpen) {
|
||
return; // Don't open if data is missing or viewer is already open
|
||
}
|
||
viewerOpen = true;
|
||
|
||
const viewerImage = document.getElementById('viewer-image');
|
||
const viewerTitle = document.getElementById('viewer-title');
|
||
const viewerSourceButton = document.getElementById('viewer-source-button');
|
||
const fullSizeLink = imageView.querySelector('.full-size');
|
||
const proxySizeLink = imageView.querySelector('.proxy-size');
|
||
|
||
// Set the viewer image to ProxyFull
|
||
viewerImage.src = fullImageUrl;
|
||
viewerTitle.textContent = title;
|
||
viewerSourceButton.href = sourceUrl;
|
||
fullSizeLink.href = sourceUrl; // Link to the source website
|
||
proxySizeLink.href = fullImageUrl; // Link to the proxied full-size image
|
||
|
||
viewerOverlay.style.display = 'flex';
|
||
imageView.classList.remove('image_hide');
|
||
imageView.classList.add('image_show');
|
||
}
|
||
|
||
// Attach event listener to the document body
|
||
document.body.addEventListener('click', function(e) {
|
||
let target = e.target;
|
||
let clickableElement = target.closest('img.clickable, .img_title.clickable');
|
||
|
||
if (clickableElement) {
|
||
e.preventDefault();
|
||
openImageViewer(clickableElement);
|
||
}
|
||
});
|
||
|
||
function closeImageViewer() {
|
||
imageView.classList.remove('image_show');
|
||
imageView.classList.add('image_hide');
|
||
viewerOverlay.style.display = 'none';
|
||
viewerOpen = false;
|
||
}
|
||
|
||
// Close viewer on overlay or button click
|
||
document.getElementById('viewer-close-button').addEventListener('click', closeImageViewer);
|
||
viewerOverlay.addEventListener('click', function(e) {
|
||
if (e.target === viewerOverlay) {
|
||
closeImageViewer();
|
||
}
|
||
});
|
||
|
||
// Handle keyboard events for closing and navigation
|
||
document.addEventListener('keydown', function(e) {
|
||
if (viewerOverlay.style.display === 'flex') {
|
||
if (e.key === 'Escape') {
|
||
closeImageViewer();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
<!-- JavaScript to Load Images -->
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const templateData = document.getElementById('template-data');
|
||
const hardCacheEnabled = templateData.getAttribute('data-hard-cache-enabled') === 'true';
|
||
|
||
console.log(!hardCacheEnabled)
|
||
if (!hardCacheEnabled) {
|
||
// Hard cache is disabled, so skip polling and load images directly
|
||
const images = document.querySelectorAll("img[data-id]");
|
||
images.forEach((img) => {
|
||
// Use the ProxyFull URL directly
|
||
img.src = img.dataset.proxyFull;
|
||
img.onload = function() {
|
||
img.classList.add('loaded');
|
||
};
|
||
img.onerror = function() {
|
||
console.error('Failed to load image:', img.dataset.proxyFull);
|
||
};
|
||
});
|
||
return;
|
||
}
|
||
|
||
let imageMap = {}; // Map of image IDs to img elements
|
||
let loadedImageIDs = new Set(); // Keep track of loaded image IDs
|
||
let pollingInterval = 2000; // Initial polling interval in milliseconds
|
||
let polling = false;
|
||
|
||
function initializeImages() {
|
||
const images = document.querySelectorAll("img[data-id]");
|
||
images.forEach((img) => {
|
||
const id = img.dataset.id;
|
||
if (!imageMap[id]) {
|
||
imageMap[id] = img;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Initialize with images present at page load
|
||
initializeImages();
|
||
|
||
// Set up MutationObserver to detect new images added to the DOM
|
||
const observer = new MutationObserver((mutationsList) => {
|
||
for (let mutation of mutationsList) {
|
||
if (mutation.type === 'childList') {
|
||
mutation.addedNodes.forEach((node) => {
|
||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||
if (node.matches && node.matches('img[data-id]')) {
|
||
const img = node;
|
||
const id = img.dataset.id;
|
||
if (!imageMap[id]) {
|
||
imageMap[id] = img;
|
||
console.log('New image added:', id);
|
||
if (!polling) {
|
||
checkImageStatus(); // Start polling if not already started
|
||
}
|
||
}
|
||
} else {
|
||
// Check for nested images within added nodes
|
||
const nestedImages = node.querySelectorAll && node.querySelectorAll('img[data-id]');
|
||
if (nestedImages && nestedImages.length > 0) {
|
||
nestedImages.forEach((img) => {
|
||
const id = img.dataset.id;
|
||
if (!imageMap[id]) {
|
||
imageMap[id] = img;
|
||
console.log('New nested image added:', id);
|
||
if (!polling) {
|
||
checkImageStatus(); // Start polling if not already started
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
// Start observing the document body for added nodes
|
||
observer.observe(document.body, { childList: true, subtree: true });
|
||
|
||
function checkImageStatus() {
|
||
polling = true;
|
||
const imageIDs = Object.keys(imageMap).filter(id => !loadedImageIDs.has(id));
|
||
if (imageIDs.length === 0) {
|
||
polling = false;
|
||
console.log('All images loaded.');
|
||
return;
|
||
}
|
||
|
||
console.log('Checking status for images:', imageIDs); // Debugging
|
||
|
||
fetch('/image_status?image_ids=' + imageIDs.join(','))
|
||
.then(response => response.json())
|
||
.then(statusMap => {
|
||
console.log('Status map:', statusMap); // Debugging
|
||
let imagesStillLoading = false;
|
||
|
||
for (const [id, url] of Object.entries(statusMap)) {
|
||
const img = imageMap[id];
|
||
if (url) {
|
||
// Append cache-busting query parameter
|
||
const cacheBustingUrl = url + '?t=' + new Date().getTime();
|
||
if (img.src !== cacheBustingUrl) {
|
||
img.src = cacheBustingUrl;
|
||
img.onload = function() {
|
||
// Image loaded successfully
|
||
img.classList.add('loaded');
|
||
loadedImageIDs.add(id);
|
||
};
|
||
img.onerror = function() {
|
||
console.error('Failed to load image:', url);
|
||
};
|
||
}
|
||
} else {
|
||
imagesStillLoading = true;
|
||
}
|
||
}
|
||
|
||
if (imagesStillLoading) {
|
||
// Poll again after a delay
|
||
setTimeout(checkImageStatus, pollingInterval);
|
||
} else {
|
||
polling = false;
|
||
console.log('All images loaded.');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error checking image status:', error);
|
||
// Retry after a delay in case of error
|
||
setTimeout(checkImageStatus, pollingInterval * 2);
|
||
});
|
||
}
|
||
|
||
// Start polling
|
||
checkImageStatus();
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
// Check if JavaScript is enabled and modify the DOM accordingly
|
||
document.getElementById('content').classList.remove('js-enabled');
|
||
</script>
|
||
</body>
|
||
</html> |