| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- (function ($) {
- 'use strict';
- $(document).ready(function () {
- var $wrap = $('.studiou-fpp-upload-wrap');
- if (!$wrap.length) {
- return;
- }
- // ==========================================
- // Config + data
- // ==========================================
- var cfg = {
- productId: parseInt($wrap.data('product-id'), 10) || 0,
- maxFileSize: parseInt($wrap.data('max-file-size'), 10) || 50,
- maxUploads: parseInt($wrap.data('max-uploads'), 10) || 0,
- chunkSize: parseInt(studiouWcfppFront.chunkSize, 10) || 1048576
- };
- var i18n = studiouWcfppFront.i18n || {};
- var data = window.studiouFppCardData || {variants: [], batchFiles: []};
- var variants = data.variants || [];
- var $dropzone = $('#studiou-fpp-dropzone');
- var $fileInput = $('#studiou-fpp-file-input');
- var $cards = $('#studiou-fpp-cards');
- var $messages = $wrap.find('.studiou-fpp-messages');
- var originalGallery = {};
- // ==========================================
- // Bootstrap
- // ==========================================
- initExistingCards();
- initDropzone();
- initCardDelegation();
- initOverviewDelegation();
- initLightbox();
- initOverviewThumbClicks();
- applyHeroImage(data.heroPreviewUrl);
- // ==========================================
- // Card construction
- // ==========================================
- function initExistingCards() {
- (data.batchFiles || []).forEach(function (f) {
- var $card = buildCard({
- fileRecordId: f.file_record_id,
- 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
- });
- $cards.append($card);
- });
- refreshMaxReachedState();
- }
- function buildCard(opts) {
- var $card = $('<div class="studiou-fpp-card"></div>');
- $card.attr('data-file-record-id', opts.fileRecordId || '');
- $card.attr('data-attachment-id', opts.attachmentId || '');
- if (opts.uploading) $card.addClass('studiou-fpp-card-uploading');
- // Remove-upload button (×)
- $card.append('<button type="button" class="studiou-fpp-card-remove" title="' +
- escAttr(i18n.removeFile || 'Remove') + '" aria-label="' +
- escAttr(i18n.removeFile || 'Remove') + '">×</button>');
- // Thumb column
- var $thumb = $('<div class="studiou-fpp-card-thumb"></div>');
- if (opts.thumbUrl) {
- 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);
- // Body column
- var $body = $('<div class="studiou-fpp-card-body"></div>');
- $body.append($('<div class="studiou-fpp-card-name"></div>').text(opts.fileName || ''));
- var $controls = $('<div class="studiou-fpp-card-controls"></div>');
- $controls.append(buildVariantSelect(opts.variationId || 0));
- $controls.append(buildQtyStepper());
- $body.append($controls);
- 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);
- $card.append($body);
- if (!opts.uploading) {
- updateCardPrice($card);
- }
- return $card;
- }
- function buildVariantSelect(initialVariationId) {
- var $select = $('<select class="studiou-fpp-card-variant"></select>');
- $select.append($('<option></option>').val(0).text(i18n.selectVariant || 'Select variant'));
- for (var i = 0; i < variants.length; i++) {
- var v = variants[i];
- // <option> text nodes cannot render HTML, so use a plain-text price formatter
- var priceLabel = v.base_price ? ' — ' + formatPricePlain(v.base_price) : '';
- var label = v.label + priceLabel;
- if (!v.in_stock) {
- label += ' — ' + (i18n.outOfStock || 'out of stock');
- }
- var $opt = $('<option></option>')
- .val(v.id)
- .text(label)
- .attr('data-base-price', v.base_price);
- if (!v.in_stock) {
- $opt.prop('disabled', true);
- }
- $select.append($opt);
- }
- if (initialVariationId) {
- $select.val(initialVariationId);
- }
- return $select;
- }
- function buildQtyStepper() {
- var $stepper = $('<div class="studiou-fpp-card-qty"></div>');
- $stepper.append('<button type="button" class="studiou-fpp-qty-btn studiou-fpp-qty-minus">−</button>');
- $stepper.append('<input type="number" class="studiou-fpp-qty-input" min="1" step="1" value="1">');
- $stepper.append('<button type="button" class="studiou-fpp-qty-btn studiou-fpp-qty-plus">+</button>');
- return $stepper;
- }
- // ==========================================
- // Card live updates
- // ==========================================
- function initCardDelegation() {
- $cards.on('change', '.studiou-fpp-card-variant', function () {
- updateCardPrice($(this).closest('.studiou-fpp-card'));
- });
- $cards.on('input change keyup', '.studiou-fpp-qty-input', function () {
- updateCardPrice($(this).closest('.studiou-fpp-card'));
- });
- $cards.on('click', '.studiou-fpp-qty-plus', function () {
- var $input = $(this).closest('.studiou-fpp-card-qty').find('.studiou-fpp-qty-input');
- $input.val((parseInt($input.val(), 10) || 1) + 1).trigger('change');
- });
- $cards.on('click', '.studiou-fpp-qty-minus', function () {
- var $input = $(this).closest('.studiou-fpp-card-qty').find('.studiou-fpp-qty-input');
- var v = Math.max(1, (parseInt($input.val(), 10) || 1) - 1);
- $input.val(v).trigger('change');
- });
- $cards.on('click', '.studiou-fpp-card-addtocart', function () {
- onAddToCartClick($(this).closest('.studiou-fpp-card'));
- });
- $cards.on('click', '.studiou-fpp-card-remove', function () {
- onRemoveUploadClick($(this).closest('.studiou-fpp-card'));
- });
- }
- function findVariant(variationId) {
- variationId = parseInt(variationId, 10) || 0;
- if (!variationId) return null;
- for (var i = 0; i < variants.length; i++) {
- if (variants[i].id === variationId) return variants[i];
- }
- return null;
- }
- function resolveTier(tiers, qty) {
- if (!tiers || !tiers.length) return null;
- var match = null;
- for (var i = 0; i < tiers.length; i++) {
- if (tiers[i].from_qty <= qty) match = tiers[i];
- else break;
- }
- return match;
- }
- function updateCardPrice($card) {
- var $price = $card.find('.studiou-fpp-card-price');
- var $badge = $card.find('.studiou-fpp-card-badge');
- var $btn = $card.find('.studiou-fpp-card-addtocart');
- var variationId = parseInt($card.find('.studiou-fpp-card-variant').val(), 10) || 0;
- var qty = Math.max(1, parseInt($card.find('.studiou-fpp-qty-input').val(), 10) || 1);
- var variant = findVariant(variationId);
- if (!variant || !variant.base_price) {
- $price.html('—');
- $badge.hide();
- $btn.prop('disabled', true);
- return;
- }
- var tier = resolveTier(variant.tiers || [], qty);
- var unit = variant.base_price;
- if (tier && tier.percent > 0) {
- unit = variant.base_price * (1 - (tier.percent / 100));
- $badge.show().text('−' + formatPercent(tier.percent));
- } else {
- $badge.hide();
- }
- var lineTotal = unit * qty;
- $price.html(formatPriceHtml(lineTotal));
- var uploading = $card.hasClass('studiou-fpp-card-uploading');
- $btn.prop('disabled', uploading);
- }
- // ==========================================
- // Upload dropzone
- // ==========================================
- function initDropzone() {
- $dropzone.on('dragover dragenter', function (e) {
- e.preventDefault(); e.stopPropagation();
- $(this).addClass('studiou-fpp-dragover');
- });
- $dropzone.on('dragleave drop', function (e) {
- e.preventDefault(); e.stopPropagation();
- $(this).removeClass('studiou-fpp-dragover');
- });
- $dropzone.on('drop', function (e) {
- var files = e.originalEvent.dataTransfer.files;
- enqueueFiles(files);
- });
- $dropzone.on('click', function () {
- if ($dropzone.hasClass('studiou-fpp-dropzone-disabled')) return;
- $fileInput.trigger('click');
- });
- $fileInput.on('change', function () {
- enqueueFiles(this.files);
- $fileInput.val('');
- });
- }
- var uploadQueue = [];
- var activeUpload = false;
- function enqueueFiles(fileList) {
- if (!fileList || !fileList.length) return;
- clearMessages();
- if (cfg.maxUploads > 0) {
- var existing = $cards.children('.studiou-fpp-card').length;
- var available = Math.max(0, cfg.maxUploads - existing - uploadQueue.length);
- if (available <= 0) {
- showMessage(formatI18n(i18n.maxUploadsReached, cfg.maxUploads), 'error');
- return;
- }
- if (fileList.length > available) {
- showMessage(formatI18n(i18n.maxUploadsReached, cfg.maxUploads), 'error');
- }
- for (var i = 0; i < Math.min(fileList.length, available); i++) {
- pushFile(fileList[i]);
- }
- } else {
- for (var j = 0; j < fileList.length; j++) {
- pushFile(fileList[j]);
- }
- }
- refreshMaxReachedState();
- drainQueue();
- }
- function pushFile(file) {
- var maxBytes = cfg.maxFileSize * 1024 * 1024;
- if (file.size > maxBytes) {
- showMessage(formatI18n(i18n.fileTooLarge, cfg.maxFileSize), 'error');
- return;
- }
- var $card = buildCard({
- fileRecordId: 0,
- attachmentId: 0,
- fileName: file.name,
- thumbUrl: '',
- variationId: 0,
- uploading: true
- });
- $cards.append($card);
- uploadQueue.push({file: file, $card: $card});
- }
- function drainQueue() {
- if (activeUpload || !uploadQueue.length) return;
- activeUpload = true;
- var next = uploadQueue.shift();
- uploadOne(next.file, next.$card, function () {
- activeUpload = false;
- drainQueue();
- });
- }
- function uploadOne(file, $card, done) {
- var totalChunks = Math.ceil(file.size / cfg.chunkSize);
- var uploadId = 'upload_' + Date.now() + '_' + Math.random().toString(36).slice(2, 10);
- var currentChunk = 0;
- var $fill = $card.find('.studiou-fpp-card-progress-fill');
- var $text = $card.find('.studiou-fpp-card-progress-text');
- $card.find('.studiou-fpp-card-progress').show();
- function next() {
- var start = currentChunk * cfg.chunkSize;
- var end = Math.min(start + cfg.chunkSize, file.size);
- var blob = file.slice(start, end);
- var form = new FormData();
- form.append('action', 'studiou_wcfpp_upload_chunk');
- form.append('nonce', studiouWcfppFront.nonce);
- form.append('product_id', cfg.productId);
- form.append('upload_id', uploadId);
- form.append('chunk_index', currentChunk);
- form.append('total_chunks', totalChunks);
- form.append('file_name', file.name);
- form.append('file_size', file.size);
- form.append('chunk', blob, 'chunk');
- var isLast = (currentChunk === totalChunks - 1);
- if (isLast) {
- $fill.css('width', '100%');
- $text.text(i18n.processing || 'Processing...');
- }
- $.ajax({
- url: studiouWcfppFront.ajaxUrl,
- type: 'POST',
- data: form,
- processData: false,
- contentType: false,
- timeout: isLast ? 120000 : 30000,
- success: function (response) {
- if (!response.success) {
- failUpload($card, response.data ? response.data.message : (i18n.uploadError || 'Upload failed.'));
- done();
- return;
- }
- if (!response.data.complete) {
- currentChunk++;
- var pct = Math.round((currentChunk / totalChunks) * 100);
- $fill.css('width', pct + '%');
- $text.text(pct + '%');
- next();
- } else {
- finishUpload($card, response.data);
- done();
- }
- },
- error: function () {
- failUpload($card, i18n.uploadError || 'Upload failed.');
- done();
- }
- });
- }
- next();
- }
- function finishUpload($card, res) {
- $card.removeClass('studiou-fpp-card-uploading');
- $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).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)
- .attr('data-preview-url', previewUrl)
- );
- }
- updateCardPrice($card);
- refreshMaxReachedState();
- // If this is now the first/only card, swap the hero gallery image
- if ($cards.children('.studiou-fpp-card').index($card) === 0) {
- applyHeroImage(previewUrl);
- }
- }
- function failUpload($card, message) {
- $card.remove();
- showMessage(message, 'error');
- refreshMaxReachedState();
- }
- function refreshMaxReachedState() {
- if (cfg.maxUploads <= 0) return;
- var reached = $cards.children('.studiou-fpp-card').length >= cfg.maxUploads;
- $dropzone.toggleClass('studiou-fpp-dropzone-disabled', reached);
- }
- // ==========================================
- // Remove upload (per-card × button)
- // ==========================================
- function onRemoveUploadClick($card) {
- var fileRecordId = parseInt($card.attr('data-file-record-id'), 10) || 0;
- if (!fileRecordId) {
- // Still uploading — just cancel locally
- $card.remove();
- refreshMaxReachedState();
- return;
- }
- $.ajax({
- url: studiouWcfppFront.ajaxUrl,
- type: 'POST',
- data: {
- action: 'studiou_wcfpp_remove_upload',
- nonce: studiouWcfppFront.nonce,
- file_record_id: fileRecordId
- },
- success: function (response) {
- if (response.success) {
- var wasFirst = ($cards.children('.studiou-fpp-card').index($card) === 0);
- $card.remove();
- refreshMaxReachedState();
- if (wasFirst) {
- var $nextFirst = $cards.children('.studiou-fpp-card').first();
- if ($nextFirst.length) {
- var nextImg = $nextFirst.find('.studiou-fpp-card-thumb img').attr('src');
- applyHeroImage(nextImg || '');
- } else {
- restoreHeroImage();
- }
- }
- } else {
- showMessage(response.data ? response.data.message : (i18n.error || 'Error'), 'error');
- }
- },
- error: function () {
- showMessage(i18n.error || 'Error', 'error');
- }
- });
- }
- // ==========================================
- // Add to cart
- // ==========================================
- function onAddToCartClick($card) {
- var fileRecordId = parseInt($card.attr('data-file-record-id'), 10) || 0;
- var variationId = parseInt($card.find('.studiou-fpp-card-variant').val(), 10) || 0;
- var qty = Math.max(1, parseInt($card.find('.studiou-fpp-qty-input').val(), 10) || 1);
- if (!fileRecordId) {
- showMessage(i18n.uploadFile || 'Please upload a file.', 'error');
- return;
- }
- if (!variationId) {
- showMessage(i18n.selectVariant || 'Please select a variant.', 'error');
- return;
- }
- var $btn = $card.find('.studiou-fpp-card-addtocart');
- $btn.prop('disabled', true);
- $.ajax({
- url: studiouWcfppFront.ajaxUrl,
- type: 'POST',
- data: {
- action: 'studiou_wcfpp_add_file_to_cart',
- nonce: studiouWcfppFront.nonce,
- file_record_id: fileRecordId,
- variation_id: variationId,
- quantity: qty
- },
- success: function (response) {
- if (response.success) {
- showMessage(i18n.addedToCart || 'Added to cart.', 'success');
- if (response.data && response.data.overview_html) {
- updateOverview(response.data.overview_html);
- }
- } else {
- showMessage(response.data ? response.data.message : (i18n.addToCartError || 'Could not add to cart.'), 'error');
- }
- },
- error: function () {
- showMessage(i18n.addToCartError || 'Could not add to cart.', 'error');
- },
- complete: function () {
- $btn.prop('disabled', false);
- }
- });
- }
- // ==========================================
- // Order overview
- // ==========================================
- function initOverviewDelegation() {
- // Remove-line on overview panel (cart-only, upload stays)
- $(document).on('click', '.studiou-fpp-overview-remove', function (e) {
- var $link = $(this);
- var href = $link.attr('href');
- // Only intercept if the link has a nonce arg — otherwise let browser navigate
- if (!href || href.indexOf('studiou-fpp-remove-line') < 0) return;
- e.preventDefault();
- var $line = $link.closest('.studiou-fpp-overview-line');
- var key = $line.data('cart-item-key');
- if (!key) return;
- $.ajax({
- url: studiouWcfppFront.ajaxUrl,
- type: 'POST',
- data: {
- action: 'studiou_wcfpp_remove_cart_line',
- nonce: studiouWcfppFront.nonce,
- cart_item_key: key
- },
- success: function (response) {
- if (response.success && response.data && response.data.overview_html) {
- updateOverview(response.data.overview_html);
- } else if (!response.success) {
- showMessage(response.data ? response.data.message : (i18n.error || 'Error'), 'error');
- }
- },
- error: function () {
- showMessage(i18n.error || 'Error', 'error');
- }
- });
- });
- }
- function updateOverview(html) {
- var $new = $(html);
- var $existing = $('#studiou-fpp-overview');
- if ($existing.length) {
- $existing.replaceWith($new);
- } else {
- $wrap.after($new);
- }
- initOverviewThumbClicks();
- $(document).trigger('studiou-fpp:cart-updated');
- }
- // ==========================================
- // Hero image
- // ==========================================
- function applyHeroImage(url) {
- if (!url) return;
- var selectors = [
- '.woocommerce-product-gallery__image img',
- '.product .wp-post-image',
- '.product-image img',
- '.product-thumbnail img',
- '.entry-summary img.attachment-woocommerce_thumbnail',
- '.product-gallery img:first'
- ];
- var $img = null;
- for (var i = 0; i < selectors.length; i++) {
- $img = $(selectors[i]).first();
- if ($img.length) break;
- }
- if (!$img || !$img.length) return;
- if (!originalGallery.src) {
- originalGallery.src = $img.attr('src');
- originalGallery.srcset = $img.attr('srcset') || '';
- originalGallery.sizes = $img.attr('sizes') || '';
- var $link = $img.closest('a');
- if ($link.length) originalGallery.href = $link.attr('href');
- }
- $img.attr('src', url).removeAttr('srcset').removeAttr('sizes');
- var $a = $img.closest('a');
- 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 = [
- '.woocommerce-product-gallery__image img',
- '.product .wp-post-image',
- '.product-image img',
- '.product-thumbnail img',
- '.entry-summary img.attachment-woocommerce_thumbnail',
- '.product-gallery img:first'
- ];
- var $img = null;
- for (var i = 0; i < selectors.length; i++) {
- $img = $(selectors[i]).first();
- if ($img.length) break;
- }
- if (!$img || !$img.length) return;
- $img.attr('src', originalGallery.src);
- if (originalGallery.srcset) $img.attr('srcset', originalGallery.srcset);
- if (originalGallery.sizes) $img.attr('sizes', originalGallery.sizes);
- if (originalGallery.href) $img.closest('a').attr('href', originalGallery.href);
- originalGallery = {};
- }
- // ==========================================
- // Helpers
- // ==========================================
- function showMessage(text, type) {
- var cls = type === 'error' ? 'studiou-fpp-msg-error'
- : type === 'success' ? 'studiou-fpp-msg-success'
- : 'studiou-fpp-msg-info';
- $messages.html('<div class="studiou-fpp-msg ' + cls + '">' + escHtml(text) + '</div>');
- }
- function clearMessages() { $messages.empty(); }
- function escHtml(str) {
- var div = document.createElement('div');
- div.appendChild(document.createTextNode(str == null ? '' : String(str)));
- return div.innerHTML;
- }
- function escAttr(str) {
- return String(str == null ? '' : str).replace(/"/g, '"');
- }
- function formatI18n(tpl, val) {
- if (!tpl) return String(val);
- return tpl.replace('%d', val).replace('%s', val);
- }
- function formatPercent(p) {
- var n = parseFloat(p) || 0;
- var str = n.toFixed(2).replace(/\.?0+$/, '');
- return str + ' %';
- }
- function formatPrice(amount) {
- return formatPriceHtml(amount);
- }
- // Plain-text price (no HTML) — used for <option> labels where spans aren't rendered
- function formatPricePlain(amount) {
- var c = studiouWcfppFront.currency || {};
- var decimals = parseInt(c.decimals, 10); if (isNaN(decimals)) decimals = 2;
- var decSep = c.decimalSeparator || '.';
- var thouSep = c.thousandSeparator || ',';
- var fmt = c.priceFormat || '%1$s%2$s';
- var symbol = c.symbol || '';
- var fixed = parseFloat(amount).toFixed(decimals);
- var parts = fixed.split('.');
- var intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thouSep);
- var decPart = parts.length > 1 ? parts[1] : '';
- var numStr = decPart ? intPart + decSep + decPart : intPart;
- // strip any HTML tags that might arrive from priceFormat (defensive)
- return fmt.replace('%1$s', symbol).replace('%2$s', numStr).replace(/<[^>]*>/g, '');
- }
- function formatPriceHtml(amount) {
- var c = studiouWcfppFront.currency || {};
- var decimals = parseInt(c.decimals, 10); if (isNaN(decimals)) decimals = 2;
- var decSep = c.decimalSeparator || '.';
- var thouSep = c.thousandSeparator || ',';
- var fmt = c.priceFormat || '%1$s%2$s';
- var symbol = c.symbol || '';
- var fixed = parseFloat(amount).toFixed(decimals);
- var parts = fixed.split('.');
- var intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thouSep);
- var decPart = parts.length > 1 ? parts[1] : '';
- var numStr = decPart ? intPart + decSep + decPart : intPart;
- var sym = '<span class="woocommerce-Price-currencySymbol">' + symbol + '</span>';
- return '<span class="woocommerce-Price-amount amount">' +
- fmt.replace('%1$s', sym).replace('%2$s', numStr) +
- '</span>';
- }
- });
- })(jQuery);
|