Merge remote-tracking branch 'origin/development'
This commit is contained in:
commit
1ee9c788d8
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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">✕</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">⇩</a>
|
<a id="gallery-download" aria-label="Download original image" title="Download original">⇩</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">></button>
|
<button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">▶</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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user