Merge remote-tracking branch 'origin/development'

This commit is contained in:
lelo 2026-02-27 11:42:38 +00:00
commit 1ee9c788d8
3 changed files with 75 additions and 13 deletions

View File

@ -39,21 +39,30 @@
cursor: pointer; cursor: pointer;
} }
/* Close Button positioning */ /* Close Button */
#gallery-close { #gallery-close {
position: fixed; position: fixed;
top: 20px; top: 20px;
right: 20px; right: 20px;
font-size: 24px; width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(0, 0, 0, 0.6);
color: #fff; color: #fff;
display: grid;
place-items: center;
cursor: pointer; cursor: pointer;
z-index: 100; z-index: 120;
background: transparent; font-size: 1.1rem;
border: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
#gallery-close:hover {
background: rgba(40, 40, 40, 0.85);
}
/* Loader for gallery */ /* Loader for gallery */
#loader-container { #loader-container {
position: fixed; position: fixed;
@ -94,11 +103,14 @@
padding: 1rem; padding: 1rem;
cursor: pointer; cursor: pointer;
z-index: 10; z-index: 10;
transition: opacity 0.4s ease;
} }
.gallery-prev { left: 20px; } .gallery-prev { left: 20px; }
.gallery-next { right: 20px; } .gallery-next { right: 20px; }
.gallery-nav.nav-hidden { opacity: 0; }
#gallery-info { #gallery-info {
position: fixed; position: fixed;
top: 20px; top: 20px;
@ -225,6 +237,8 @@
cursor: pointer; cursor: pointer;
z-index: 120; z-index: 120;
font-size: 1.2rem; font-size: 1.2rem;
line-height: 1;
padding: 0 0 0 2px; /* optical compensation for ▶ glyph whitespace */
--autoplay-duration: 5s; --autoplay-duration: 5s;
--play-progress: 0deg; --play-progress: 0deg;
overflow: visible; overflow: visible;

View File

