|
|
@@ -35,6 +35,8 @@
|
|
|
initDropzone();
|
|
|
initCardDelegation();
|
|
|
initOverviewDelegation();
|
|
|
+ initLightbox();
|
|
|
+ initOverviewThumbClicks();
|
|
|
applyHeroImage(data.heroPreviewUrl);
|
|
|
|
|
|
// ==========================================
|
|
|
@@ -47,6 +49,7 @@
|
|
|
attachmentId: f.attachment_id,
|
|
|
fileName: f.file_name,
|
|
|
thumbUrl: f.thumb_url,
|
|
|
+ previewUrl: f.preview_url || f.thumb_url,
|
|
|
variationId: f.variation_id || 0,
|
|
|
uploading: false
|
|
|
});
|
|
|
@@ -69,13 +72,23 @@
|
|
|
// Thumb column
|
|
|
var $thumb = $('<div class="studiou-fpp-card-thumb"></div>');
|
|
|
if (opts.thumbUrl) {
|
|
|
- $thumb.append($('<img>').attr('src', opts.thumbUrl).attr('alt', opts.fileName || ''));
|
|
|
+ var $img = $('<img>').attr('src', opts.thumbUrl).attr('alt', opts.fileName || '');
|
|
|
+ if (opts.previewUrl) {
|
|
|
+ $img.attr('data-preview-url', opts.previewUrl);
|
|
|
+ }
|
|
|
+ $thumb.append($img);
|
|
|
} else {
|
|
|
$thumb.append('<div class="studiou-fpp-card-thumb-placeholder"></div>');
|
|
|
}
|
|
|
+ // Element-level click binding — survives even if something intercepts bubble-phase
|
|
|
+ // or capture-phase on document. Fires at target phase for clicks inside $thumb.
|
|
|
+ bindThumbClick($thumb[0]);
|
|
|
var $progress = $('<div class="studiou-fpp-card-progress"></div>');
|
|
|
$progress.append('<div class="studiou-fpp-card-progress-fill"></div>');
|
|
|
$progress.append('<span class="studiou-fpp-card-progress-text">0%</span>');
|
|
|
+ if (!opts.uploading) {
|
|
|
+ $progress.hide();
|
|
|
+ }
|
|
|
$thumb.append($progress);
|
|
|
$card.append($thumb);
|
|
|
|
|
|
@@ -91,6 +104,10 @@
|
|
|
var $priceRow = $('<div class="studiou-fpp-card-price-row"></div>');
|
|
|
$priceRow.append('<span class="studiou-fpp-card-price">—</span>');
|
|
|
$priceRow.append('<span class="studiou-fpp-card-badge" style="display:none;"></span>');
|
|
|
+ var $enlargeBtn = $('<button type="button" class="studiou-fpp-card-enlarge"></button>').text(i18n.enlarge || 'Enlarge');
|
|
|
+ // Direct target-phase binding — primitive, runs regardless of delegation state.
|
|
|
+ $enlargeBtn[0].onclick = onEnlargeClick;
|
|
|
+ $priceRow.append($enlargeBtn);
|
|
|
$priceRow.append('<button type="button" class="button studiou-fpp-card-addtocart">' +
|
|
|
escHtml(i18n.addToCart || 'Add to cart') + '</button>');
|
|
|
$body.append($priceRow);
|
|
|
@@ -365,12 +382,15 @@
|
|
|
$card.find('.studiou-fpp-card-progress').hide();
|
|
|
$card.attr('data-file-record-id', res.file_record_id);
|
|
|
$card.attr('data-attachment-id', res.attachment_id);
|
|
|
+ var previewUrl = res.preview_url || res.thumbnail_url;
|
|
|
var $img = $card.find('.studiou-fpp-card-thumb img');
|
|
|
if ($img.length) {
|
|
|
- $img.attr('src', res.thumbnail_url);
|
|
|
+ $img.attr('src', res.thumbnail_url).attr('data-preview-url', previewUrl);
|
|
|
} else {
|
|
|
$card.find('.studiou-fpp-card-thumb-placeholder').replaceWith(
|
|
|
- $('<img>').attr('src', res.thumbnail_url).attr('alt', res.file_name)
|
|
|
+ $('<img>').attr('src', res.thumbnail_url)
|
|
|
+ .attr('alt', res.file_name)
|
|
|
+ .attr('data-preview-url', previewUrl)
|
|
|
);
|
|
|
}
|
|
|
updateCardPrice($card);
|
|
|
@@ -378,7 +398,7 @@
|
|
|
|
|
|
// If this is now the first/only card, swap the hero gallery image
|
|
|
if ($cards.children('.studiou-fpp-card').index($card) === 0) {
|
|
|
- applyHeroImage(res.preview_url || res.thumbnail_url);
|
|
|
+ applyHeroImage(previewUrl);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -533,6 +553,7 @@
|
|
|
} else {
|
|
|
$wrap.after($new);
|
|
|
}
|
|
|
+ initOverviewThumbClicks();
|
|
|
$(document).trigger('studiou-fpp:cart-updated');
|
|
|
}
|
|
|
|
|
|
@@ -568,6 +589,172 @@
|
|
|
if ($a.length) $a.attr('href', url);
|
|
|
}
|
|
|
|
|
|
+ // ==========================================
|
|
|
+ // Lightbox (click thumbnail → enlarged preview)
|
|
|
+ // ==========================================
|
|
|
+ var $lightbox = null;
|
|
|
+
|
|
|
+ // Attach a target-phase click listener directly on a thumbnail element.
|
|
|
+ // Element-level listeners are the most reliable form of click binding — no
|
|
|
+ // delegation, no capture/bubble surprises. Called from buildCard + finishUpload
|
|
|
+ // (upload cards) and from initOverviewThumbClicks (after every overview render).
|
|
|
+ function bindThumbClick(el) {
|
|
|
+ if (!el || el.__studiouFppBound) return;
|
|
|
+ el.__studiouFppBound = true;
|
|
|
+ el.addEventListener('click', function (ev) {
|
|
|
+ var t = ev.target;
|
|
|
+ if (t && t.closest && t.closest('button, input, select, textarea, a')) return;
|
|
|
+
|
|
|
+ var card = el.closest ? el.closest('.studiou-fpp-card') : null;
|
|
|
+ if (card && card.classList.contains('studiou-fpp-card-uploading')) return;
|
|
|
+
|
|
|
+ var img = el.matches && el.matches('.studiou-fpp-overview-thumb')
|
|
|
+ ? el
|
|
|
+ : (el.querySelector ? el.querySelector('img') : null);
|
|
|
+ if (!img) return;
|
|
|
+
|
|
|
+ var url = img.getAttribute('data-preview-url') || img.getAttribute('src');
|
|
|
+ if (!url) return;
|
|
|
+
|
|
|
+ var name = '';
|
|
|
+ if (card) {
|
|
|
+ var nameEl = card.querySelector('.studiou-fpp-card-name');
|
|
|
+ name = nameEl ? nameEl.textContent : '';
|
|
|
+ } else {
|
|
|
+ name = img.getAttribute('data-file-name') || img.getAttribute('alt') || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ ev.preventDefault();
|
|
|
+ ev.stopPropagation();
|
|
|
+ openLightbox(url, name);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Explicit "Enlarge" button handler — survives any parent-level click interceptor
|
|
|
+ // because clicks on <button type="button"> don't match the thumb-image selectors that
|
|
|
+ // theme lightboxes (iLightBox/Magnific) delegate to.
|
|
|
+ function onEnlargeClick(ev) {
|
|
|
+ ev.preventDefault();
|
|
|
+ ev.stopPropagation();
|
|
|
+ var btn = this;
|
|
|
+ var card = btn.closest('.studiou-fpp-card');
|
|
|
+ var line = btn.closest('.studiou-fpp-overview-line');
|
|
|
+ var img, name;
|
|
|
+ if (card) {
|
|
|
+ img = card.querySelector('.studiou-fpp-card-thumb img');
|
|
|
+ var nameEl = card.querySelector('.studiou-fpp-card-name');
|
|
|
+ name = nameEl ? nameEl.textContent : '';
|
|
|
+ } else if (line) {
|
|
|
+ img = line.querySelector('.studiou-fpp-overview-thumb');
|
|
|
+ name = img ? (img.getAttribute('data-file-name') || img.getAttribute('alt') || '') : '';
|
|
|
+ }
|
|
|
+ if (!img) return;
|
|
|
+ var url = img.getAttribute('data-preview-url') || img.getAttribute('src');
|
|
|
+ if (!url) return;
|
|
|
+ openLightbox(url, name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // (Re)bind all overview thumbs — called after initial render and after updateOverview.
|
|
|
+ function initOverviewThumbClicks() {
|
|
|
+ var nodes = document.querySelectorAll('.studiou-fpp-overview-thumb');
|
|
|
+ for (var i = 0; i < nodes.length; i++) {
|
|
|
+ bindThumbClick(nodes[i]);
|
|
|
+ }
|
|
|
+ var btns = document.querySelectorAll('.studiou-fpp-overview-enlarge');
|
|
|
+ for (var j = 0; j < btns.length; j++) {
|
|
|
+ btns[j].onclick = onEnlargeClick;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function initLightbox() {
|
|
|
+ $lightbox = $(
|
|
|
+ '<div class="studiou-fpp-lightbox" role="dialog" aria-modal="true" style="display:none;">' +
|
|
|
+ '<button type="button" class="studiou-fpp-lightbox-close" aria-label="' + escAttr(i18n.close || 'Close') + '">×</button>' +
|
|
|
+ '<div class="studiou-fpp-lightbox-inner">' +
|
|
|
+ '<img class="studiou-fpp-lightbox-img" alt="" />' +
|
|
|
+ '<div class="studiou-fpp-lightbox-caption"></div>' +
|
|
|
+ '</div>' +
|
|
|
+ '</div>'
|
|
|
+ );
|
|
|
+ $('body').append($lightbox);
|
|
|
+
|
|
|
+ // Enlarge button — primary, reliable opener. Delegated on document so newly
|
|
|
+ // rendered cards and overview lines pick it up automatically.
|
|
|
+ $(document).on('click', '.studiou-fpp-card-enlarge, .studiou-fpp-overview-enlarge', function (e) {
|
|
|
+ onEnlargeClick.call(this, e);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Use native capture-phase listener so theme/plugin handlers that stopPropagation
|
|
|
+ // on <img> clicks (common in gallery-lightbox plugins) can't swallow us. Target the
|
|
|
+ // .studiou-fpp-card-thumb wrapper, not just the <img>, so any click inside the
|
|
|
+ // thumbnail — including padding around the image — opens the preview.
|
|
|
+ document.addEventListener('click', function (ev) {
|
|
|
+ var target = ev.target;
|
|
|
+ if (!target || !target.closest) return;
|
|
|
+
|
|
|
+ // Ignore clicks on interactive controls (remove button, progress text, etc.)
|
|
|
+ if (target.closest('button, input, select, textarea, a')) return;
|
|
|
+
|
|
|
+ var cardThumb = target.closest('.studiou-fpp-card-thumb');
|
|
|
+ if (cardThumb) {
|
|
|
+ var card = cardThumb.closest('.studiou-fpp-card');
|
|
|
+ if (!card || card.classList.contains('studiou-fpp-card-uploading')) return;
|
|
|
+ var img = cardThumb.querySelector('img');
|
|
|
+ if (!img) return;
|
|
|
+ ev.preventDefault();
|
|
|
+ ev.stopPropagation();
|
|
|
+ var nameEl = card.querySelector('.studiou-fpp-card-name');
|
|
|
+ openLightbox(
|
|
|
+ img.getAttribute('data-preview-url') || img.getAttribute('src'),
|
|
|
+ nameEl ? nameEl.textContent : ''
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var overviewImg = target.closest('.studiou-fpp-overview-thumb');
|
|
|
+ if (overviewImg) {
|
|
|
+ ev.preventDefault();
|
|
|
+ ev.stopPropagation();
|
|
|
+ openLightbox(
|
|
|
+ overviewImg.getAttribute('data-preview-url') || overviewImg.getAttribute('src'),
|
|
|
+ overviewImg.getAttribute('data-file-name') || overviewImg.getAttribute('alt') || ''
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ // Close: × button, backdrop click, ESC key
|
|
|
+ $lightbox.on('click', '.studiou-fpp-lightbox-close', closeLightbox);
|
|
|
+ $lightbox.on('click', function (e) {
|
|
|
+ if (e.target === this) closeLightbox();
|
|
|
+ });
|
|
|
+ $(document).on('keydown.studiouFppLightbox', function (e) {
|
|
|
+ if (e.key === 'Escape' && $lightbox && $lightbox.is(':visible')) {
|
|
|
+ closeLightbox();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function openLightbox(url, caption) {
|
|
|
+ if (!url) return;
|
|
|
+ // Fallback: if the overlay DOM somehow failed to initialize, at least open the
|
|
|
+ // preview in a new tab so the button is never a no-op.
|
|
|
+ if (!$lightbox || !$lightbox.length) {
|
|
|
+ window.open(url, '_blank', 'noopener');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $lightbox.find('.studiou-fpp-lightbox-img').attr('src', url).attr('alt', caption || '');
|
|
|
+ $lightbox.find('.studiou-fpp-lightbox-caption').text(caption || '');
|
|
|
+ $lightbox.css('display', 'flex');
|
|
|
+ $('body').addClass('studiou-fpp-lightbox-open');
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeLightbox() {
|
|
|
+ if (!$lightbox) return;
|
|
|
+ $lightbox.hide();
|
|
|
+ $lightbox.find('.studiou-fpp-lightbox-img').attr('src', '');
|
|
|
+ $('body').removeClass('studiou-fpp-lightbox-open');
|
|
|
+ }
|
|
|
+
|
|
|
function restoreHeroImage() {
|
|
|
if (!originalGallery.src) return;
|
|
|
var selectors = [
|