Add lightbox for image gallery
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
This commit is contained in:
185
script.js
185
script.js
@@ -43,6 +43,171 @@
|
||||
}
|
||||
}
|
||||
|
||||
function ensureLightbox() {
|
||||
var existing = document.querySelector('.luxtools-lightbox');
|
||||
if (existing) return existing;
|
||||
|
||||
var root = document.createElement('div');
|
||||
root.className = 'luxtools-lightbox';
|
||||
root.setAttribute('role', 'dialog');
|
||||
root.setAttribute('aria-modal', 'true');
|
||||
root.setAttribute('aria-hidden', 'true');
|
||||
|
||||
root.innerHTML =
|
||||
'<div class="luxtools-lightbox-backdrop" data-luxtools-action="close"></div>' +
|
||||
'<div class="luxtools-lightbox-stage">' +
|
||||
'<button type="button" class="luxtools-lightbox-btn luxtools-lightbox-close" data-luxtools-action="close" aria-label="Close">×</button>' +
|
||||
'<button type="button" class="luxtools-lightbox-btn luxtools-lightbox-prev" data-luxtools-action="prev" aria-label="Previous">‹</button>' +
|
||||
'<img class="luxtools-lightbox-img" alt="" />' +
|
||||
'<button type="button" class="luxtools-lightbox-btn luxtools-lightbox-next" data-luxtools-action="next" aria-label="Next">›</button>' +
|
||||
'<button type="button" class="luxtools-lightbox-btn luxtools-lightbox-opennew" data-luxtools-action="newtab" aria-label="Open in new tab">↗</button>' +
|
||||
'<div class="luxtools-lightbox-caption"></div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
function getGalleryItems(galleryEl) {
|
||||
var links = galleryEl.querySelectorAll('a.luxtools-gallery-item[data-luxtools-full]');
|
||||
var items = [];
|
||||
links.forEach(function (a) {
|
||||
var full = a.getAttribute('data-luxtools-full') || a.getAttribute('href') || '';
|
||||
var name = a.getAttribute('data-luxtools-name') || a.getAttribute('title') || '';
|
||||
if (!full) return;
|
||||
items.push({ el: a, full: full, name: name });
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function openLightboxFor(galleryEl, startEl) {
|
||||
var items = getGalleryItems(galleryEl);
|
||||
if (!items.length) return;
|
||||
|
||||
var index = 0;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i].el === startEl) { index = i; break; }
|
||||
}
|
||||
|
||||
var lb = ensureLightbox();
|
||||
var img = lb.querySelector('img.luxtools-lightbox-img');
|
||||
var cap = lb.querySelector('.luxtools-lightbox-caption');
|
||||
|
||||
var pushedHistory = false;
|
||||
var closingFromPopstate = false;
|
||||
|
||||
function clamp(n) {
|
||||
if (n < 0) return items.length - 1;
|
||||
if (n >= items.length) return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var it = items[index];
|
||||
img.src = it.full;
|
||||
img.setAttribute('data-luxtools-index', String(index));
|
||||
cap.textContent = (it.name || '').trim();
|
||||
}
|
||||
|
||||
function openInNewTab() {
|
||||
var it = items[index];
|
||||
if (!it || !it.full) return;
|
||||
try {
|
||||
window.open(it.full, '_blank', 'noopener');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
lb.classList.remove('is-open');
|
||||
lb.setAttribute('aria-hidden', 'true');
|
||||
try { document.documentElement.classList.remove('luxtools-noscroll'); } catch (e) {}
|
||||
try { document.body.style.overflow = ''; } catch (e) {}
|
||||
// Clear src to stop downloads on close.
|
||||
img.src = '';
|
||||
document.removeEventListener('keydown', onKeyDown, true);
|
||||
window.removeEventListener('popstate', onPopState, true);
|
||||
|
||||
// If we opened by pushing a state, pop it when closing via UI.
|
||||
if (pushedHistory && !closingFromPopstate) {
|
||||
try {
|
||||
if (window.history && window.history.state && window.history.state.luxtoolsLightbox === 1) {
|
||||
window.history.back();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function next() {
|
||||
index = clamp(index + 1);
|
||||
render();
|
||||
}
|
||||
|
||||
function prev() {
|
||||
index = clamp(index - 1);
|
||||
render();
|
||||
}
|
||||
|
||||
function onKeyDown(e) {
|
||||
if (!lb.classList.contains('is-open')) return;
|
||||
var key = e.key || '';
|
||||
if (key === 'Escape') {
|
||||
e.preventDefault();
|
||||
close();
|
||||
} else if (key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
next();
|
||||
} else if (key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
prev();
|
||||
}
|
||||
}
|
||||
|
||||
function onPopState() {
|
||||
if (!lb.classList.contains('is-open')) return;
|
||||
closingFromPopstate = true;
|
||||
try { close(); } finally { closingFromPopstate = false; }
|
||||
}
|
||||
|
||||
lb.onclick = function (e) {
|
||||
var t = e.target;
|
||||
if (!t || !t.getAttribute) return;
|
||||
var action = t.getAttribute('data-luxtools-action') || '';
|
||||
if (action === 'close') { e.preventDefault(); close(); }
|
||||
if (action === 'next') { e.preventDefault(); next(); }
|
||||
if (action === 'prev') { e.preventDefault(); prev(); }
|
||||
if (action === 'newtab') { e.preventDefault(); openInNewTab(); }
|
||||
|
||||
// Click outside the image closes (but don't interfere with controls).
|
||||
if (t.closest && t.closest('button.luxtools-lightbox-btn')) return;
|
||||
if (t.closest && t.closest('img.luxtools-lightbox-img')) return;
|
||||
e.preventDefault();
|
||||
close();
|
||||
};
|
||||
|
||||
// Open
|
||||
lb.classList.add('is-open');
|
||||
lb.setAttribute('aria-hidden', 'false');
|
||||
try { document.documentElement.classList.add('luxtools-noscroll'); } catch (e) {}
|
||||
try { document.body.style.overflow = 'hidden'; } catch (e) {}
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
window.addEventListener('popstate', onPopState, true);
|
||||
|
||||
// Allow closing via browser back button.
|
||||
try {
|
||||
if (window.history && window.history.pushState) {
|
||||
window.history.pushState({ luxtoolsLightbox: 1 }, '', window.location.href);
|
||||
pushedHistory = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
function getServiceUrl(el) {
|
||||
var url = el.getAttribute('data-service-url') || '';
|
||||
url = (url || '').trim();
|
||||
@@ -128,7 +293,27 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function findGalleryItem(target) {
|
||||
var el = target;
|
||||
while (el && el !== document) {
|
||||
if (el.classList && el.classList.contains('luxtools-gallery-item')) return el;
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function onClick(event) {
|
||||
// Image gallery lightbox: intercept clicks so we don't navigate away.
|
||||
var galleryItem = findGalleryItem(event.target);
|
||||
if (galleryItem) {
|
||||
var gallery = galleryItem.closest ? galleryItem.closest('div.luxtools-gallery[data-luxtools-gallery="1"]') : null;
|
||||
if (gallery) {
|
||||
event.preventDefault();
|
||||
openLightboxFor(gallery, galleryItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var el = findOpenElement(event.target);
|
||||
if (!el) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user