feat: Lightbox image viewer on batch page with arrow key navigation
Clicking an image thumbnail opens a full-size overlay instead of navigating away. Left/right arrows and on-screen buttons cycle through images. Click backdrop or press Escape to close. Includes filename and download link in the lightbox footer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ff62bbf5a9
commit
92c931d7da
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Required</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Count}} files — upload.jeffemmett.com</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container batch-container">
|
||||
|
|
@ -19,10 +19,13 @@
|
|||
<div class="batch-grid">
|
||||
{{range .Files}}
|
||||
<div class="batch-card">
|
||||
<a href="/f/{{.ID}}" class="batch-card-preview">
|
||||
{{if eq .ThumbType "image"}}
|
||||
{{if eq .ThumbType "image"}}
|
||||
<a href="{{.PreviewURL}}" class="batch-card-preview" data-lightbox data-filename="{{.Filename}}" data-dl="/f/{{.ID}}/dl">
|
||||
<img src="{{.PreviewURL}}" alt="{{.Filename}}" loading="lazy">
|
||||
{{else if eq .ThumbType "video"}}
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/f/{{.ID}}" class="batch-card-preview">
|
||||
{{if eq .ThumbType "video"}}
|
||||
<video src="{{.PreviewURL}}" preload="metadata" muted playsinline></video>
|
||||
{{else if eq .ThumbType "audio"}}
|
||||
<div class="batch-card-icon">
|
||||
|
|
@ -49,6 +52,7 @@
|
|||
</div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
<div class="batch-card-footer">
|
||||
<a href="/f/{{.ID}}" class="batch-card-name" title="{{.Filename}}">{{.Filename}}</a>
|
||||
<span class="batch-card-size">{{formatSize .Size}}</span>
|
||||
|
|
@ -61,5 +65,78 @@
|
|||
<a href="/">upload.jeffemmett.com</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Lightbox -->
|
||||
<div id="lightbox" class="lightbox hidden">
|
||||
<div class="lightbox-backdrop"></div>
|
||||
<button class="lightbox-close" title="Close">×</button>
|
||||
<button class="lightbox-prev" title="Previous">‹</button>
|
||||
<button class="lightbox-next" title="Next">›</button>
|
||||
<div class="lightbox-content">
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
</div>
|
||||
<div class="lightbox-footer">
|
||||
<span id="lightbox-filename"></span>
|
||||
<a id="lightbox-dl" href="" class="lightbox-dl-btn">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
const lbImg = document.getElementById('lightbox-img');
|
||||
const lbName = document.getElementById('lightbox-filename');
|
||||
const lbDl = document.getElementById('lightbox-dl');
|
||||
const items = [...document.querySelectorAll('[data-lightbox]')];
|
||||
let current = -1;
|
||||
|
||||
if (items.length === 0) return;
|
||||
|
||||
function open(index) {
|
||||
const el = items[index];
|
||||
current = index;
|
||||
lbImg.src = el.href;
|
||||
lbName.textContent = el.dataset.filename;
|
||||
lbDl.href = el.dataset.dl;
|
||||
lightbox.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function close() {
|
||||
lightbox.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
current = -1;
|
||||
}
|
||||
|
||||
function prev() {
|
||||
if (current <= 0) return;
|
||||
open(current - 1);
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (current >= items.length - 1) return;
|
||||
open(current + 1);
|
||||
}
|
||||
|
||||
items.forEach((el, i) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
open(i);
|
||||
});
|
||||
});
|
||||
|
||||
lightbox.querySelector('.lightbox-backdrop').addEventListener('click', close);
|
||||
lightbox.querySelector('.lightbox-close').addEventListener('click', close);
|
||||
lightbox.querySelector('.lightbox-prev').addEventListener('click', prev);
|
||||
lightbox.querySelector('.lightbox-next').addEventListener('click', next);
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (current === -1) return;
|
||||
if (e.key === 'Escape') close();
|
||||
else if (e.key === 'ArrowLeft') prev();
|
||||
else if (e.key === 'ArrowRight') next();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Filename}} — upload.jeffemmett.com</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>upload.jeffemmett.com</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Required</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="/static/style.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
|||
|
|
@ -452,6 +452,109 @@ footer a:hover { color: var(--accent); }
|
|||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* Lightbox */
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lightbox-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.lightbox-content {
|
||||
position: relative;
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lightbox-content img {
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.lightbox-close,
|
||||
.lightbox-prev,
|
||||
.lightbox-next {
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.lightbox-close:hover,
|
||||
.lightbox-prev:hover,
|
||||
.lightbox-next:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lightbox-close {
|
||||
top: 1rem;
|
||||
right: 1.25rem;
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lightbox-prev,
|
||||
.lightbox-next {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lightbox-prev {
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
.lightbox-next {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.lightbox-footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8125rem;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lightbox-dl-btn {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.lightbox-dl-btn:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Download page */
|
||||
.file-card {
|
||||
margin-top: 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue