// attach.js is the attachment lightbox — the one enhancement the
// attachment section has, and the only thing on the page that needs
// script at all.
//
// Without it every image is a plain link to its inline view, which
// opens the picture. With it the picture opens in place, with its name
// and size, and the arrow keys walk the page's other images. That is
// the whole contract: nothing here is load-bearing.
(function () {
'use strict';
// Every image on the page that an attachment route serves: the cards'
// previews and the inline images a body resolved from attach:. They
// are collected in document order, which is the order prev/next walk.
function shots() {
var out = [];
document.querySelectorAll('.attach-card[data-kind="image"] .attach-prev').forEach(function (a) {
var img = a.querySelector('img');
out.push({
src: a.getAttribute('href'),
name: a.getAttribute('data-attach-name') || (img && img.alt) || '',
sub: a.getAttribute('data-attach-sub') || '',
dl: a.getAttribute('data-attach-dl') || a.getAttribute('href'),
from: a
});
});
document.querySelectorAll('article img, .tkt-body img, .f-post-b img').forEach(function (img) {
var src = img.getAttribute('src') || '';
if (!/\/attachment\/[^/]+\/view$/.test(src)) return;
out.push({
src: src,
name: img.getAttribute('alt') || '',
sub: '',
dl: src.replace(/\/view$/, ''),
from: img
});
});
return out;
}
var all = shots();
if (!all.length) return;
var dlg = document.createElement('dialog');
dlg.className = 'attach-lightbox';
dlg.innerHTML =
'<figure><img alt=""></figure>' +
'<div class="attach-lb-bar">' +
'<button type="button" class="attach-lb-prev" aria-label="previous image">‹</button>' +
'<span class="attach-lb-meta"><span class="attach-lb-name"></span> <span class="attach-lb-sub"></span></span>' +
'<a class="attach-lb-dl" download>Download</a>' +
'<button type="button" class="attach-lb-next" aria-label="next image">›</button>' +
'<button type="button" class="attach-lb-close" aria-label="close">×</button>' +
'</div>';
document.body.appendChild(dlg);
var img = dlg.querySelector('img');
var nameEl = dlg.querySelector('.attach-lb-name');
var subEl = dlg.querySelector('.attach-lb-sub');
var dlEl = dlg.querySelector('.attach-lb-dl');
var at = 0;
var opener = null;
function show(i) {
if (i < 0) i = all.length - 1;
if (i >= all.length) i = 0;
at = i;
var s = all[at];
img.setAttribute('src', s.src);
img.setAttribute('alt', s.name);
nameEl.textContent = s.name;
subEl.textContent = s.sub;
dlEl.setAttribute('href', s.dl);
// One image is not a carousel; hide the arrows rather than offering
// two controls that do nothing.
var many = all.length > 1;
dlg.querySelector('.attach-lb-prev').hidden = !many;
dlg.querySelector('.attach-lb-next').hidden = !many;
}
function open(i, from) {
opener = from || null;
show(i);
if (typeof dlg.showModal === 'function') dlg.showModal();
else dlg.setAttribute('open', '');
dlg.querySelector('.attach-lb-close').focus();
}
// Focus goes back where it came from. A reader who opened the fourth
// card with the keyboard lands on the fourth card again, not at the
// top of the document.
dlg.addEventListener('close', function () {
if (opener && typeof opener.focus === 'function') opener.focus();
opener = null;
});
all.forEach(function (s, i) {
var target = s.from.tagName === 'IMG' && s.from.parentElement &&
s.from.parentElement.tagName === 'A' ? s.from.parentElement : s.from;
target.addEventListener('click', function (e) {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
open(i, target);
});
});
dlg.querySelector('.attach-lb-prev').addEventListener('click', function () { show(at - 1); });
dlg.querySelector('.attach-lb-next').addEventListener('click', function () { show(at + 1); });
dlg.querySelector('.attach-lb-close').addEventListener('click', function () { dlg.close(); });
// The backdrop is the dialog itself: a click that lands on the
// element rather than on its contents is a click outside the picture.
dlg.addEventListener('click', function (e) { if (e.target === dlg) dlg.close(); });
dlg.addEventListener('keydown', function (e) {
if (e.key === 'ArrowLeft') { e.preventDefault(); show(at - 1); }
else if (e.key === 'ArrowRight') { e.preventDefault(); show(at + 1); }
});
})();