Search/templates/images.html

333 lines
20 KiB
HTML
Raw Normal View History

2024-08-13 16:38:02 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2024-08-28 21:31:27 +02:00
{{ if .IsThemeDark }}
<meta name="darkreader-lock">
{{ end }}
2024-10-09 21:03:53 +02:00
<title>{{ .Query }} - {{ translate "site_name" }}</title>
2024-08-13 16:38:02 +02:00
<link rel="stylesheet" href="/static/css/style.css">
2024-11-19 12:14:11 +01:00
<noscript>
<style>
img.placeholder-img {
display: none;
}
</style>
</noscript>
<link rel="stylesheet" href="/static/css/style-fixedwidth.css">
2024-08-13 16:38:02 +02:00
<link rel="stylesheet" href="/static/css/{{.Theme}}.css">
2024-10-28 10:52:39 +01:00
<link rel="search" type="application/opensearchdescription+xml" title="{{ translate "site_name" }}" href="/opensearch.xml">
<!-- Icons -->
<link rel="icon" href="{{ .IconPathSVG }}" type="image/svg+xml">
<link rel="icon" href="{{ .IconPathPNG }}" type="image/png">
<link rel="apple-touch-icon" href="{{ .IconPathPNG }}">
2024-08-13 16:38:02 +02:00
</head>
<body>
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
2024-10-22 12:15:09 +02:00
<h1 class="logomobile">
<div class="logo-container" herf="/">
<a href="/">
2024-11-26 08:07:35 +01:00
<svg xmlns="http://www.w3.org/2000/svg" width="29" height="86" viewBox="0 0 29 86"><path fill-rule="evenodd" d="M-44.35.78C-70.8 6.76-74.8 43.17-50.67 55.73c1.7.88 4.42 1.7 7.83 2.22 4.48.68 9.6.86 9.58.15-.04-1.43-7.3-8.28-8.67-8.28-3.15 0-9.94-5.66-11.97-10C-61.95 22.66-48.1 4.54-31.12 10c13.5 4.34 18.1 22.7 8.66 34.44-1.85 2.3-1.75 2.3-4.4-.22-4.8-4.59-8.57-5.25-11.98-2.1-2.18 2-2.15 2.66.15 4.14 1.9 1.22 13.4 12.95 17.49 17.83 4.3 5.13 5.24 6.14 7.52 7.97C-9.25 75.6-1.23 77.91 1 76.28c.67-.5 1.86-7.8 1.35-8.3-.12-.12-1.34-.4-2.7-.61-5.36-.86-9.23-3.46-14.2-9.55-3.49-4.27-4.12-5.26-3.38-5.26 2.54 0 8.05-8.62 9.86-15.43C-2.36 15.63-22.18-4.23-44.35.78m65.13 1.53C4.92 6.02-4.86 22.36-.72 38.24 3 52.62 18.43 59.63 33.67 57.64c4.7-.62 2.43-.66 4.45-.8s6.45-.01 6.93-.2c.4.03.72-.45.72-.94V42.31c0-7.36-.16-13.62-.33-13.9-.26-.4-2.36-.49-10.19-.4-11.44.15-10.96-.03-10.96 4.09 0 2.44-.04 3.99 1.17 4.7 1.13.68 3.43.59 6.68.41l3.76-.2.27 5.68c.33 6.59.57 6.15-3.64 6.7-15.53 2.04-24-5.02-23.37-19.43.66-15.1 12.2-22.78 26.96-17.94 4.5 1.47 4.4 1.52 6.16-2.8 1.5-3.68 1.5-3.69-.82-4.69C36.03 2.2 25.9 1.11 20.78 2.31m78.83.8c-2.87.76-2.9.84-3.15 6.12-.25 5.56.12 4.96-3.35 5.29-3.43.32-3.32.15-2.76 4.2.61 4.37.6 4.34 3.76 4.34h2.65v12.7c0 14.5 1.55 16.33 3.5 18.3 3.6 3.48 9.59 4.92 14.93 3.06 2.45-.85 2.43-.8 2.18-4.95-.25-4.1-.43-3.5-3.16-2.91-7.73 1.64-8.27.6-8.27-15.05V22.87h5.66l5.34-.1c.67-.01.97.4 1.28-3.9.35-4.8-.2-4.01-.8-4.14l-5.82.18-5.66.26v-5.16c0-5.84-.2-6.48-2.25-7.04-1.75-.49-1.76-.49-4.08.13m-34.5 11.02c-2.64.38-4.71 1.04-8.54 2.72l-4.03 1.76c-1.09.39-.28 1.29.69 3.89 1.06 2.75 1.35 3.35 2.11 3.03.76-.32.7-.23 1.43-.65 9.08-5.25 20.26-2.63 20.26 4.74v2.14l-5.95.2c-13.84.48-20.29 4.75-20.38 13.51-.13 12.4 14.18 17.22 24.62 8.3l2.3-1.97.23 1.85c.32 2.53.6 3.06 2.04 3.67 1.42.6 7.16.62 7.75.03.77-.77.37-6-.25-6.34-.94-.5-.77-1.57-.88-12.63-.15-14.87-.5-16.5-4.4-20.13-3.03-2.84-11.55-4.9-17-4.12m72.86 0c-27.2 5.27-24.13 43.96 3.47 43.9 14.67-.04 24.4-12.77 21.53-28.16-1.86-9.95-14.33-17.8-25-15.73m8.29 8.96c6.88 2.34 9.61 11.51 5.9 19.79-4.13 9.19-17.89 9.17-22.14-.03-1.32-2.85-1.24-10.79.14-13.54 3-6 9.45-8.49 16.1-6.22m-68.84 18.5v3.09l-1.85 1.63c-7.46 6.58-16.36 5.49-15.6-1.9.45-4.35 3.62-5.77 13.06-5.87l4.4-.05v3.1" style="fill:currentColor" transform="translate(-31.68 4.9)"/><path d="M-13.47 73.3v1.11q-.65-.3-1.23-.46-.57-.15-1.11-.15-.93 0-1.44.36-.5.36-.5 1.03 0 .56.33.85.34.28 1.28.46l.69.14q1.27.24 1.88.86.6.6.6 1.64 0 1.22-.82 1.86-.82.63-2.4.63-.6 0-1.28-.14-.68-.13-1.4-.4v-1.17q.7.39 1.36.58.67.2 1.31.2.98 0 1.51-.38.54-.39.54-1.1 0-.62-.39-.97-.38-.35-1.25-.53l-.7-.13q-1.27-.26-1.84-.8-.57-.54-.57-1.51 0-1.12.78-1.76.8-.65 2.18-.65.6 0 1.2.1.63.12 1.27.33zm2.29-.28h5.34V74h-4.2v2.5h4.02v.96h-4.02v3.05h4.3v.97h-5.44zm10.14 1.13-1.55 4.2H.5zm-.65-1.13h1.3l3.21 8.45H1.64L.87 79.3h-3.8l-.78 2.17h-1.2zm9.75 4.48q.37.13.71.54.35.4.7 1.12l1.16 2.3H9.41L8.33 79.3q-.42-.85-.82-1.13-.39-.27-1.07-.27H5.2v3.57H4.06v-8.45h2.58q1.44 0 2.16.6.7.61.7 1.84 0 .8-.36 1.32-.37.52-1.08.73zM5.2 73.97v3h1.44q.82 0 1.24-.38.42-.38.42-1.12 0-.75-.42-1.12-.42-.38-1.24-.38zm12.65-.3v1.2q-.58-.53-1.23-.8-.65-.26-1.39-.26-1.45 0-2.22.89-.77.88-.77 2.55t.77 2.56q.77.88 2.22.88.74 0 1.39-.26.65-.27 1.23-.8v1.19q-.6.4-1.27.6-.67.21-1.42.21-1.91 0-3.02-1.17-1.1-1.18-1.1-3.2 0-2.04 1.1-3.21 1.1-1.18 3.02-1.18.76 0 1.43.2.67.2 1.26.6zm1.76-.65h1.14v3.46h4.15v-3.46h1.15v8.45H24.9v-4.02h-4.15v4.02h-1.14zm12.39 0h5.34V74h-4.2v2.5h4.02v.96h-4.02v3.05h4.3v.97H32zm7.32 0h1.53l3.75 7.07v-7.07h1.1v8.45h-1.53l-3.74-7.07v7.07h-1.11zm14.42 7.24V78h-1.87v-.93h3v3.62q-.67.47-1.46.71-.8.24-1.7.24-1.98 0-3.1-1.15-1.12-1.16-1.12-3.23 0-2.07 1.12-3.22 1.12-1.16 3.1-1.16.82 0 1.56.2.75.2 1.38.6v1.22q-.64-.54-1.35-.8-.71-.28-1.5-.28-1.55 0-2.33.86-.77.87-.77 2.58t.77 2.58q.78.86 2.33.86.6 0 1.08-.1.48-.1.86-.33zm3.21-7.24h1.14v8.45h-1.14zm3.42 0h1.54l3.74 7.07v-7.07h1.1v8.45h-1.53l-3.74-7.07v7.07h-1.11zm8.66 0h5.34V74h-4.2v2.5h4.02v.96h-4.02v3.05h4.3v.97h-5.44z" aria-label="SEARCH ENGINE" style="font-family:'ADLaM Display';white-space:pre;fi
2024-10-22 12:15:09 +02:00
</a>
</div>
</h1>
2024-08-13 16:38:02 +02:00
<div class="wrapper-results">
<input type="text" name="q" value="{{ .Query }}" id="search-input"/>
2024-08-13 16:38:02 +02:00
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="image">search</button>
2024-08-21 23:23:08 +02:00
<div class="autocomplete">
<ul>
</ul>
</div>
2024-08-13 16:38:02 +02:00
<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>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</div>
</div>
<noscript>
<input type="hidden" name="js_enabled" value="true">
</noscript>
2024-08-13 16:38:02 +02:00
</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>
2024-08-13 16:38:02 +02:00
</select>
<select class="results-settings" name="lang" id="languageSelect">
{{range .LanguageOptions}}
2024-10-10 18:41:53 +02:00
<option value="{{.Code}}" {{if eq .Code $.CurrentLang}}selected{{end}}>{{.Name}}</option> <!-- this is too wide or too less, fix -->
2024-08-13 16:38:02 +02:00
{{end}}
</select>
<button class="results-save" name="t" value="image">{{ translate "save_settings" }}</button>
2024-08-13 16:38:02 +02:00
</form>
<div class="search-results" id="results">
2024-11-13 16:59:42 +01:00
<!-- Results go here -->
{{ if .Results }}
<div class="images images_viewer_hidden">
<!-- Images Grid -->
{{ range $index, $result := .Results }}
<div class="image">
{{ if $.HardCacheEnabled }}
<noscript>
2024-11-13 16:59:42 +01:00
<!-- JavaScript is disabled; serve actual images -->
<img src="{{ $result.ProxyFull }}" alt="{{ $result.Title }}" class="clickable" />
</noscript>
<!-- JavaScript is enabled; use placeholders -->
2024-11-19 12:14:11 +01:00
<img
src="/static/images/placeholder.svg"
data-id="{{ $result.ID }}"
data-full="{{ $result.ProxyFull }}"
data-proxy-full="{{ $result.ProxyThumb }}"
alt="{{ $result.Title }}"
class="clickable placeholder-img"
/>
2024-11-13 16:59:42 +01:00
{{ else }}
<!-- HardCacheEnabled is false; serve images directly -->
<img src="{{ $result.ProxyFull }}" alt="{{ $result.Title }}" class="clickable" />
{{ end }}
<div class="resolution">{{ $result.Width }} × {{ $result.Height }}</div>
<div class="details">
<span class="img_title clickable">{{ $result.Title }}</span>
2024-10-13 00:04:46 +02:00
</div>
2024-11-13 16:59:42 +01:00
</div>
{{ end }}
</div>
<!-- Nav buttons -->
<noscript>
<form action="/search" id="prev-next-form" class="results-search-container" method="GET" autocomplete="off">
<!-- Existing form fields -->
<input type="hidden" name="js_disabled" value="true">
</form>
<!-- Nav buttons -->
<div class="pagination">
{{ if .HasPrevPage }}
2024-11-19 12:14:11 +01:00
<a href="/search?q={{ .Query }}&t=image&p={{ sub .Page 1 }}">{{ translate "previous" }}</a>
2024-11-13 16:59:42 +01:00
{{ end }}
{{ if .HasNextPage }}
2024-11-19 12:14:11 +01:00
<a href="/search?q={{ .Query }}&t=image&p={{ add .Page 1 }}">{{ translate "next" }}</a>
2024-08-13 16:38:02 +02:00
{{ end }}
</div>
2024-11-13 16:59:42 +01:00
</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 }}
2024-08-13 16:38:02 +02:00
</div>
<div class="message-bottom-left" id="message-bottom-left">
<span>{{ translate "searching_for_new_results" }}</span>
2024-08-13 16:38:02 +02:00
</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>
2024-08-21 23:23:08 +02:00
<script defer src="/static/js/autocomplete.js"></script>
<script defer src="/static/js/imagetitletrim.js"></script>
2024-10-22 21:58:06 +02:00
<script defer src="/static/js/imageviewer.js"></script>
2024-11-13 16:59:42 +01:00
<!-- JavaScript to Load Images and Dynamic loading of the page -->
2024-10-13 00:04:46 +02:00
<script>
2024-11-13 16:59:42 +01:00
(function() {
// Configuration
const imageStatusInterval = 500; // Interval in milliseconds to check image status
2024-11-13 16:59:42 +01:00
const scrollThreshold = 500; // Distance from bottom of the page to trigger loading
let isFetching = false;
let page = parseInt(document.getElementById('template-data').getAttribute('data-page')) || 1;
let query = document.getElementById('template-data').getAttribute('data-query');
let hardCacheEnabled = document.getElementById('template-data').getAttribute('data-hard-cache-enabled') === 'true';
let noMoreImages = false; // Flag to indicate if there are no more images to load
2024-11-13 16:59:42 +01:00
let imageElements = [];
let imageIds = [];
/**
* Function to handle image load errors with retry logic
* @param {HTMLElement} imgElement - The image element that failed to load
* @param {number} retryCount - Number of retries left
* @param {number} retryDelay - Delay between retries in milliseconds
*/
function handleImageError(imgElement, retryCount = 3, retryDelay = 1000) {
if (retryCount > 0) {
setTimeout(() => {
imgElement.src = imgElement.getAttribute('data-full');
imgElement.onerror = function() {
handleImageError(imgElement, retryCount - 1, retryDelay);
};
}, retryDelay);
} else {
// After retries, hide the image container or set a fallback image
console.warn('Image failed to load after retries:', imgElement.getAttribute('data-full'));
imgElement.parentElement.style.display = 'none'; // Hide the image container
// Alternatively, set a fallback image:
// imgElement.src = '/static/images/fallback.svg';
}
}
/**
* Function to ensure the page is scrollable by loading more images if necessary
*/
function ensureScrollable() {
if (noMoreImages) return; // Do not attempt if no more images are available
// Check if the page is not scrollable
if (document.body.scrollHeight <= window.innerHeight) {
// If not scrollable, fetch the next page
fetchNextPage();
}
}
/**
* Function to fetch the next page of images
*/
function fetchNextPage() {
if (isFetching || noMoreImages) return;
isFetching = true;
page += 1;
fetch(`/search?q=${encodeURIComponent(query)}&t=image&p=${page}&ajax=true`)
.then(response => response.text())
.then(html => {
// Parse the returned HTML and extract image elements
let parser = new DOMParser();
let doc = parser.parseFromString(html, 'text/html');
let newImages = doc.querySelectorAll('.image');
if (newImages.length > 0) {
let resultsContainer = document.querySelector('.images');
newImages.forEach(imageDiv => {
// Append new images to the container
resultsContainer.appendChild(imageDiv);
// Get the img element
let img = imageDiv.querySelector('img');
if (img) {
if (hardCacheEnabled) {
// Replace image with placeholder
img.src = '/static/images/placeholder.svg';
img.onerror = function() {
handleImageError(img);
};
let id = img.getAttribute('data-id');
if (id) { // Only include if ID is not empty
imageElements.push(img);
imageIds.push(id);
}
}
}
});
if (hardCacheEnabled) {
checkImageStatus();
}
// After appending new images, ensure the page is scrollable
ensureScrollable();
} else {
// No more images to load
noMoreImages = true;
}
isFetching = false;
})
.catch(error => {
console.error('Error fetching next page:', error);
isFetching = false;
});
}
/**
* Function to check image status via AJAX
*/
2024-10-13 00:04:46 +02:00
function checkImageStatus() {
2024-11-13 16:59:42 +01:00
if (!hardCacheEnabled) return;
if (imageIds.length === 0) {
// No images to check, do nothing
2024-10-13 00:04:46 +02:00
return;
}
2024-11-13 16:59:42 +01:00
// Send AJAX request to check image status
fetch(`/image_status?image_ids=${imageIds.join(',')}`)
2024-10-13 00:04:46 +02:00
.then(response => response.json())
.then(statusMap => {
2024-11-13 16:59:42 +01:00
imageElements = imageElements.filter(img => {
let id = img.getAttribute('data-id');
if (statusMap[id]) {
// Image is ready, update src
img.src = statusMap[id];
img.onerror = function() {
handleImageError(img);
};
2024-11-13 16:59:42 +01:00
// Remove the image id from the list
imageIds = imageIds.filter(imageId => imageId !== id);
return false; // Remove img from imageElements
2024-10-13 00:04:46 +02:00
}
2024-11-13 16:59:42 +01:00
return true; // Keep img in imageElements
});
// After updating images, ensure the page is scrollable
ensureScrollable();
2024-10-13 00:04:46 +02:00
})
.catch(error => {
console.error('Error checking image status:', error);
});
}
2024-11-13 16:59:42 +01:00
// Initialize imageElements and imageIds
if (hardCacheEnabled) {
imageElements = Array.from(document.querySelectorAll('img[data-id]'));
imageIds = imageElements
.map(img => img.getAttribute('data-id'))
.filter(id => id); // Exclude empty IDs
2024-11-13 16:59:42 +01:00
// Replace images with placeholders
imageElements.forEach(img => {
img.src = '/static/images/placeholder.svg';
});
// Start checking image status
let imageStatusTimer = setInterval(checkImageStatus, imageStatusInterval);
checkImageStatus(); // Initial check
// After initial images are loaded, ensure the page is scrollable
window.addEventListener('load', ensureScrollable);
2024-11-13 16:59:42 +01:00
}
// Infinite scrolling
window.addEventListener('scroll', function() {
if (isFetching || noMoreImages) return;
2024-11-13 16:59:42 +01:00
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - scrollThreshold) {
// User scrolled near the bottom
fetchNextPage();
2024-11-13 16:59:42 +01:00
}
});
// Remove 'js-enabled' class from content
document.getElementById('content').classList.remove('js-enabled');
})();
2024-08-13 16:38:02 +02:00
</script>
</body>
</html>