@ -7,6 +7,7 @@ let exifAbortController = null;
let isGalleryLoading = false; let isGalleryLoading = false;
let autoPlayTimer = null; let autoPlayTimer = null;
let isAutoPlay = false; let isAutoPlay = false;
let navHideTimer = null;
const AUTO_PLAY_INTERVAL_MS = 5000; const AUTO_PLAY_INTERVAL_MS = 5000;
let pendingAutoAdvance = false; let pendingAutoAdvance = false;
@ -197,12 +198,13 @@ function updateDownloadLink(relUrl) {
if (!relUrl) { if (!relUrl) {
downloadBtn.removeAttribute('href'); downloadBtn.removeAttribute('href');
downloadBtn.removeAttribute('download'); downloadBtn.removeAttribute('download');
downloadBtn.removeAttribute('data-relurl');
return; return;
} }
const parts = relUrl.split('/'); // Store relUrl for the click handler (which fetches a short-lived token)
const filename = parts[parts.length - 1] || relUrl; downloadBtn.dataset.relurl = relUrl;
downloadBtn.href = '/media/' + encodeGalleryPath(relUrl) + '?download=true'; downloadBtn.removeAttribute('href');
downloadBtn.setAttribute('download', filename); downloadBtn.removeAttribute('download');
} }
function updateNextButtonState() { function updateNextButtonState() {
@ -216,11 +218,20 @@ function updateNextButtonState() {
nextButton.setAttribute('aria-label', atLastImage ? 'Close gallery' : 'Next image'); nextButton.setAttribute('aria-label', atLastImage ? 'Close gallery' : 'Next image');
} }
function showGalleryNav() {
clearTimeout(navHideTimer);
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
navHideTimer = setTimeout(() => {
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.add('nav-hidden'));
}, 1000);
}
function openGalleryModal(relUrl) { function openGalleryModal(relUrl) {
document.body.style.overflow = 'hidden'; // Disable background scrolling. document.body.style.overflow = 'hidden'; // Disable background scrolling.
currentGalleryIndex = currentGalleryImages.indexOf(relUrl); currentGalleryIndex = currentGalleryImages.indexOf(relUrl);
showGalleryImage(relUrl); showGalleryImage(relUrl);
document.getElementById('gallery-modal').style.display = 'flex'; document.getElementById('gallery-modal').style.display = 'flex';
showGalleryNav();
} }
function showGalleryImage(relUrl) { function showGalleryImage(relUrl) {
@ -334,6 +345,8 @@ function preloadNextImage() {
} }
function closeGalleryModal() { function closeGalleryModal() {
clearTimeout(navHideTimer);
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
document.getElementById('gallery-modal').style.display = 'none'; document.getElementById('gallery-modal').style.display = 'none';
isGalleryLoading = false; isGalleryLoading = false;
setAutoPlay(false); setAutoPlay(false);
@ -388,7 +401,10 @@ function initGallerySwipe() {
let touchEndX = 0; let touchEndX = 0;
const galleryModal = document.getElementById('gallery-modal'); const galleryModal = document.getElementById('gallery-modal');
galleryModal.addEventListener('mousemove', showGalleryNav, false);
galleryModal.addEventListener('touchstart', function(e) { galleryModal.addEventListener('touchstart', function(e) {
showGalleryNav();
// If more than one finger is on screen, ignore swipe tracking. // If more than one finger is on screen, ignore swipe tracking.
if (e.touches.length > 1) { if (e.touches.length > 1) {
return; return;
@ -434,7 +450,7 @@ function setAutoPlay(active) {
const playButton = document.getElementById('gallery-play'); const playButton = document.getElementById('gallery-play');
isAutoPlay = active; isAutoPlay = active;
if (playButton) { if (playButton) {
playButton.textContent = active ? '||' : '>'; playButton.textContent = active ? '\u23F8' : '\u25B6';
playButton.setAttribute('aria-pressed', active ? 'true' : 'false'); playButton.setAttribute('aria-pressed', active ? 'true' : 'false');
playButton.classList.toggle('playing', active); playButton.classList.toggle('playing', active);
} }
@ -598,8 +614,40 @@ function initGalleryControls() {
const downloadButton = document.getElementById('gallery-download'); const downloadButton = document.getElementById('gallery-download');
if (downloadButton) { if (downloadButton) {
downloadButton.addEventListener('click', function (e) { downloadButton.addEventListener('click', async function (e) {
e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const relUrl = downloadButton.dataset.relurl;
if (!relUrl) return;
const encodedSubpath = relUrl
.replace(/^\/+/, '')
.split('/')
.map(segment => encodeURIComponent(decodeURIComponent(segment)))
.join('/');
// Fetch a short-lived download token (works in Telegram's internal browser)
let tokenizedUrl;
try {
const resp = await fetch(`/create_dltoken/${encodedSubpath}`);
if (!resp.ok) return;
tokenizedUrl = (await resp.text()).trim();
} catch {
return;
}
if (!tokenizedUrl) return;
// Append download=true so the server returns the full-size original as attachment
const sep = tokenizedUrl.includes('?') ? '&' : '?';
const downloadUrl = new URL(tokenizedUrl + sep + 'download=true', window.location.href);
const a = document.createElement('a');
a.href = downloadUrl.toString();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}); });
} }

View File

@ -204,13 +204,13 @@
<!-- Gallery Modal for Images --> <!-- Gallery Modal for Images -->
<div id="gallery-modal" style="display: none;"> <div id="gallery-modal" style="display: none;">
<button id="gallery-close">x</button> <button id="gallery-close">&#x2715;</button>
<button id="gallery-info" aria-expanded="false" aria-controls="gallery-exif">i</button> <button id="gallery-info" aria-expanded="false" aria-controls="gallery-exif">i</button>
<a id="gallery-download" aria-label="Download original image" title="Download original">&#8681;</a> <a id="gallery-download" aria-label="Download original image" title="Download original">&#8681;</a>
<img id="gallery-modal-content" src=""> <img id="gallery-modal-content" src="">
<button class="gallery-nav gallery-prev"></button> <button class="gallery-nav gallery-prev"></button>
<button class="gallery-nav gallery-next"></button> <button class="gallery-nav gallery-next"></button>
<button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">&gt;</button> <button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">&#9654;</button>
<div id="gallery-exif" aria-live="polite"></div> <div id="gallery-exif" aria-live="polite"></div>
<div id="gallery-filename" aria-live="polite"></div> <div id="gallery-filename" aria-live="polite"></div>
</div> </div>