1/** 2 * MetaSlider Lightbox - Clean Implementation 3 * Simple class-based lightbox functionality 4 */ 5 6(function($) { 7 'use strict'; 8 9 const CONSTANTS = { 10 SELECTORS: { 11 WP_GALLERY: '.wp-block-gallery', 12 ML_LIGHTBOX_ENABLED: '.ml-lightbox-enabled', 13 ML_LIGHTBOX_BUTTON: '.ml-lightbox-button', 14 LG_INITIALIZED: '.lg-initialized', 15 CONTENT_CONTAINERS: '.entry-content, .post-content, .page-content, article, .content, main, [role="main"], .site-main, .elementor-widget-theme-post-content, .et_pb_section, .fusion-body' 16 }, 17 CLASSES: { 18 ML_LIGHTBOX_ENABLED: 'ml-lightbox-enabled', 19 ML_LIGHTBOX_BUTTON: 'ml-lightbox-button', 20 LG_INITIALIZED: 'lg-initialized' 21 }, 22 VIDEO_HANDLERS: { 23 youtube: { 24 selector: 'div.youtube[data-id]', 25 idAttr: 'data-id', 26 urlTemplate: (id) => `https://www.youtube.com/watch?v=${id}`, 27 thumbTemplate: (id) => `https://img.youtube.com/vi/${id}/maxresdefault.jpg` 28 }, 29 vimeo: { 30 selector: 'div.vimeo[data-id]', 31 idAttr: 'data-id', 32 urlTemplate: (id) => `https://vimeo.com/${id}`, 33 thumbTemplate: (id) => `https://vumbnail.com/${id}.jpg`, 34 fallbackSelectors: ['[data-slide-id]', 'a[href*="vimeo.com"]'] 35 }, 36 externalVideo: { 37 selector: 'div.external-video', 38 sourcesAttr: 'data-sources', 39 posterAttr: 'data-poster' 40 }, 41 localVideo: { 42 selector: 'div.local-video', 43 sourcesAttr: 'data-sources', 44 posterAttr: 'data-poster' 45 } 46 } 47 }; 48 49 var _metasliderButtonSliderId = null; 50 51 function escapeHtml(str) { 52 return $('<div>').text(str).html(); 53 } 54 55 function isSafeCaptionUrl(url) { 56 var value = $.trim(url || ''); 57 58 return value === '' || 59 value.charAt(0) === '#' || 60 value.charAt(0) === '/' || 61 /^(https?:|mailto:|tel:)/i.test(value); 62 } 63 64 function sanitizeCaptionHtml(html) { 65 var allowedTags = { 66 a: ['href', 'title', 'target', 'rel'], 67 b: [], 68 br: [], 69 em: [], 70 i: [], 71 p: [], 72 span: [], 73 strong: [] 74 }; 75 var $root = $('<div>').html(html || ''); 76 77 $root.find('*').each(function() { 78 var element = this; 79 var tag = element.nodeName.toLowerCase(); 80 81 if (!Object.prototype.hasOwnProperty.call(allowedTags, tag)) { 82 $(element).replaceWith(document.createTextNode($(element).text())); 83 return; 84 } 85 86 $.each($.makeArray(element.attributes), function(index, attribute) { 87 var name = attribute.name.toLowerCase(); 88 89 if (allowedTags[tag].indexOf(name) === -1) { 90 element.removeAttribute(attribute.name); 91 return; 92 } 93 94 if ('href' === name && !isSafeCaptionUrl(attribute.value)) { 95 element.removeAttribute(attribute.name); 96 } 97 }); 98 99 if ('a' === tag && '_blank' === (element.getAttribute('target') || '').toLowerCase()) { 100 element.setAttribute('rel', 'noopener noreferrer'); 101 } 102 }); 103 104 return $.trim($root.html()); 105 } 106 107 function getButtonText(sliderId) { 108 var id = sliderId !== undefined ? sliderId : _metasliderButtonSliderId; 109 if (resolveSliderIcon(id)) { 110 return '<svg class="ml-lightbox-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M21 9V3H15M21 3L13 11M10 5H7.8C6.11984 5 5.27976 5 4.63803 5.32698C4.07354 5.6146 3.6146 6.07354 3.32698 6.63803C3 7.27976 3 8.11984 3 9.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H14.2C15.8802 21 16.7202 21 17.362 20.673C17.9265 20.3854 18.3854 19.9265 18.673 19.362C19 18.7202 19 17.8802 19 16.2V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 111 } 112
113 return (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.button_text) 114 ? mlLightboxSettings.button_text 115 : 'Open in Gallery'; 116 } 117 118 /** 119 * Add accessibility attributes to overlay elements 120 * @param {jQuery} $overlay - The overlay element 121 * @param {string} type - Type of content ('image' or 'video') 122 * @param {string} altText - Alt text or description 123 */ 124 function addOverlayAccessibility($overlay, type, altText) { 125 altText = altText || type; 126 $overlay.attr({ 127 'role': 'button', 128 'aria-label': 'View ' + type + (altText ? ' - ' + altText : ''), 129 'tabindex': '0' 130 }); 131 132 $overlay.on('keydown', function(e) { 133 if (e.key === 'Enter' || e.key === ' ') { 134 e.preventDefault(); 135 $(this).click(); 136 } 137 }); 138 } 139 140 /** 141 * Add accessible name (aria-label) to a link based on image alt text 142 * @param {jQuery} $link - The link element 143 * @param {jQuery} $img - The image element inside the link 144 */ 145 function addAccessibleNameToLink($link, $img) { 146 if (!$link || !$img || $img.length === 0) return; 147 148 var altText = $img.attr('alt') || ''; 149 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 150 151 if (!$link.attr('aria-label') && !$link.text().trim()) { 152 $link.attr('aria-label', ariaLabel); 153 } 154 } 155 156 function isInsideMetaSliderContainer($element) { 157 var $container = $element.closest('.metaslider, [id*="metaslider"], .filmstrip, [id*="filmstrip"]'); 158 if ($container.length > 0) { 159 if ($container.is('body') || $container.is('html')) { 160 return false; 161 } 162 } 163 return $container.length > 0; 164 } 165 166 $(document).ready(function() { 167 if ($('#ml-lightbox-sr-live').length === 0) { 168 $('<div id="ml-lightbox-sr-live" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>') 169 .appendTo('body'); 170 } 171 172 initLightboxes(); 173 removeConflictingAttributes(); 174 ensureAccessibleNames(); 175 176 $(document).on('lgAfterOpen.lg', function(event) { 177 var $lgContainer = $(event.target).find('.lg-container'); 178 179 if ($lgContainer.length > 0) { 180 var totalItems = $(event.target).find('[data-lg-item-id]').length; 181 var ariaLabel = totalItems > 1 182 ? 'Image gallery with ' + totalItems + ' images' 183 : 'Image gallery'; 184 $lgContainer.attr('aria-label', ariaLabel); 185 } 186 187 announceToScreenReader('Gallery opened'); 188 }); 189 190 $(document).on('lgAfterSlide.lg', function(event, prevIndex, index) { 191 var $container = $(event.target); 192 var $items = $container.find('[data-lg-item-id="' + index + '"]'); 193 if ($items.length > 0) { 194 var $item = $items.first(); 195 var alt = $item.find('img').attr('alt') || ''; 196 var caption = $item.attr('data-sub-html') || ''; 197 var totalItems = $container.find('[data-lg-item-id]').length; 198 199 var announcement = 'Image ' + (index + 1) + ' of ' + totalItems; 200 if (alt) announcement += ', ' + alt; 201 if (caption) announcement += ', ' + $('<div>').html(caption).text(); 202 203 announceToScreenReader(announcement); 204 } 205 }); 206 207 $(document).on('lgBeforeClose.lg', function() { 208 announceToScreenReader('Gallery closed'); 209 }); 210 }); 211 212 /** 213 * Announce message to screen readers via live region 214 * @param {string} message - The message to announce 215 */ 216 function announceToScreenReader(message) { 217 var $liveRegion = $('#ml-lightbox-sr-live'); 218 if ($liveRegion.length > 0) { 219 $liveRegion.text(message); 220 setTimeout(function() { 221 $liveRegion.text(''); 222 }, 1000); 223 } 224 } 225 226 /**
227 * Remove conflicting attributes when lightbox button is present 228 * Prevents container clicks when buttons are available 229 */ 230 function removeConflictingAttributes() { 231 $('.ml-lightbox-enabled').each(function() { 232 var $container = $(this); 233 234 if ($container.find('.ml-lightbox-button').length > 0) { 235 $container.removeAttr('href'); 236 $container.removeAttr('data-src'); 237 $container.removeAttr('data-thumb'); 238 } 239 }); 240 } 241 242 /** 243 * Ensure all lightbox-enabled links have accessible names 244 * Adds aria-label to links without accessible text 245 */ 246 function ensureAccessibleNames() { 247 $('.ml-lightbox-enabled').each(function() { 248 var $link = $(this); 249 250 if ($link.attr('aria-label') || $link.text().trim()) { 251 return; 252 } 253 254 var $img = $link.find('img').first(); 255 if ($img.length > 0) { 256 ensureImageAltAttribute($img); 257 addAccessibleNameToLink($link, $img); 258 } 259 }); 260 261 $('a[data-src]').each(function() { 262 var $link = $(this); 263 264 if ($link.attr('aria-label') || $link.text().trim()) { 265 return; 266 } 267 268 var $img = $link.find('img').first(); 269 if ($img.length > 0) { 270 ensureImageAltAttribute($img); 271 addAccessibleNameToLink($link, $img); 272 } 273 }); 274 } 275 276 /** 277 * Ensure an image has an alt attribute 278 * @param {jQuery} $img - The image element 279 */ 280 function ensureImageAltAttribute($img) { 281 if (typeof $img.attr('alt') === 'undefined') { 282 var src = $img.attr('src') || ''; 283 var filename = src.split('/').pop().split('?')[0]; 284 var altText = ''; 285 286 if (filename) { 287 altText = filename 288 .replace(/\.(jpg|jpeg|png|gif|webp|svg)$/i, '') 289 .replace(/[-_]/g, ' ') 290 .trim(); 291 } 292 293 $img.attr('alt', altText); 294 } 295 } 296 297 /** 298 * Main initialization function 299 */ 300 function initLightboxes() { 301 302 if (typeof lightGallery === 'undefined') { 303 console.warn('MetaSlider Lightbox: LightGallery not loaded'); 304 return; 305 } 306 307 if (typeof mlLightboxSettings === 'undefined') { 308 console.warn('MetaSlider Lightbox: Settings not available'); 309 return; 310 } 311 312 initLightboxSystems(); 313 314 } 315 316 function initLightboxSystems() { 317 initMetaSlider(); 318 319 var manualExcluded = mlLightboxSettings.manual_excluded === true || mlLightboxSettings.manual_excluded === '1' || mlLightboxSettings.manual_excluded === 1; 320 321 if (!manualExcluded) { 322 var $elements = $('.ml-lightbox-enabled'); 323 var useButtonsForManual = false; 324 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.metaslider_options) { 325 useButtonsForManual = mlLightboxSettings.metaslider_options.show_lightbox_button !== false; 326 } 327 328 $elements.each(function() { 329 var $element = $(this); 330 if (isInsideMetaSliderContainer($element)) { 331 return; 332 } 333 initLightboxEnabled($element, useButtonsForManual); 334 }); 335 } 336 337 var overrideEnabled = typeof mlLightboxSettings !== 'undefined' && (mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1); 338 if (overrideEnabled && !manualExcluded) { 339 var $wordpressOverrideGalleries = $('.wp-block-gallery').filter(function() { 340 var $gallery = $(this); 341 var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]'); 342 var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]'); 343 return ($wpImagesOld.length > 1 || $wpContainersNew.length > 1) && 344 !$gallery.hasClass('ml-lightbox-enabled') && 345 !$gallery.hasClass('lg-initialized'); 346 }); 347 348 $wordpressOverrideGalleries.each(function() { 349 initWordPressGallery($(this), useButtonsForManual); 350 }); 351 } 352
353 var pageExcluded = mlLightboxSettings.page_excluded === true || mlLightboxSettings.page_excluded === '1' || mlLightboxSettings.page_excluded === 1; 354 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) { 355 var $wordpressGalleries = $('.wp-block-gallery').filter(function() { 356 var $gallery = $(this); 357 358 if ($gallery.attr('data-ml-exclude-gallery') === 'true') { 359 return false; 360 } 361 var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]'); 362 var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]'); 363 var overrideEnabled = mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1; 364 if (!overrideEnabled && ($wpImagesOld.length > 0 || $wpContainersNew.length > 0)) { 365 return false; 366 } 367 return ($wpImagesOld.length > 1 || $wpContainersNew.length > 1) && 368 !$gallery.hasClass('ml-lightbox-enabled') && 369 !$gallery.hasClass('lg-initialized'); 370 }); 371 372 $wordpressGalleries.each(function() { 373 initWordPressGallery($(this), useButtonsForManual); 374 }); 375 376 var $classicGalleries = $('.gallery').filter(function() { 377 var $gallery = $(this); 378 379 if ($gallery.attr('data-ml-exclude-gallery') === 'true') { 380 return false; 381 } 382 383 var $galleryLinks = $gallery.find('a.ml-gallery-lightbox'); 384 385 return $galleryLinks.length > 1 && 386 !$gallery.hasClass('ml-lightbox-enabled') && 387 !$gallery.hasClass('lg-initialized'); 388 }); 389 390 $classicGalleries.each(function() { 391 initClassicGallery($(this)); 392 }); 393 } 394 395 if (!manualExcluded) { 396 var $wordpressContainersOld = $('.wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger'); 397 var $wordpressContainersNew = $('.wp-lightbox-container[data-wp-interactive="core/image"]'); 398 var $allWordpressContainers = $wordpressContainersOld.add($wordpressContainersNew); 399 400 var $unprocessedContainers = $allWordpressContainers.filter(function() { 401 var $figure = $(this); 402 var $parentGallery = $figure.closest('.wp-block-gallery'); 403 404 var overrideEnabled = typeof mlLightboxSettings !== 'undefined' && (mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1); 405 if (!overrideEnabled) { 406 return false; 407 } 408 409 if ($parentGallery.length > 0) { 410 var $galleryContainers = $parentGallery.find('.wp-lightbox-container[data-wp-interactive="core/image"], .wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger'); 411 if ($galleryContainers.length > 1) { 412 return false; 413 } 414 } 415 416 return !$figure.hasClass('ml-lightbox-enabled') && 417 !$figure.find('.ml-lightbox-enabled').length && 418 !$figure.hasClass('lg-initialized') && 419 ($parentGallery.length === 0 || !$parentGallery.hasClass('lg-initialized')); 420 }); 421 422 $unprocessedContainers.each(function() { 423 var $container = $(this); 424 var $img = $container.find('img').first(); 425 426 if ($img.length) { 427 if (useButtonsForManual) { 428 initWordPressEnlargeOnClickWithButton($img); 429 } else { 430 initWordPressEnlargeOnClick($img); 431 } 432 } 433 }); 434 } 435
436 var overrideEnabled = typeof mlLightboxSettings !== 'undefined' && 437 (mlLightboxSettings.override_enlarge_on_click === true || 438 mlLightboxSettings.override_enlarge_on_click === '1' || 439 mlLightboxSettings.override_enlarge_on_click === 1); 440 441 if (overrideEnabled && !manualExcluded) { 442 var $enlargeOnClickGalleries = $('.wp-block-gallery').filter(function() { 443 var $gallery = $(this); 444 var $wpButtons = $gallery.find('button[data-wp-on], button.lightbox-trigger'); 445 446 return $wpButtons.length > 1 && 447 !$gallery.hasClass('ml-lightbox-enabled') && 448 !$gallery.hasClass('lg-initialized'); 449 }); 450 451 $enlargeOnClickGalleries.each(function() { 452 initEnlargeOnClickGallery($(this), useButtonsForManual); 453 }); 454 } 455 456 var linkOverrideEnabled = typeof mlLightboxSettings !== 'undefined' && 457 (mlLightboxSettings.override_link_to_image_file === true || 458 mlLightboxSettings.override_link_to_image_file === '1' || 459 mlLightboxSettings.override_link_to_image_file === 1); 460 461 if (linkOverrideEnabled && !manualExcluded) { 462 var $linkToMediaManualGalleries = $('.wp-block-gallery').filter(function() { 463 var $gallery = $(this); 464 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 465 return $(this).find('img').length > 0; 466 }); 467 468 return $mediaLinks.length > 1 && 469 !$gallery.hasClass('ml-lightbox-enabled') && 470 !$gallery.hasClass('lg-initialized') && 471 $gallery.find('.wp-lightbox-container').length === 0 && 472 $gallery.find('button[data-wp-on], button.lightbox-trigger').length === 0; 473 }); 474 475 $linkToMediaManualGalleries.each(function() { 476 initLinkToMediaManualGallery($(this), useButtonsForManual); 477 }); 478 } 479 480 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) { 481 var $linkToMediaGalleries = $('.wp-block-gallery').filter(function() { 482 var $gallery = $(this); 483 484 if ($gallery.attr('data-ml-exclude-gallery') === 'true') { 485 return false; 486 } 487 488 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 489 return $(this).find('img').length > 0; 490 }); 491 492 return $mediaLinks.length > 1 && 493 !$gallery.hasClass('ml-lightbox-enabled') && 494 !$gallery.hasClass('lg-initialized') && 495 $gallery.find('.wp-lightbox-container').length === 0; 496 }); 497 498 $linkToMediaGalleries.each(function() { 499 initLinkToMediaGallery($(this), useButtonsForManual); 500 }); 501 } 502 503 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) { 504 var $plainImageGalleries = $('.wp-block-gallery').filter(function() { 505 var $gallery = $(this); 506 507 if ($gallery.attr('data-ml-exclude-gallery') === 'true') { 508 return false; 509 } 510 511 var $images = $gallery.find('img'); 512 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]'); 513 var $wpLightboxImages = $gallery.find('img[data-wp-on], img[data-wp-interactive]'); 514 515 return $images.length > 1 && 516 $mediaLinks.length === 0 && 517 $wpLightboxImages.length === 0 && 518 !$gallery.hasClass('ml-lightbox-enabled') && 519 !$gallery.hasClass('lg-initialized'); 520 }); 521 522 $plainImageGalleries.each(function() { 523 initPlainImageGallery($(this), useButtonsForManual); 524 }); 525 } 526
527 if (mlLightboxSettings.override_link_to_image_file && !manualExcluded) { 528 var $mediaLinks = $('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 529 var $link = $(this); 530 var $parentGallery = $link.closest('.wp-block-gallery'); 531 var $parentContainer = $link.closest('.wp-lightbox-container'); 532 var $wooGallery = $link.closest('.woocommerce-product-gallery'); 533 var $mlGallery = $link.closest('.ml-gallery-container'); 534 return $link.find('img').length > 0 && 535 $parentGallery.length === 0 && 536 $parentContainer.length === 0 && 537 $wooGallery.length === 0 && 538 $mlGallery.length === 0 && 539 !$link.hasClass('lg-initialized') && 540 !$link.closest('.ml-lightbox-enabled').length; 541 }); 542 543 $mediaLinks.each(function() { 544 if (useButtonsForManual) { 545 initLinkToMediaFileWithButton($(this)); 546 } else { 547 initLinkToMediaFile($(this)); 548 } 549 }); 550 } 551 552 if (mlLightboxSettings.enable_on_content && !pageExcluded) { 553 var contentImageSelectors = []; 554 var containers = CONSTANTS.SELECTORS.CONTENT_CONTAINERS.split(', '); 555 containers.forEach(function(container) { 556 contentImageSelectors.push(container + ' img[src$=".jpg"]'); 557 contentImageSelectors.push(container + ' img[src$=".jpeg"]'); 558 contentImageSelectors.push(container + ' img[src$=".png"]'); 559 contentImageSelectors.push(container + ' img[src$=".gif"]'); 560 contentImageSelectors.push(container + ' img[src$=".webp"]'); 561 contentImageSelectors.push(container + ' img[src$=".svg"]'); 562 }); 563 564 var $standaloneImages = $(contentImageSelectors.join(', ')).filter(function() { 565 var $img = $(this); 566 if ($img.closest('.wp-block-gallery').length > 0) { 567 return false; 568 } 569 if ($img.closest('.woocommerce-product-gallery').length > 0) { 570 return false; 571 } 572 if (isInsideMetaSliderContainer($img)) { 573 return false; 574 } 575 if ($img.closest('.flex-control-nav, .flex-control-thumbs, .filmstrip').length > 0) { 576 return false; 577 } 578 if ($img.hasClass('ml-lightbox-enabled') || 579 $img.closest('.ml-lightbox-wrapper').length > 0 || 580 $img.closest('.ml-image-overlay').length > 0 || 581 $img.siblings('.ml-lightbox-button').length > 0 || 582 $img.parent().hasClass('lg-initialized')) { 583 return false; 584 } 585 if ($img.parent('a').length > 0) { 586 return false; 587 } 588 if ($img.hasClass('wp-post-image')) { 589 return false; 590 } 591 if ($img.attr('data-wp-on') || $img.attr('data-wp-interactive')) { 592 return false; 593 } 594 if ($img.attr('data-ml-exclude') === 'true') { 595 return false; 596 } 597 598 var uiClasses = ['custom-logo', 'site-logo', 'logo', 'avatar', 'icon', 'emoji', 'thumbnail', 'thumb']; 599 for (var i = 0; i < uiClasses.length; i++) { 600 if ($img.hasClass(uiClasses[i])) { 601 return false; 602 } 603 } 604 605 var uiContainers = ['.wp-block-site-logo', '.site-logo', '.site-branding', '.site-header', '.site-footer', '.navigation', '.nav', '.menu', '.widget', '.sidebar']; 606 for (var j = 0; j < uiContainers.length; j++) { 607 if ($img.closest(uiContainers[j]).length > 0) { 608 return false;
609 } 610 } 611 612 var minWidth = mlLightboxSettings.minimum_image_width || 200; 613 var minHeight = mlLightboxSettings.minimum_image_height || 200; 614 615 // Check displayed dimensions (not actual file size) to match server-side behavior 616 var imgWidth = $img.width() || $img.attr('width') || 0; 617 var imgHeight = $img.height() || $img.attr('height') || 0; 618 619 if (minWidth > 0 && imgWidth > 0 && imgWidth < minWidth) { 620 return false; 621 } 622 if (minHeight > 0 && imgHeight > 0 && imgHeight < minHeight) { 623 return false; 624 } 625 626 return true; 627 }); 628 629 $standaloneImages.each(function() { 630 if (useButtonsForManual) { 631 handleSingleElementWithButton($(this)); 632 } else { 633 handleSingleElement($(this)); 634 } 635 }); 636 } 637 638 if (mlLightboxSettings.enable_featured_images && !pageExcluded) { 639 $('a.ml-featured-lightbox[data-src]').filter(function() { 640 return !$(this).hasClass('lg-initialized'); 641 }).each(function() { 642 if (useButtonsForManual) { 643 initFeaturedImageWithButton($(this)); 644 } else { 645 initFeaturedImage($(this)); 646 } 647 }); 648 } 649 650 // Note: pageExcluded does NOT gate gallery init â galleries are explicitly 651 // placed by the user and are unaffected by Automatic Mode content filtering. 652 { 653 /** 654 * Apply all per-gallery data-lg-* settings onto a settings object. 655 * Used by both regular and carousel init paths. 656 */ 657 // Wrap caption HTML in a .ml-caption-text span so the caption 658 // transition animates the text, not the .lg-sub-html bar. Set in JS 659 // (never in the PHP data-sub-html attribute â wptexturize mangles 660 // markup inside attributes). Idempotent. 661 function wrapMlCaptionText(sub) { 662 sub = sanitizeCaptionHtml(sub); 663 if (sub && sub.indexOf('ml-caption-text') === -1) { 664 return '<span class="ml-caption-text">' + sub + '</span>'; 665 } 666 return sub; 667 } 668 669 function bindLividSlides(container) { 670 var autoplayFirst = container.getAttribute('data-lg-video-autoplay') === '1'; 671 var autoplayEach = container.getAttribute('data-lg-video-autoplay-each') === '1'; 672 var openedAt = -1; 673 674 var frameAt = function(index) { 675 var inst = container._mlLgInstance; 676 var item = inst ? document.getElementById('lg-item-' + inst.lgId + '-' + index) : null; 677 var frame = item ? item.querySelector('iframe.lg-object') : null; 678 var src = frame ? (frame.getAttribute('data-ml-livid-src') || frame.getAttribute('src') || '') : ''; 679 return src.indexOf('https://livid.com/embed/') === 0 ? frame : null; 680 }; 681 682 var load = function(index, autoplay, force) { 683 var frame = frameAt(index); 684 if (!frame) { return; } 685 var base = frame.getAttribute('data-ml-livid-src'); 686 if (!base) { return; } 687 var target = autoplay ? base + (base.indexOf('?') === -1 ? '?' : '&') + 'autoplay=1' : base; 688 if (force || frame.getAttribute('src') !== target) { 689 frame.setAttribute('src', target); 690 } 691 }; 692 693 container.addEventListener('lgBeforeOpen', function() { 694 openedAt = container._mlLgInstance ? container._mlLgInstance.index : -1; 695 }); 696 697 container.addEventListener('lgAfterAppendSlide', function(e) { 698 var frame = frameAt(e.detail.index); 699 if (!frame || frame.hasAttribute('data-ml-livid-src')) { return; } 700 frame.setAttribute('data-ml-livid-src', frame.getAttribute('src')); 701 frame.setAttribute('allow', 'autoplay; fullscreen; picture-in-picture; encrypted-media'); 702 var isCurrent = container._mlLgInstance.index === e.detail.index; 703 var isOpening = autoplayFirst && openedAt === e.detail.index; 704 if (isOpening) { openedAt = -1; } 705 load(e.detail.index, isCurrent && (autoplayEach || isOpening), true); 706 }); 707 708 container.addEventListener('lgBeforeSlide', function(e) { 709 if (e.detail.prevIndex !== e.detail.index) { 710 load(e.detail.prevIndex, false, true); 711 } 712 }); 713 714 container.addEventListener('lgAfterSlide', function(e) { 715 if (autoplayEach && e.detail.prevIndex !== e.detail.index) { 716 load(e.detail.index, true, false); 717 } 718 }); 719 } 720 721 function applyGallerySettings(settings, $container) { 722 var lgClass = $container.data('lg-class') || ''; 723 if (lgClass) { settings.addClass = lgClass; } 724 725 var captionTransition = $container.data('lg-caption-transition'); 726 if (captionTransition && captionTransition !== 'none') { 727 settings.appendSubHtmlTo = '.lg-item'; 728 settings.slideDelay = settings.slideDelay || 400; 729 settings.addClass = (settings.addClass ? settings.addClass + ' ' : '') + 'ml-cap-' + captionTransition; 730 } 731 732 settings.mode = $container.data('lg-mode') || settings.mode; 733 settings.controls = !!parseInt($container.data('lg-control
733s'), 10); 734 settings.counter = !!parseInt($container.data('lg-counter'), 10); 735 settings.download = !!parseInt($container.data('lg-download'), 10); 736 settings.loop = !!parseInt($container.data('lg-loop'), 10); 737 738 // Caption Content is the only source; never the image's alt/title. 739 settings.getCaptionFromTitleOrAlt = false; 740 741 if (!parseInt($container.data('lg-captions'), 10)) { 742 settings.subHtml = ''; 743 } 744 745 var wantThumbs = !!parseInt($container.data('lg-thumbnails'), 10); 746 if (typeof lgThumbnail !== 'undefined') { 747 if (wantThumbs) { 748 if (settings.plugins.indexOf(lgThumbnail) === -1) { 749 settings.plugins.push(lgThumbnail); 750 } 751 } else { 752 settings.plugins = settings.plugins.filter(function(p) { return p !== lgThumbnail; }); 753 } 754 } 755 if (wantThumbs) { 756 settings.thumbWidth = 100; 757 settings.thumbHeight = '80px'; 758 settings.thumbMargin = 4; 759 settings.exThumbImage = 'data-thumb'; 760 } 761 762 if (!!parseInt($container.data('lg-zoom'), 10) && typeof lgZoom !== 'undefined') { 763 if (settings.plugins.indexOf(lgZoom) === -1) { settings.plugins.push(lgZoom); } 764 } else if (typeof lgZoom !== 'undefined') { 765 settings.plugins = settings.plugins.filter(function(p) { return p !== lgZoom; }); 766 } 767 768 if (!!parseInt($container.data('lg-fullscreen'), 10) && typeof lgFullscreen !== 'undefined') { 769 if (settings.plugins.indexOf(lgFullscreen) === -1) { settings.plugins.push(lgFullscreen); } 770 } 771 772 if (!!parseInt($container.data('lg-rotate'), 10) && typeof lgRotate !== 'undefined') { 773 if (settings.plugins.indexOf(lgRotate) === -1) { settings.plugins.push(lgRotate); } 774 } 775 776 if (!!parseInt($container.data('lg-share'), 10) && typeof lgShare !== 'undefined') { 777 if (settings.plugins.indexOf(lgShare) === -1) { settings.plugins.push(lgShare); } 778 } 779 780 if (!!parseInt($container.data('lg-autoplay'), 10) && typeof lgAutoplay !== 'undefined') { 781 if (settings.plugins.indexOf(lgAutoplay) === -1) { settings.plugins.push(lgAutoplay); } 782 settings.autoplay = true; 783 settings.progressBar = true; 784 settings.autoplayFirstVideo = false; 785 settings.autoplayInterval = parseInt($container.data('lg-autoplay-interval'), 10) || 3000; 786 } 787 788 if (!!parseInt($container.data('lg-pager'), 10) && typeof lgPager !== 'undefined') { 789 if (settings.plugins.indexOf(lgPager) === -1) { settings.plugins.push(lgPager); } 790 } 791 792 if (!!parseInt($container.data('lg-hash'), 10) && typeof lgHash !== 'undefined') { 793 if (settings.plugins.indexOf(lgHash) === -1) { settings.plugins.push(lgHash); } 794 } 795 796 settings.swipeToClose = !!parseInt($container.data('lg-swipe-close'), 10); 797 settings.mousewheel = !!parseInt($container.data('lg-mousewheel'), 10); 798 settings.keyPress = !!parseInt($container.data('lg-keyboard'), 10); 799 800 // Video playback. The attributes are only emitted for galleries that 801 // hold a video, and an absent one leaves lg-video's own default alone 802 // (autoplayFirstVideo and gotoNextSlideOnVideoEnd both default true). 803 var videoFlag = function(attr) { 804 var raw = $container.attr(attr); 805 return (typeof raw === 'undefined') ? null : !!parseInt(raw, 10); 806 }; 807 808 var videoAutoplay = videoFlag('data-lg-video-autoplay'); 809 var videoAutoplayEach = videoFlag('data-lg-video-autoplay-each'); 810 var videoChain = videoFlag('data-lg-video-chain'); 811 812 if (videoAutoplay !== null) { settings.autoplayFirstVideo = videoAutoplay; } 813 if (videoAutoplayEach !== null) { settings.autoplayVideoOnSlide = videoAutoplayEach; } 814 if (videoChain !== null) { settings.gotoNextSlideOnVideoEnd = videoChain; } 815 816 return applyMobileSettings(settings); 817 } 818 819 var inlineLayoutPlugins = { 820 carousel: [] 821 }; 822 823 var $mlInlineFsGallery = null; 824 $(document).off('.mlInlineFs').on( 825 'fullscreenchange.mlInlineFs webkitfullscreenchange.mlInlineFs mozfullscreenchange.mlInlineFs MSFullscreenChange.mlInlineFs', 826 function() { 827 if (!$mlInlineFsGallery) { return; } 828 var inFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || 829 document.mozFullScreenElement || document.msFullscreenElement); 830 var $gallery = $mlInlineFsGallery; 831 $gallery.find('.lg-container').first().toggleClass('lg-inline', !inFullscreen); 832 var inst = $gallery[0]._mlLgInstance; 833 if (inst && typeof inst.refreshOnResize === 'function') { inst.refreshOnResize(); } 834 if (!inFullscreen) { $mlInlineFsGallery = null; } 835 } 836 ); 837
838 function bindInlineTrigger($container, inlineGallery, dynamicEl) { 839 var openModalAt = function(index) { 840 var modal = $container[0]._mlModalInstance; 841 if (!modal) { 842 var host = document.createElement('div'); 843 host.className = 'ml-gallery-modal-host'; 844 $container[0].appendChild(host); 845 846 var modalSettings = applyGallerySettings(getLightboxSettings(true), $container); 847 modalSettings.dynamic = true; 848 modalSettings.dynamicEl = dynamicEl; 849 modalSettings.selector = ''; 850 modalSettings.hash = false; 851 852 modal = lightGallery(host, modalSettings); 853 bindLividSlides(host); 854 $container[0]._mlModalInstance = modal; 855 } 856 modal.openGallery(index); 857 }; 858 859 var $stageButton = $container.find('.ml-gallery-stage-button'); 860 if ($stageButton.length) { 861 $stageButton.on('click', function(e) { 862 e.preventDefault(); 863 openModalAt(inlineGallery.index || 0); 864 }); 865 return; 866 } 867 868 // LG inline handles swipe, so only a press that did not drag counts as a click. 869 var downX = 0; 870 var downY = 0; 871 $container.on('pointerdown.mlTrigger', '.lg-object', function(e) { 872 downX = e.clientX; 873 downY = e.clientY; 874 }); 875 $container.on('click.mlTrigger', '.lg-object', function(e) { 876 if (Math.abs(e.clientX - downX) > 6 || Math.abs(e.clientY - downY) > 6) { return; } 877 e.preventDefault(); 878 openModalAt(inlineGallery.index || 0); 879 }); 880 } 881 882 $('.ml-gallery-container[data-ml-gallery]').filter(function() { 883 return !$(this).hasClass('lg-initialized'); 884 }).each(function() { 885 var $container = $(this); 886 887 var mlLayout = $container.data('ml-layout'); 888 // An inline layout is the gallery itself, so it is built either way; the flag 889 // only decides whether clicking it opens the modal on top. 890 var lightboxOn = $container.data('ml-lightbox') !== 0; 891 892 if ( inlineLayoutPlugins.hasOwnProperty( mlLayout ) ) { 893 var dynamicEl = []; 894 // Copy: inlineLayoutPlugins is shared, so per-container additions 895 // (and applyGallerySettings' own pushes) must not leak to the next gallery. 896 var inlinePlugins = inlineLayoutPlugins[ mlLayout ].slice(); 897 var hasInlineVideo = false; 898 899 $container.find('a[data-src], a[data-video]').each(function () { 900 var $a = $(this); 901 var dataVideo = $a.attr('data-video'); 902 var el; 903 904 if (dataVideo) { 905 // lightGallery JSON.parses a string `video` prop. src must be 906 // blank or it is treated as the slide source and video 907 // detection fails â same convention as the DOM-selector paths. 908 el = { src: '', video: dataVideo }; 909 hasInlineVideo = true; 910 } else { 911 el = { src: $a.attr('data-src') }; 912 if ($a.attr('data-iframe') === 'true') { 913 el.iframe = true; 914 el.downloadUrl = false; 915 } 916 if ($a.attr('data-download-url') === 'false') { 917 el.downloadUrl = false; 918 } 919 // A provider video (YouTube, Vimeo) keeps its watch URL as the 920 // src so lightGallery's own detection can embed it, and carries 921 // the poster separately rather than
921inside a video payload. 922 if ($a.attr('data-poster')) { 923 el.poster = $a.attr('data-poster'); 924 } 925 if ($a.hasClass('ml-gallery-video') && $a.attr('data-iframe') !== 'true') { 926 hasInlineVideo = true; 927 } 928 } 929 930 if ($a.attr('data-thumb')) { el.thumb = $a.attr('data-thumb'); } 931 if ($a.attr('data-sub-html')) { el.subHtml = wrapMlCaptionText($a.attr('data-sub-html')); } 932 dynamicEl.push(el); 933 }); 934 935 // This path passes plugins explicitly, so lgVideo never arrives 936 // via getBaseSettings(). Mirror it here when there is video to render. 937 var inlineWantsVideo = hasInlineVideo && typeof lgVideo !== 'undefined'; 938 if (inlineWantsVideo && inlinePlugins.indexOf(lgVideo) === -1) { 939 inlinePlugins.push(lgVideo); 940 } 941 942 if (dynamicEl.length) { 943 var inlineBase = { 944 container : $container[0], 945 dynamic : true, 946 dynamicEl : dynamicEl, 947 plugins : inlinePlugins, 948 hash : false, 949 closable : false, 950 showMaximizeIcon: !!parseInt($container.data('lg-expand'), 10), 951 appendSubHtmlTo : '.lg-item', 952 }; 953 if (inlineWantsVideo) { 954 inlineBase.videoMaxSize = '1280-720'; 955 } 956 957 var inlineSettings = applyGallerySettings(inlineBase, $container); 958 959 try { 960 var inlineGallery = lightGallery($container[0], inlineSettings); 961 $container[0]._mlLgInstance = inlineGallery;
962 bindLividSlides($container[0]); 963 inlineGallery.openGallery(); 964 $container.addClass('lg-initialized'); 965 966 $container.on('click.mlInlineFs', '.lg-fullscreen', function() { 967 $mlInlineFsGallery = $container; 968 }); 969 970 if (lightboxOn) { 971 bindInlineTrigger($container, inlineGallery, dynamicEl); 972 } 973 } catch (error) { 974 console.error('MetaSlider Lightbox: inline gallery init error:', error); 975 } 976 } 977 return; 978 } 979 980 if (!lightboxOn) { 981 return; 982 } 983 984 var settings = getLightboxSettings(true); 985 applyGallerySettings(settings, $container); 986 987 var perGalleryButton = $container.attr('data-ml-show-button') === '1'; 988 989 if (perGalleryButton) { 990 var perIcon = $container.attr('data-ml-button-icon') === '1'; 991 var perText = $container.attr('data-ml-button-text') || ''; 992 993 // Re-runnable: processed anchors lose data-src, so a later call 994 // only picks up items the Load More reveal just unhid. 995 var ensureButtons = function() { 996 // .not('.ml-lightbox-button'): the injected buttons are themselves 997 // a[data-src], so a re-run would nest a button inside every button. 998 $container.find('a[data-src], a[data-video]').not('.ml-item-hidden').not('.ml-lightbox-button').each(function() { 999 var $a = $(this); 1000 var dataSrc = $a.attr('data-src'); 1001 var dataVideo = $a.attr('data-video'); 1002 var $img = $a.find('img').first(); 1003 var imgSrc = $img.attr('src') || dataSrc; 1004 var alt = $img.attr('alt') || ''; 1005 1006 var btnLabel; 1007 if (perIcon) { 1008 btnLabel = '<svg class="ml-lightbox-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M21 9V3H15M21 3L13 11M10 5H7.8C6.11984 5 5.27976 5 4.63803 5.32698C4.07354 5.6146 3.6146 6.07354 3.32698 6.63803C3 7.27976 3 8.11984 3 9.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H14.2C15.8802 21 16.7202 21 17.362 20.673C17.9265 20.3854 18.3854 19.9265 18.673 19.362C19 18.7202 19 17.8802 19 16.2V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 1009 } else { 1010 btnLabel = perText || 'Open in Gallery'; 1011 } 1012 1013 var $btn = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-gallery" href="#">' + btnLabel + '</a>'); 1014 var btnAttrs = { 1015 'data-thumb': $a.attr('data-thumb') || imgSrc, 1016 'aria-label': mlLightboxSettings.view_image_label + (alt ? ': ' + alt : ''), 1017 }; 1018 if (dataVideo) { 1019 // lightGallery maps both href and data-src onto the same 1020 // internal "src" key, last DOM attribute wins. The button's 1021 // own href="#" would otherwise be read as the slide src and 1022 // fail lightGallery's video-URL detection, so data-src must 1023 // be explicitly blanked (matches the pattern used for 1024 // content-embedded videos elsewhere in this file). 1025 btnAttrs['data-src'] = '';
1026 btnAttrs['data-video'] = dataVideo; 1027 } else { 1028 btnAttrs['data-src'] = dataSrc; 1029 if ($a.attr('data-iframe')) { btnAttrs['data-iframe'] = $a.attr('data-iframe'); } 1030 if ($a.attr('data-download-url')) { btnAttrs['data-download-url'] = $a.attr('data-download-url'); } 1031 // A provider video's poster lives on the anchor, so it has 1032 // to move onto the button that becomes the slide source. 1033 if ($a.attr('data-poster')) { btnAttrs['data-poster'] = $a.attr('data-poster'); } 1034 } 1035 $btn.attr(btnAttrs); 1036 // The button becomes lightGallery's slide source (selector below), 1037 // so carry the caption over or it would be dropped. 1038 var subHtml = $a.attr('data-sub-html'); 1039 if (subHtml) { 1040 $btn.attr('data-sub-html', sanitizeCaptionHtml(subHtml)); 1041 } 1042 $a.css('position', 'relative').append($btn); 1043 1044 // Strip the wrapper link's own attributes so clicking the 1045 // image itself doesn't navigate to the image URL. 1046 $a.removeAttr('href').removeAttr('data-src').removeAttr('data-video').removeAttr('data-iframe').removeAttr('data-download-url').removeAttr('data-thumb').removeAttr('data-poster'); 1047 }); 1048 }; 1049 ensureButtons(); 1050 $container[0]._mlEnsureButtons = ensureButtons; 1051 settings.selector = '.ml-lightbox-button'; 1052 } else { 1053 // Same href/data-src collision as ensureButtons above: the video 1054 // anchor's own href (the raw .mp4 URL) would otherwise be read as 1055 // the slide src and fail lightGallery's video-URL detection. Prep 1056 // every video anchor up front (including still-hidden ones so 1057 // Load More reveals need no extra wiring) â visibility is still 1058 // gated by the :not(.ml-item-hidden) selector below. 1059 $container.find('a[data-video]').attr('data-src', ''); 1060 settings.selector = 'a[data-src]:not(.ml-item-hidden), a[data-video]:not(.ml-item-hidden)'; 1061 } 1062 1063 var galleryCaptionTransition = $container.data('lg-caption-transition'); 1064 $container.find('a[data-sub-html]').each(function() { 1065 var $a = $(this); 1066 if (galleryCaptionTransition && galleryCaptionTransition !== 'none') { 1067 $a.attr('data-sub-html', wrapMlCaptionText($a.attr('data-sub-html'))); 1068 } else { 1069 $a.attr('data-sub-html', sanitizeCaptionHtml($a.attr('data-sub-html'))); 1070 } 1071 }); 1072 1073 try { 1074 $container[0]._mlLgInstance = lightGallery($container[0], settings);
1075 bindLividSlides($container[0]); 1076 $container.addClass('lg-initialized'); 1077 } catch (error) { 1078 console.error('MetaSlider Lightbox: Error initializing gallery shortcode:', error); 1079 } 1080 }); 1081 } 1082 } 1083 1084 function initLightboxEnabled($container, useButtonMode) { 1085 useButtonMode = useButtonMode || false; 1086 1087 if ($container.hasClass('lg-initialized') || $container.data('lightgallery')) { 1088 return; 1089 } 1090 var $imageLinks = $container.find('a[data-src], a[data-video]'); 1091 1092 if ($imageLinks.length === 0) { 1093 var $images = $container.find('img'); 1094 1095 if ($images.length > 1) { 1096 $images.each(function() { 1097 var $img = $(this); 1098 var $figure = $img.closest('figure, div'); 1099 if ($figure.length === 0) $figure = $img.parent(); 1100 1101 if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) { 1102 return; 1103 } 1104 1105 var src = $img.attr('src'); 1106 if (!src) return; 1107 1108 var fullUrl = removeWordPressSizeSuffix(src); 1109 1110 if (useButtonMode) { 1111 var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>'); 1112 $button.attr({ 1113 'data-src': fullUrl, 1114 'data-thumb': src 1115 }); 1116 $figure.css('position', 'relative').append($button); 1117 } else {
1118 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1119 $overlay.attr({ 1120 'data-src': fullUrl, 1121 'data-thumb': src 1122 }); 1123 1124 var altText = $img.attr('alt') || ''; 1125 addOverlayAccessibility($overlay, 'image', altText); 1126 1127 $figure.css('position', 'relative').prepend($overlay); 1128 } 1129 }); 1130 1131 var gallerySettings = getLightboxSettings(true); 1132 gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay'; 1133 1134 try { 1135 var instance = lightGallery($container[0], gallerySettings); 1136 $container.addClass('lg-initialized'); 1137 } catch (error) { 1138 console.error('MetaSlider Lightbox: Error initializing manual gallery:', error); 1139 } 1140 return; 1141 } 1142 1143 if (useButtonMode) { 1144 handleSingleElementWithButton($container); 1145 } else { 1146 handleSingleElement($container); 1147 } 1148 return; 1149 } 1150 1151 var isGallery = $imageLinks.length > 1; 1152 1153 1154 var settings = getLightboxSettings(isGallery); 1155 1156 try { 1157 var instance = lightGallery($container[0], settings); 1158 $container.addClass('lg-initialized'); 1159 1160 } catch (error) { 1161 console.error('MetaSlider Lightbox: Error initializing LightGallery:', error); 1162 } 1163 } 1164 1165 /** 1166 * Handle single elements that don't have PHP-processed data-src links 1167 */ 1168 function handleSingleElement($element) { 1169 var $target = null; 1170 var dataSrc = ''; 1171 var dataThumb = ''; 1172 var isVideo = false; 1173 1174 if ($element.is('img')) { 1175 var src = $element.attr('src'); 1176 if (!src) return; 1177 1178 var $wrapper = $('<div class="ml-lightbox-wrapper"></div>'); 1179 $element.wrap($wrapper); 1180 $target = $element.parent(); 1181 1182 var fullUrl = removeWordPressSizeSuffix(src); 1183 1184 $target.attr({ 1185 'data-src': fullUrl, 1186 'data-thumb': src 1187 }); 1188 1189 var caption = extractCaption($element); 1190 if (caption) { 1191 $target.attr('data-sub-html', caption); 1192 } 1193 1194 1195 } else if ($element.is('a')) { 1196 var href = $element.attr('href'); 1197 if (!href) return; 1198 1199 $target = $element; 1200 var $img = $element.find('img').first(); 1201 var imgSrc = $img.length ? $img.attr('src') : href; 1202 1203 isVideo = isVideoUrl(href); 1204 1205 if (isVideo) { 1206 var videoUrl = getVideoUrl(href); 1207 $target.attr({ 1208 'data-src': videoUrl, 1209 'data-thumb': imgSrc || getVideoThumbnail(href) 1210 }); 1211 } else { 1212 $target.attr({ 1213 'data-src': href, 1214 'data-thumb': imgSrc 1215 }); 1216 } 1217 1218 var caption = extractCaption($element); 1219 if (caption) { 1220 $target.attr('data-sub-html', caption); 1221 } 1222 1223 } else { 1224 1225 var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first(); 1226 var $video = $element.find('video').first(); 1227 1228 if ($iframe.length > 0) { 1229 var iframeSrc = $iframe.attr('src'); 1230
1231 var $overlay = $('<a href="#" class="ml-video-overlay"></a>'); 1232 1233 var videoUrl = getVideoUrl(iframeSrc); 1234 $overlay.attr({ 1235 'data-src': videoUrl, 1236 'data-thumb': getVideoThumbnail(iframeSrc) 1237 }); 1238 1239 var caption = extractCaption($element); 1240 if (caption) { 1241 $overlay.attr('data-sub-html', caption); 1242 } 1243 1244 $element.css('position', 'relative').prepend($overlay); 1245 $target = $element; 1246 1247 isVideo = true; 1248 1249 } else if ($video.length > 0) { 1250 var videoSrc = $video.attr('src') || $video.find('source').first().attr('src'); 1251 if (!videoSrc) return; 1252 1253 var videoData = { 1254 source: [{ src: videoSrc, type: getVideoType(videoSrc) }], 1255 attributes: { preload: false, controls: true } 1256 }; 1257 1258 var $overlay = $('<a href="#" class="ml-video-overlay"></a>'); 1259 1260 $overlay.attr({ 1261 'data-src': '', 1262 'data-video': JSON.stringify(videoData), 1263 'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+' 1264 }); 1265 1266 var caption = extractCaption($element); 1267 if (caption) { 1268 $overlay.attr('data-sub-html', caption); 1269 } 1270 1271 $element.css('position', 'relative').prepend($overlay); 1272 $target = $element; 1273 1274 isVideo = true; 1275 1276 } else { 1277 var $img = $element.find('img').first(); 1278 if (!$img.length) return; 1279 1280 var src = $img.attr('src'); 1281 if (!src) return; 1282 1283 var fullUrl = removeWordPressSizeSuffix(src); 1284 1285 if ($element.hasClass('wp-lightbox-container')) {
1286 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1287 1288 $overlay.attr({ 1289 'data-src': fullUrl, 1290 'data-thumb': src 1291 }); 1292 1293 var caption = extractCaption($element); 1294 if (caption) { 1295 $overlay.attr('data-sub-html', caption); 1296 } 1297 1298 var altText = $img.attr('alt') || ''; 1299 addOverlayAccessibility($overlay, 'image', altText); 1300 1301 $element.css('position', 'relative').prepend($overlay); 1302 $target = $element; 1303 1304 } else { 1305 $target = $element; 1306 $target.attr({ 1307 'data-src': fullUrl, 1308 'data-thumb': src 1309 }); 1310 1311 var caption = extractCaption($element); 1312 if (caption) { 1313 $target.attr('data-sub-html', caption); 1314 } 1315 1316 } 1317 } 1318 } 1319 1320 if (!$target) return; 1321 1322 var settings = getLightboxSettings(false); 1323 1324 if (isVideo && $target.find('.ml-video-overlay').length > 0) { 1325 settings.selector = '.ml-video-overlay'; 1326 } else if (!isVideo && $target.find('.ml-image-overlay').length > 0) { 1327 settings.selector = '.ml-image-overlay'; 1328 } else { 1329 settings.selector = 'this'; 1330 } 1331 1332 try { 1333 var instance = lightGallery($target[0], settings); 1334 $target.addClass('lg-initialized'); 1335 1336 } catch (error) { 1337 console.error('MetaSlider Lightbox: Error initializing single lightbox:', error); 1338 } 1339 } 1340 1341 /** 1342 * Handle single elements that need button mode instead of overlay mode 1343 */ 1344 function handleSingleElementWithButton($element) { 1345 if ($element.hasClass('lg-initialized') || $element.data('lightgallery')) { 1346 return; 1347 } 1348 1349 var $button = null; 1350 1351 if ($element.is('img')) { 1352 var src = $element.attr('src'); 1353 if (!src) return; 1354 1355 var fullUrl = removeWordPressSizeSuffix(src); 1356 1357 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-img" href="#">' + getButtonText() + '</a>'); 1358 1359 $button.attr({ 1360 'data-src': fullUrl, 1361 'data-thumb': src 1362 }); 1363 1364 var caption = extractCaption($element); 1365 if (caption) { 1366 $button.attr('data-sub-html', caption); 1367 } 1368 1369 $element.wrap('<div style="position: relative; display: inline-block;"></div>'); 1370 $element.parent().append($button); 1371 1372 var settings = getLightboxSettings(false); 1373 settings.selector = '.ml-lightbox-button'; 1374 1375 try { 1376 var instance = lightGallery($element.parent()[0], settings); 1377 $element.parent().addClass('lg-initialized'); 1378 } catch (error) { 1379 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1380 } 1381 1382 } else if ($element.is('a')) { 1383 var href = $element.attr('href'); 1384 if (!href) return; 1385 1386 var $img = $element.find('img').first(); 1387 var imgSrc = $img.length > 0 ? $img.attr('src') : ''; 1388 1389 var isVideo = isVideoUrl(href); 1390 1391 if (isVideo) { 1392 var videoUrl = getVideoUrl(href); 1393 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-video" href="#">' + getButtonText() + '</a>'); 1394 1395 $button.attr({ 1396 'data-src': videoUrl, 1397 'data-thumb': imgSrc || getVideoThumbnail(href) 1398 }); 1399 } else { 1400 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-link" href="#">' + getButtonText() + '</a>'); 1401 1402 $button.attr({ 1403 'data-src': href,
1404 'data-thumb': imgSrc 1405 }); 1406 } 1407 1408 var caption = extractCaption($element); 1409 if (caption) { 1410 $button.attr('data-sub-html', caption); 1411 } 1412 1413 $element.css('position', 'relative').append($button); 1414 1415 var settings = getLightboxSettings(false); 1416 settings.selector = '.ml-lightbox-button'; 1417 1418 try { 1419 var instance = lightGallery($element[0], settings); 1420 $element.addClass('lg-initialized'); 1421 } catch (error) { 1422 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1423 } 1424 1425 } else { 1426 1427 var $existingButton = $element.find('.ml-lightbox-button').first(); 1428 if ($existingButton.length > 0) { 1429 var settings = getLightboxSettings(false); 1430 settings.selector = '.ml-lightbox-button'; 1431 1432 try { 1433 var instance = lightGallery($element[0], settings); 1434 $element.addClass('lg-initialized'); 1435 } catch (error) { 1436 console.error('MetaSlider Lightbox: Error initializing existing button:', error); 1437 } 1438 return; 1439 } 1440 1441 var $img = $element.find('img').first(); 1442 var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first(); 1443 var $video = $element.find('video').first(); 1444 1445 if ($img.length > 0) { 1446 var src = $img.attr('src'); 1447 if (src) { 1448 var fullUrl = removeWordPressSizeSuffix(src); 1449 1450 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-img" href="#">' + getButtonText() + '</a>'); 1451 1452 $button.attr({ 1453 'data-src': fullUrl, 1454 'data-thumb': src 1455 }); 1456 } 1457 } else if ($iframe.length > 0) { 1458 var iframeSrc = $iframe.attr('src'); 1459 if (iframeSrc) { 1460 var videoUrl = getVideoUrl(iframeSrc); 1461 1462 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-iframe" href="#">' + getButtonText() + '</a>'); 1463 1464 $button.attr({ 1465 'data-src': videoUrl, 1466 'data-thumb': getVideoThumbnail(iframeSrc) 1467 }); 1468 } 1469 } else if ($video.length > 0) { 1470 var videoSrc = $video.attr('src') || $video.find('source').first().attr('src'); 1471 if (videoSrc) { 1472 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-video" href="#">' + getButtonText() + '</a>'); 1473 1474 var videoData = { 1475 source: [{ 1476 src: videoSrc, 1477 type: getVideoType(videoSrc) 1478 }], 1479 attributes: { 1480 preload: false, 1481 controls: true 1482 } 1483 }; 1484 1485 $button.attr({ 1486 'data-src': '', 1487 'data-video': JSON.stringify(videoData), 1488 'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+' 1489 }); 1490 } 1491 } 1492 1493 if ($button) { 1494 var caption = extractCaption($element); 1495 if (caption) { 1496 $button.attr('data-sub-html', caption); 1497 } 1498 1499 $element.css('position', 'relative').append($button); 1500
1501 var settings = getLightboxSettings(false); 1502 settings.selector = '.ml-lightbox-button'; 1503 1504 try { 1505 var instance = lightGallery($element[0], settings); 1506 $element.addClass('lg-initialized'); 1507 } catch (error) { 1508 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1509 } 1510 } 1511 } 1512 } 1513 1514 // lightGallery's own mobileSettings default drops the close icon, the arrows 1515 // and download on iOS/Android, overriding whatever was configured here. 1516 function applyMobileSettings(settings) { 1517 settings.mobileSettings = { 1518 controls: settings.controls, 1519 showCloseIcon: true, 1520 download: settings.download 1521 }; 1522 1523 return settings; 1524 } 1525 1526 /** 1527 * Get LightGallery settings based on admin configuration 1528 */ 1529 function getLightboxSettings(isGallery) { 1530 var metasliderOptions = mlLightboxSettings.metaslider_options || {}; 1531 1532 var settings = { 1533 selector: 'a[data-src]', 1534 mode: 'lg-fade', 1535 speed: 350, 1536 download: false, 1537 counter: isGallery, 1538 closable: true, 1539 closeOnTap: true, 1540 controls: !!metasliderOptions.show_arrows, 1541 plugins: [], 1542 escapeKey: true, 1543 keyPress: true, 1544 mousewheel: true, 1545 allowMediaOverlap: false, 1546 ariaLabelledby: '', 1547 ariaDescribedby: '' 1548 }; 1549 1550 // Feed WordPress-translated labels into lightGallery's built-in UI 1551 // (close, prev/next, download, etc.), which are hardcoded English by default. 1552 if (mlLightboxSettings.lg_strings) { 1553 settings.strings = mlLightboxSettings.lg_strings; 1554 } 1555 1556 if (typeof _mlLk !== 'undefined') { 1557 settings.licenseKey = _mlLk; 1558 } 1559 1560 if (!shouldShowCaptions()) { 1561 settings.subHtml = ''; 1562 settings.getCaptionFromTitleOrAlt = false; 1563 } 1564 1565 if (isGallery && metasliderOptions.show_thumbnails && typeof lgThumbnail !== 'undefined') { 1566 settings.plugins.push(lgThumbnail); 1567 settings.thumbWidth = 100; 1568 settings.thumbHeight = '80px'; 1569 settings.thumbMargin = 4; 1570 settings.exThumbImage = 'data-thumb'; 1571 } 1572 1573 if (typeof lgVideo !== 'undefined') { 1574 settings.plugins.push(lgVideo); 1575 settings.videoMaxSize = '1280-720'; 1576 } 1577 1578 if (typeof window.MetaSliderLightboxPro !== 'undefined' && 1579 typeof window.MetaSliderLightboxPro.enhanceSettings === 'function') { 1580 settings = window.MetaSliderLightboxPro.enhanceSettings(settings, null); 1581 } 1582 1583 return applyMobileSettings(settings); 1584 } 1585 1586 /** 1587 * Remove WordPress image size suffix to get full-size image URL 1588 */ 1589 function removeWordPressSizeSuffix(url) { 1590 if (!url) return ''; 1591 return url.replace(/-\d+x\d+(\.[^.]+)$/, '$1'); 1592 } 1593 1594 function getSlideImageLink($slide) { 1595 return $slide.find('a').has('img').first(); 1596 } 1597 1598 function slideHasCustomLink($slide) { 1599 var href = $.trim(getSlideImageLink($slide).attr('href') || ''); 1600 1601 return href !== '' && href !== '#' && href.toLowerCase().indexOf('javascript:') !== 0; 1602 } 1603 1604 /** 1605 * Check if URL is a video URL 1606 */ 1607 function isVideoUrl(url) { 1608 if (!url) return false; 1609 1610 return /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/|vimeo\.com\/(?:video\/)?|\.(?:mp4|webm|ogg|mov|avi)(?:\?|$))/i.test(url); 1611 } 1612 1613 /** 1614 * Get video URL in format that LightGallery expects 1615 * LightGallery automatically detects and handles YouTube/Vimeo URLs 1616 */ 1617 function getVideoUrl(url) { 1618 if (!url) return ''; 1619 1620 var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/); 1621 if (youtubeMatch) { 1622 return 'https://www.youtube.com/watch?v=' + youtubeMatch[1]; 1623 } 1624 1625 var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/); 1626 if (vimeoMatch) { 1627 return 'https://vimeo.com/' + vimeoMatch[1]; 1628 } 1629 1630 return url; 1631 } 1632 1633 /** 1634 * Get video thumbnail URL 1635 */ 1636 function getVideoThumbnail(url) { 1637 if (!url) return ''; 1638 1639 var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/); 1640 if (youtubeMatch) { 1641 return 'https://img.youtube.com/vi/' + youtubeMatch[1] + '/maxresdefault.jpg'; 1642 } 1643 1644 var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/); 1645 if (vimeoMatch) { 1646 return 'https://vumbnail.com/' + vimeoMatch[1] + '.jpg'; 1647 } 1648 1649 return ''; 1650 } 1651 1652 /** 1653 * Initialize a WordPress "Enlarge on Click" gallery 1654 */ 1655 function initWordPressGallery($gallery, useButtonMode) { 1656 useButtonMode = useButtonMode || false; 1657 1658 var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]'); 1659 var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]'); 1660 1661 var $allContainers = $(); 1662 1663 $wpImagesOld.each(function() { 1664 $allContainers = $allContainers.add($(this).closest('.wp-lightbox-container')); 1665 }); 1666 1667 $allContainers = $allContainers.add($wpContainersNew); 1668 1669 if ($allContainers.length <= 1) return; 1670 1671 $allContainers.each(function() { 1672 var $figure = $(this); 1673 var $img = $figure.find('img').first(); 1674 1675 if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) { 1676 return; 1677 } 1678 1679 var src = $img.attr('src'); 1680 if (!src) return; 1681 1682 var fullUrl = removeWordPressSizeSuffix(src); 1683 1684 if (useButtonMode) { 1685 var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>'); 1686 $button.attr({ 1687 'data-src': fullUrl,
1688 'data-thumb': src 1689 }); 1690 $figure.css('position', 'relative').append($button); 1691 } else { 1692 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1693 $overlay.attr({ 1694 'data-src': fullUrl, 1695 'data-thumb': src 1696 }); 1697 1698 var altText = $img.attr('alt') || ''; 1699 addOverlayAccessibility($overlay, 'image', altText); 1700 1701 $figure.css('position', 'relative').prepend($overlay); 1702 } 1703 }); 1704 1705 var gallerySettings = getLightboxSettings(true); 1706 gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay'; 1707 1708 gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); 1709 1710 try { 1711 var instance = lightGallery($gallery[0], gallerySettings); 1712 $gallery.addClass('lg-initialized'); 1713 1714 } catch (error) { 1715 console.error('MetaSlider Lightbox: Error initializing WordPress gallery:', error); 1716 } 1717 } 1718 1719 1720 /** 1721 * Initialize a classic [gallery] shortcode gallery 1722 */ 1723 function initClassicGallery($gallery) { 1724 var $galleryLinks = $gallery.find('a.ml-gallery-lightbox'); 1725 1726 if ($galleryLinks.length <= 1) return; 1727 1728 $galleryLinks.each(function() { 1729 var $link = $(this); 1730 var $img = $link.find('img').first(); 1731 1732 if ($link.attr('data-src')) { 1733 return; 1734 } 1735 1736 var href = $link.attr('href'); 1737 var src = $img.attr('src'); 1738 1739 if (!href) return; 1740 1741 $link.attr({ 1742 'data-src': href, 1743 'data-thumb': src || href 1744 }); 1745 1746 var caption = escapeHtml($img.attr('alt') || $img.attr('title') || ''); 1747 var $figcaption = $link.closest('.gallery-item').find('figcaption, .wp-caption-text'); 1748 if ($figcaption.length > 0) { 1749 caption = escapeHtml($figcaption.text().trim()); 1750 } 1751 1752 if (caption) { 1753 $link.attr('data-sub-html', '<div class="lg-sub-html"><p>' + caption + '</p></div>'); 1754 } 1755 }); 1756 1757 var gallerySettings = getLightboxSettings(true); 1758 gallerySettings.selector = 'a.ml-gallery-lightbox'; 1759 1760 gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); 1761 1762 try { 1763 var instance = lightGallery($gallery[0], gallerySettings); 1764 $gallery.addClass('lg-initialized'); 1765 1766 } catch (error) { 1767 console.error('MetaSlider Lightbox: Error initializing classic gallery:', error); 1768 } 1769 } 1770 1771 /** 1772 * Initialize a single WordPress "Enlarge on Click" image with overlay approach 1773 */ 1774 function initWordPressEnlargeOnClick($img) { 1775 var $figure = $img.closest('.wp-lightbox-container'); 1776 1777 var src = $img.attr('src'); 1778 if (!src) return; 1779 1780 var fullUrl = removeWordPressSizeSuffix(src); 1781
1782 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1783 1784 $overlay.attr({ 1785 'data-src': fullUrl, 1786 'data-thumb': src 1787 }); 1788 1789 var caption = extractCaption($img); 1790 if (caption) { 1791 $overlay.attr('data-sub-html', caption); 1792 } 1793 1794 var altText = $img.attr('alt') || ''; 1795 addOverlayAccessibility($overlay, 'image', altText); 1796 1797 var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 1798 $wpButton.remove(); 1799 1800 $figure.css('position', 'relative').prepend($overlay); 1801 1802 var settings = getLightboxSettings(false); 1803 settings.selector = '.ml-image-overlay'; 1804 1805 try { 1806 var instance = lightGallery($figure[0], settings); 1807 $figure.addClass('lg-initialized'); 1808 1809 } catch (error) { 1810 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click overlay:', error); 1811 } 1812 } 1813 1814 /** 1815 * Initialize a single WordPress "Enlarge on Click" image with button approach 1816 */ 1817 function initWordPressEnlargeOnClickWithButton($img) { 1818 var $figure = $img.closest('.wp-lightbox-container'); 1819 1820 var src = $img.attr('src'); 1821 if (!src) return; 1822 1823 var fullUrl = removeWordPressSizeSuffix(src); 1824 1825 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-single" href="#">' + getButtonText() + '</a>'); 1826 1827 $button.attr({ 1828 'data-src': fullUrl, 1829 'data-thumb': src 1830 }); 1831 1832 var caption = extractCaption($img); 1833 if (caption) { 1834 $button.attr('data-sub-html', caption); 1835 } 1836 1837 var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 1838 $wpButton.remove(); 1839 1840 $figure.css('position', 'relative').append($button); 1841 1842 var settings = getLightboxSettings(false); 1843 settings.selector = '.ml-lightbox-button'; 1844 1845 try { 1846 var instance = lightGallery($figure[0], settings); 1847 $figure.addClass('lg-initialized'); 1848 1849 } catch (error) { 1850 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click button:', error); 1851 } 1852 } 1853 1854 /** 1855 * Initialize a "Link to Media File" gallery 1856 */ 1857 function initLinkToMediaGallery($gallery, useButtonMode) { 1858 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 1859 return $(this).find('img').length > 0; 1860 }); 1861 1862 if ($mediaLinks.length <= 1) return; 1863 1864 if (useButtonMode) { 1865 $mediaLinks.each(function() { 1866 var $link = $(this); 1867 var href = $link.attr('href'); 1868 var $img = $link.find('img').first(); 1869 var imgSrc = $img.attr('src'); 1870 1871 if (!href || !imgSrc) return; 1872 1873 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single" href="#">' + getButtonText() + '</a>'); 1874 $button.attr({ 1875 'data-src': href, 1876 'data-thumb': imgSrc 1877 }); 1878 1879 var caption = extractCaption($img); 1880 if (caption) { 1881 $button.attr('data-sub-html', caption); 1882 } 1883 1884 $link.parent().append($button); 1885 }); 1886 1887 var gallerySettings = getLightboxSettings(true); 1888 gallerySettings.selector = '.ml-lightbox-button'; 1889 1890 } else { 1891 $mediaLinks.each(function() { 1892 var $link = $(this); 1893 var href = $link.attr('href'); 1894 var $img = $link.find('img').first(); 1895 var imgSrc = $img.attr('src'); 1896 1897 if (!href || !imgSrc) return; 1898 1899 $link.attr({ 1900 'data-src': href, 1901 'data-thumb': imgSrc 1902 }); 1903 1904 var caption = extractCaption($img); 1905 if (caption) { 1906 $link.attr('data-sub-html', caption); 1907 } 1908 }); 1909
1910 var gallerySettings = getLightboxSettings(true); 1911 gallerySettings.selector = 'a[data-src]'; 1912 } 1913 1914 try { 1915 var instance = lightGallery($gallery[0], gallerySettings); 1916 $gallery.addClass('lg-initialized'); 1917 1918 } catch (error) { 1919 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" gallery:', error); 1920 } 1921 } 1922 1923 /** 1924 * Initialize a plain image gallery (no links, no WordPress lightbox) 1925 */ 1926 function initPlainImageGallery($gallery, useButtonMode) { 1927 var $images = $gallery.find('img'); 1928 1929 if ($images.length <= 1) return; 1930 1931 if (useButtonMode) { 1932 $images.each(function() { 1933 var $img = $(this); 1934 var imgSrc = $img.attr('src'); 1935 1936 if (!imgSrc) return; 1937 1938 var fullUrl = removeWordPressSizeSuffix(imgSrc); 1939 1940 var $figure = $img.closest('.wp-block-image'); 1941 if ($figure.length === 0) { 1942 $figure = $img.parent(); 1943 } 1944 1945 $figure.attr({ 1946 'data-src': fullUrl, 1947 'data-thumb': imgSrc 1948 }); 1949 1950 var caption = extractCaption($img); 1951 if (caption) { 1952 $figure.attr('data-sub-html', caption); 1953 } 1954 1955 var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-plain-gallery" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>'); 1956 if (caption) { 1957 $button.attr('data-sub-html', caption); 1958 } 1959 1960 $figure.css('position', 'relative').append($button); 1961 }); 1962 1963 var gallerySettings = getLightboxSettings(true); 1964 gallerySettings.selector = '.ml-lightbox-button'; 1965 1966 } else { 1967 $images.each(function() { 1968 var $img = $(this); 1969 var imgSrc = $img.attr('src'); 1970 1971 if (!imgSrc) return; 1972 1973 var fullUrl = removeWordPressSizeSuffix(imgSrc); 1974 1975 var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>'); 1976 1977 var caption = extractCaption($img); 1978 if (caption) { 1979 $wrapper.attr('data-sub-html', caption); 1980 } 1981 1982 $img.wrap($wrapper); 1983 }); 1984 1985 var gallerySettings = getLightboxSettings(true); 1986 gallerySettings.selector = 'a[data-src]'; 1987 } 1988 1989 try { 1990 var instance = lightGallery($gallery[0], gallerySettings); 1991 $gallery.addClass('lg-initialized'); 1992 1993 } catch (error) { 1994 console.error('MetaSlider Lightbox: Error initializing plain image gallery:', error); 1995 } 1996 } 1997 1998 /** 1999 * Initialize a single "Link to Media File" image 2000 */ 2001 function initLinkToMediaFile($link) { 2002 var href = $link.attr('href'); 2003 var $img = $link.find('img').first(); 2004 var imgSrc = $img.attr('src'); 2005 2006 if (!href || !imgSrc) return; 2007 2008 var altText = $img.attr('alt') || ''; 2009 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 2010 2011 $link.attr({ 2012 'data-src': href, 2013 'data-thumb': imgSrc, 2014 'aria-label': ariaLabel 2015 }); 2016 2017 var caption = extractCaption($img); 2018 if (caption) { 2019 $link.attr('data-sub-html', caption); 2020 } 2021 2022 var settings = getLightboxSettings(false); 2023 settings.selector = 'this'; 2024 2025 try { 2026 var instance = lightGallery($link[0], settings); 2027 $link.addClass('lg-initialized'); 2028 2029 } catch (error) { 2030 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" image:', error); 2031 } 2032 } 2033 2034 /** 2035 * Initialize a single "Link to Media File" image with button approach 2036 */ 2037 function initLinkToMediaFileWithButton($link) { 2038 var href = $link.attr('href'); 2039 var $img = $link.find('img').first(); 2040 var imgSrc = $img.attr('src'); 2041 2042 if (!href || !imgSrc) return; 2043 2044 var altText = $img.attr('alt') || ''; 2045 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 2046 2047 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">
2047' + getButtonText() + '</a>'); 2048 2049 $button.attr({ 2050 'data-src': href, 2051 'data-thumb': imgSrc, 2052 'aria-label': ariaLabel 2053 }); 2054 2055 var caption = extractCaption($img); 2056 if (caption) { 2057 $button.attr('data-sub-html', caption); 2058 } 2059 2060 $link.attr('aria-label', ariaLabel); 2061 2062 $link.css('position', 'relative').append($button); 2063 2064 var settings = getLightboxSettings(false); 2065 settings.selector = '.ml-lightbox-button'; 2066 2067 try { 2068 var instance = lightGallery($link[0], settings); 2069 $link.addClass('lg-initialized'); 2070 2071 } catch (error) { 2072 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" button:', error); 2073 } 2074 } 2075 2076 /** 2077 * Initialize a featured image lightbox where the outer <a> links to the post 2078 * and data-src holds the image URL for the lightbox 2079 */ 2080 function initFeaturedImageWithButton($link) { 2081 var dataSrc = $link.attr('data-src'); 2082 var $img = $link.find('img').first(); 2083 var imgSrc = $img.attr('src'); 2084 2085 if (!dataSrc || !imgSrc) return; 2086 2087 var altText = $img.attr('alt') || ''; 2088 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 2089 2090 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">' + getButtonText() + '</a>'); 2091 $button.attr({ 2092 'data-src': dataSrc, 2093 'data-thumb': imgSrc, 2094 'aria-label': ariaLabel 2095 }); 2096 2097 $link.attr('aria-label', ariaLabel).css('position', 'relative').append($button); 2098 2099 var settings = getLightboxSettings(false); 2100 settings.selector = '.ml-lightbox-button'; 2101 2102 try { 2103 lightGallery($link[0], settings); 2104 $link.addClass('lg-initialized'); 2105 } catch (error) { 2106 console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error); 2107 } 2108 } 2109 2110 /** 2111 * Initialize a featured image lightbox without a button â clicking the link opens the lightbox directly 2112 */ 2113 function initFeaturedImage($link) { 2114 var dataSrc = $link.attr('data-src'); 2115 var $img = $link.find('img').first(); 2116 var imgSrc = $img.attr('src'); 2117 2118 if (!dataSrc || !imgSrc) return; 2119 2120 var altText = $img.attr('alt') || ''; 2121 $link.attr('aria-label', 'View image' + (altText ? ': ' + altText : '')); 2122 2123 var settings = getLightboxSettings(false); 2124 settings.selector = 'this'; 2125 2126 try { 2127 lightGallery($link[0], settings); 2128 $link.addClass('lg-initialized'); 2129 } catch (error) { 2130 console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error); 2131 } 2132 } 2133 2134 /** 2135 * Initialize MetaSlider lightbox functionality 2136 * Based on settings from ml-lightgallery-init.js but simplified for integration 2137 */ 2138 function initMetaSlider() { 2139 var $sliders = $('.metaslider, [class*="ml-slider-lightbox-"]'); 2140 2141 if ($sliders.length === 0) { 2142 return; 2143 } 2144 2145 2146 $sliders.each(function() { 2147 var $slider = $(this); 2148 2149 if ($slider.hasClass('lg-initialized')) { 2150 return; 2151 } 2152 2153 var sliderId = $slider.attr('id'); 2154 2155 if (!sliderId) { 2156 var $firstSlide = $slider.find('li').first(); 2157 var slideClass = $firstSlide.attr('class'); 2158 if (slideClass) { 2159 var classMatch = slideClass.match(/slider-(\d+)/); 2160 if (classMatch) { 2161 sliderId = classMatch[1]; 2162 } 2163 } 2164 2165 if (!sliderId) { 2166 var $firstImg = $firstSlide.find('img').first(); 2167 var imgClass = $firstImg.attr('class'); 2168 if (imgClass) { 2169 var imgClassMatch = imgClass.match(/slider-(\d+)/); 2170 if (imgClassMatch) { 2171 sliderId = imgClassMatch[1]; 2172 } 2173 } 2174 } 2175 } 2176 2177 if (sliderId) { 2178 var matches = sliderId.match(/(\d+)/); 2179 if (matches) { 2180 sliderId = matches[1]; 2181 } 2182 } 2183 2184
2185 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.slider_settings && sliderId) { 2186 var sliderSettings = mlLightboxSettings.slider_settings[sliderId]; 2187 if (sliderSettings && sliderSettings.lightbox_enabled === false) { 2188 return; 2189 } 2190 } 2191 2192 var showButton = resolveSliderButton(sliderId); 2193 2194 if (showButton) { 2195 initMetaSliderButtonMode($slider, sliderId); 2196 } else { 2197 initMetaSliderDirectMode($slider, sliderId); 2198 } 2199 }); 2200 } 2201 2202 /** 2203 * Read a per-slider boolean override (true/false) from slider_settings. 2204 * PHP sends true/false/null where null means "use global default". 2205 * Returns the per-slider value when set, otherwise calls globalFn(). 2206 */ 2207 function resolveSliderSetting(sliderId, key, globalFn) { 2208 var perSlider = mlLightboxSettings.slider_settings && 2209 sliderId && 2210 mlLightboxSettings.slider_settings[sliderId]; 2211 var val = perSlider ? perSlider[key] : null; 2212 if (val === true) { return true; } 2213 if (val === false) { return false; } 2214 return globalFn(); 2215 } 2216 2217 function resolveSliderCaptions(sliderId) { 2218 return resolveSliderSetting(sliderId, 'lightbox_captions', shouldShowCaptions); 2219 } 2220 2221 function resolveSliderNavigation(sliderId) { 2222 return resolveSliderSetting(sliderId, 'lightbox_navigation', function() { 2223 return (mlLightboxSettings.metaslider_options || {}).show_arrows !== false; 2224 }); 2225 } 2226 2227 /** 2228 * Unified "how visitors open images" mode for a slider: 'image'|'icon'|'button'. 2229 * PHP sends slider_settings[id].lightbox_open_with as one of those three 2230 * strings, or null to fall back to the global show/icon settings. 2231 * 2232 * @since 2.35 2233 */ 2234 function resolveSliderOpenWith(sliderId) { 2235 var perSlider = mlLightboxSettings.slider_settings && 2236 sliderId && 2237 mlLightboxSettings.slider_settings[sliderId]; 2238 var mode = perSlider ? perSlider.lightbox_open_with : null; 2239 if (mode === 'image' || mode === 'icon' || mode === 'button') { 2240 return mode; 2241 } 2242 2243 var opts = mlLightboxSettings.metaslider_options || {}; 2244 if (opts.show_lightbox_button === false) { 2245 return 'image'; 2246 } 2247 return opts.use_icon_instead_of_button ? 'icon' : 'button'; 2248 } 2249 2250 function resolveSliderButton(sliderId) { 2251 return resolveSliderOpenWith(sliderId) !== 'image'; 2252 } 2253 2254 function resolveSliderIcon(sliderId) { 2255 return resolveSliderOpenWith(sliderId) === 'icon'; 2256 } 2257 2258 /** 2259 * Initialize MetaSlider in button mode 2260 */ 2261 function initMetaSliderButtonMode($slider, sliderId) { 2262 _metasliderButtonSliderId = sliderId; 2263 try { 2264 var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb'); 2265 var buttonsAdded = 0; 2266 2267 $slides.each(function() { 2268 var $slide = $(this); 2269 var $button = null; 2270 2271 if ($slide.find('.ml-lightbox-button').length > 0) { 2272 return; 2273 } 2274 2275 if ($slide.hasClass('ms-youtube')) { 2276 $button = createYouTubeButton($slide); 2277 } else if ($slide.hasClass('ms-vimeo')) { 2278 $button = createVimeoButton($slide); 2279 } else if ($slide.hasClass('ms-external-video')) { 2280 $button = createExternalVideoButton($slide); 2281 } else if ($slide.hasClass('ms-local-video')) { 2282 $button = createLocalVideoButton($slide); 2283 } else if ($slide.hasClass('ms-postfeed')) { 2284 $button = createPostFeedButton($slide); 2285 } else if ($slide.hasClass('ms-layer')) { 2286 $button = createLayerButton($slide); 2287 } else if ($slide.hasClass('ms-custom-html')) { 2288 $button = createCustomHtmlButton($slide); 2289 } else { 2290 $button = createImageButton($slide); 2291 } 2292 2293 if ($button) { 2294 $slide.css('position', 'relative').append($button); 2295 buttonsAdded++; 2296 } 2297 }); 2298 2299 if (buttonsAdded > 0) {
2300 var settings = getLightboxSettings(buttonsAdded > 1); 2301 settings.selector = '.ml-lightbox-button'; 2302 2303 if (!resolveSliderCaptions(sliderId)) { 2304 settings.subHtml = ''; 2305 settings.getCaptionFromTitleOrAlt = false; 2306 $slider.find('.ml-lightbox-button').removeAttr('data-sub-html'); 2307 } 2308 2309 settings.controls = resolveSliderNavigation(sliderId); 2310 2311 try { 2312 lightGallery($slider[0], settings); 2313 $slider.addClass('lg-initialized'); 2314 } catch (error) { 2315 console.error('MetaSlider Lightbox: Error initializing button mode:', error); 2316 } 2317 } 2318 } finally { 2319 _metasliderButtonSliderId = null; 2320 } 2321 } 2322 2323 /** 2324 * Initialize MetaSlider in direct-click mode 2325 */ 2326 function initMetaSliderDirectMode($slider, sliderId) { 2327 2328 var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb'); 2329 var linksAdded = 0; 2330 2331 $slides.each(function() { 2332 var $slide = $(this); 2333 2334 if ($slide.find('.ml-metaslider-link, .ml-video-overlay').length > 0) return; 2335 2336 var dataAttributes = null; 2337 2338 if ($slide.hasClass('ms-youtube')) { 2339 dataAttributes = getYouTubeOverlayData($slide); 2340 } 2341 else if ($slide.hasClass('ms-vimeo')) { 2342 dataAttributes = getVimeoOverlayData($slide); 2343 } 2344 else if ($slide.hasClass('ms-external-video')) { 2345 dataAttributes = getExternalVideoOverlayData($slide); 2346 } 2347 else if ($slide.hasClass('ms-local-video')) { 2348 dataAttributes = getLocalVideoOverlayData($slide); 2349 } 2350 else if ($slide.hasClass('ms-postfeed')) { 2351 dataAttributes = getPostFeedOverlayData($slide); 2352 } 2353 else if ($slide.hasClass('ms-layer')) { 2354 dataAttributes = getLayerOverlayData($slide); 2355 } 2356 else if ($slide.hasClass('ms-custom-html')) { 2357 dataAttributes = getCustomHtmlOverlayData($slide); 2358 } 2359 else { 2360 dataAttributes = getImageOverlayData($slide); 2361 } 2362 2363 if (dataAttributes) { 2364 if (dataAttributes.overlay) { 2365 var overlayClass = 'ml-video-overlay'; 2366 var contentType = 'video'; 2367 if ($slide.hasClass('ms-image')) { 2368 overlayClass = 'ml-image-overlay'; 2369 contentType = 'image'; 2370 }
2371 var $overlay = $('<a href="#" class="' + overlayClass + '"></a>'); 2372 $overlay.attr(dataAttributes.attrs); 2373 2374 var $img = $slide.find('img').first(); 2375 var altText = $img.length > 0 ? ($img.attr('alt') || '') : ''; 2376 addOverlayAccessibility($overlay, contentType, altText); 2377 2378 $slide.css('position', 'relative').prepend($overlay); 2379 } else { 2380 var $link = $slide.find('a').first(); 2381 if ($link.length > 0) { 2382 $link.attr(dataAttributes.attrs).addClass('ml-metaslider-link'); 2383 } 2384 } 2385 linksAdded++; 2386 } 2387 }); 2388 2389 if (linksAdded > 0) { 2390 var settings = getLightboxSettings(linksAdded > 1); 2391 settings.selector = '.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay'; 2392 2393 if (!resolveSliderCaptions(sliderId)) { 2394 settings.subHtml = ''; 2395 settings.getCaptionFromTitleOrAlt = false; 2396 $slider.find('.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay').removeAttr('data-sub-html'); 2397 } 2398 2399 settings.controls = resolveSliderNavigation(sliderId); 2400 2401 try { 2402 lightGallery($slider[0], settings); 2403 $slider.addClass('lg-initialized'); 2404 } catch (error) { 2405 console.error('MetaSlider Lightbox: Error initializing direct-click mode:', error); 2406 } 2407 } 2408 } 2409 2410 2411 /** 2412 * Check if gallery is already processed 2413 */ 2414 function isGalleryAlreadyProcessed($gallery) { 2415 return $gallery.hasClass(CONSTANTS.CLASSES.ML_LIGHTBOX_ENABLED) || 2416 $gallery.hasClass(CONSTANTS.CLASSES.LG_INITIALIZED); 2417 } 2418 2419 /** 2420 * Extract video ID using various fallback methods 2421 */ 2422 function extractVideoId($slide, handler) { 2423 var $element = $slide.find(handler.selector); 2424 if ($element.length === 0) return null; 2425 2426 var id = $element.attr(handler.idAttr); 2427 2428 if (!id && handler.fallbackSelectors) { 2429 for (var i = 0; i < handler.fallbackSelectors.length; i++) { 2430 var fallbackSelector = handler.fallbackSelectors[i]; 2431 var $fallback = $slide.find(fallbackSelector); 2432 2433 if (fallbackSelector.startsWith('a[href*=')) { 2434 var url = $fallback.attr('href'); 2435 if (url) { 2436 var match = url.match(/vimeo\.com\/(\d+)/); 2437 if (match) { 2438 id = match[1]; 2439 break; 2440 } 2441 } 2442 } else { 2443 id = $fallback.attr(handler.idAttr); 2444 if (id) break; 2445 } 2446 } 2447 } 2448 2449 return id; 2450 } 2451 2452 /** 2453 * Unified video button creation function 2454 */ 2455 function createVideoButton($slide, videoType) { 2456 var handler = CONSTANTS.VIDEO_HANDLERS[videoType]; 2457 if (!handler) return null; 2458 2459 var videoData = null; 2460 2461 if (videoType === 'youtube' || videoType === 'vimeo') { 2462 var videoId = extractVideoId($slide, handler); 2463 if (!videoId) return null; 2464 2465 videoData = { 2466 url: handler.urlTemplate(videoId), 2467 thumb: handler.thumbTemplate(videoId) 2468 }; 2469 } else if (videoType === 'externalVideo' || videoType === 'localVideo') { 2470 var $element = $slide.find(handler.selector); 2471 if ($element.length === 0) return null; 2472 2473 var dataSources = $element.attr(handler.sourcesAttr); 2474 var videoSources = []; 2475 var videoUrl = null; 2476 2477 if (dataSources) { 2478 try { 2479 var sources = JSON.parse(dataSources); 2480 if (sources && sources.length > 0) { 2481 videoUrl = sources[0].src; 2482 videoSources = sources.map(function (source) { 2483 return { 2484 'src': source.src, 2485 'type': source.type || getVideoType(source.src) 2486 }; 2487 }); 2488 } 2489 } catch (e) { 2490 console.error('MetaSlider Lightbox: Error parsing video sources', e); 2491 return null; 2492 } 2493 } 2494 2495 if (!videoUrl || videoSources.length === 0) return null; 2496 2497 videoData = { 2498 url: '', 2499 thumb: $element.attr(handler.posterAttr) || getPlaceholderThumb(), 2500 videoData: { 2501 'source': videoSources, 2502 'attributes': { 2503 'preload': false, 2504 'controls': true 2505 } 2506 } 2507 }; 2508 } 2509 2510 if (!videoData) return null; 2511 2512 var diagnosticClass = 'ml-button-metaslider ml-button-metaslider-' + videoType.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, ''); 2513 var buttonText = getButtonText(); 2514 var $button = $('<a class="' + CONSTANTS.CLASSES.ML_LIGHTBOX_BUTTON + ' ' + diagnosticClass + '" href="#">
2514' + buttonText + '</a>'); 2515 2516 var baseAttrs = { 2517 'role': 'button', 2518 'aria-label': buttonText + ' - ' + videoType + ' video', 2519 'tabindex': '0' 2520 }; 2521 2522 if (videoData.videoData) { 2523 $button.attr($.extend({}, baseAttrs, { 2524 'data-src': '', 2525 'data-video': JSON.stringify(videoData.videoData), 2526 'data-thumb': videoData.thumb 2527 })); 2528 } else { 2529 if (!videoData.url) return null; 2530 $button.attr($.extend({}, baseAttrs, { 2531 'data-src': videoData.url, 2532 'data-thumb': videoData.thumb 2533 })); 2534 } 2535 2536 return $button; 2537 } 2538 2539 /** 2540 * Create lightbox button for regular image slides 2541 */ 2542 function createImageButton($slide) { 2543 var $img = $slide.find('img').first(); 2544 2545 if ($img.length === 0) return null; 2546 2547 var imgSrc = $img.attr('src'); 2548 var fullSizeUrl = removeWordPressSizeSuffix(imgSrc); 2549 2550 var altText = $img.attr('alt') || 'image'; 2551 var buttonText = getButtonText(); 2552 2553 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-image" href="#">' + buttonText + '</a>'); 2554 2555 $button.attr({ 2556 'data-src': fullSizeUrl, 2557 'data-thumb': imgSrc, 2558 'role': 'button', 2559 'aria-label': buttonText + ' - ' + altText, 2560 'tabindex': '0' 2561 }); 2562 2563 var caption = extractCaption($slide); 2564 if (caption) { 2565 $button.attr('data-sub-html', caption); 2566 } 2567 2568 return $button; 2569 } 2570 2571 /** 2572 * Create lightbox button for YouTube slides 2573 */ 2574 function createYouTubeButton($slide) { 2575 return createVideoButton($slide, 'youtube'); 2576 } 2577 2578 /** 2579 * Create lightbox button for Vimeo slides 2580 */ 2581 function createVimeoButton($slide) { 2582 return createVideoButton($slide, 'vimeo'); 2583 } 2584 2585 /** 2586 * Create lightbox button for external video slides 2587 */ 2588 function createExternalVideoButton($slide) { 2589 return createVideoButton($slide, 'externalVideo'); 2590 } 2591 2592 /** 2593 * Create lightbox button for local video slides 2594 */ 2595 function createLocalVideoButton($slide) { 2596 return createVideoButton($slide, 'localVideo'); 2597 } 2598 2599 /** 2600 * Helper function to get video type from URL 2601 */ 2602 function getVideoType(url) { 2603 if (url.includes('.mp4')) return 'video/mp4'; 2604 if (url.includes('.webm')) return 'video/webm'; 2605 if (url.includes('.ogg')) return 'video/ogg'; 2606 return 'video/mp4'; 2607 } 2608 2609 /** 2610 * Helper function to get placeholder thumbnail 2611 */ 2612 function getPlaceholderThumb() { 2613 return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+'; 2614 } 2615 2616 /** 2617 * Create lightbox button for PostFeed slides 2618 */ 2619 function createPostFeedButton($slide) { 2620 var $img = $slide.find('img').first(); 2621 if ($img.length === 0) return null; 2622 2623 var imgSrc = removeWordPressSizeSuffix($img.attr('src')); 2624 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-postfeed" href="#">' + getButtonText() + '</a>'); 2625 2626 $button.attr({ 2627 'data-src': imgSrc, 2628 'data-thumb': imgSrc 2629 }); 2630 2631 var caption = extractCaption($slide); 2632 if (caption) { 2633 $button.attr('data-sub-html', caption); 2634 } 2635 2636 return $button; 2637 } 2638 2639 /** 2640 * Get overlay data for PostFeed slides in direct-click mode 2641 */ 2642 function getPostFeedOverlayData($slide) { 2643 var $img = $slide.find('img').first(); 2644 if ($img.length === 0) return null; 2645 2646 var imgSrc = removeWordPressSizeSuffix($img.attr('src')); 2647 2648 var attrs = { 2649 'data-src': imgSrc,
2650 'data-thumb': imgSrc 2651 }; 2652 2653 var caption = extractCaption($slide); 2654 if (caption) { 2655 attrs['data-sub-html'] = caption; 2656 } 2657 2658 return { 2659 overlay: false, 2660 attrs: attrs 2661 }; 2662 } 2663 2664 /** 2665 * Create lightbox button for Layer slides using iframe data URI approach 2666 */ 2667 function createLayerButton($slide) { 2668 var slideContent = $slide.html(); 2669 if (!slideContent || !slideContent.trim()) return null; 2670 2671 var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) { 2672 var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) { 2673 var numValue = parseFloat(value); 2674 var scaledValue = Math.round(numValue * 2); 2675 return property + ': ' + scaledValue + unit; 2676 }); 2677 return match.replace(styleAttr, scaledStyle); 2678 }); 2679 2680 var $tempDiv = $('<div>').html(slideContent); 2681 var animationClasses = []; 2682 2683 $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() { 2684 var $elem = $(this); 2685 var classes = $elem.attr('class') || ''; 2686 var animationType = $elem.attr('data-animation') || ''; 2687 2688 if (classes) { 2689 animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) { 2690 return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i); 2691 })); 2692 } 2693 2694 if (animationType) { 2695 animationClasses.push(animationType); 2696 } 2697 }); 2698 2699 var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) { 2700 return self.indexOf(value) === index; 2701 }).join(' '); 2702 2703 var scaleCSS = ` 2704 .layer-slide-content { 2705 position: relative !important; 2706 display: inline-block !important; 2707 } 2708 .layer-slide-content .msDefaultImage { 2709 display: block !important; 2710 width: 1400px !important; 2711 height: 600px !important; 2712 max-width: 80% !important; 2713 margin: 0 auto !important; 2714 object-fit: cover !important; 2715 } 2716 .layer-slide-content .msHtmlOverlay { 2717 position: absolute !important; 2718 top: 0 !important; 2719 left: 50% !important; 2720 width: 80% !important; 2721 height: 100% !important; 2722 transform: translateX(-50%) !important; 2723 } 2724 .layer-slide-content .msHtmlOverlay .content { 2725 font-size: 1.8em !important; 2726 line-height: 1.2 !important; 2727 } 2728 `; 2729 2730 var htmlContent = '<div style="display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; box-sizing: border-box; background: #000;"><div class="layer-slide-content ' + uniqueAnimationClasses + '" style="position: relative; display: inline-block; max-width: 100%; max-height: 100vh;">' + scaledSlideContent + '</div></div>'; 2731 var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layer Content</title><style>body{margin:0;padding:0;background:#000;} ' + scaleCSS + '</style><link rel="stylesheet" href="' + window.location.origin + '/wp-content/plugins/metaslider/assets/animate/animate.css" type="text/css" /></head><body>' + htmlContent + '</body></html>'); 2732 2733 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-layer" href="#">' + getButtonText() + '</a>'); 2734 2735 $button.attr({ 2736 'data-src': dataUri, 2737 'data-thumb': getPlaceholderThumb(), 2738 'data-iframe': 'true', 2739 'data-width': '800', 2740 'data-height': '600' 2741 }); 2742 2743 return $button; 2744 } 2745 2746 /** 2747 * Get overlay data for Layer slides in direct-click mode using iframe data URI approach 2748 */ 2749 function getLayerOverlayData($slide) { 2750 var slideContent = $slide.html(); 2751 if (!slideContent || !slideContent.trim()) return null; 2752 2753 var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) { 2754 var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) { 2755 var numValue = parseFloat(value); 2756 var scaledValue = Math.round(numValue * 2); 2757 return property + ': ' + scaledValue + unit; 2758 }); 2759 return match.replace(styleAttr, scaledStyle); 2760 }); 2761
2762 var $tempDiv = $('<div>').html(slideContent); 2763 var animationClasses = []; 2764 2765 $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() { 2766 var $elem = $(this); 2767 var classes = $elem.attr('class') || ''; 2768 var animationType = $elem.attr('data-animation') || ''; 2769 2770 if (classes) { 2771 animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) { 2772 return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i); 2773 })); 2774 } 2775 2776 if (animationType) { 2777 animationClasses.push(animationType); 2778 } 2779 }); 2780 2781 var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) { 2782 return self.indexOf(value) === index; 2783 }).join(' '); 2784 2785 var scaleCSS = ` 2786 .layer-slide-content { 2787 position: relative !important; 2788 display: inline-block !important; 2789 } 2790 .layer-slide-content .msDefaultImage { 2791 display: block !important; 2792 width: 1400px !important; 2793 height: 600px !important; 2794 max-width: 80% !important; 2795 margin: 0 auto !important; 2796 object-fit: cover !important; 2797 } 2798 .layer-slide-content .msHtmlOverlay { 2799 position: absolute !important; 2800 top: 0 !important; 2801 left: 50% !important; 2802 width: 80% !important; 2803 height: 100% !important; 2804 transform: translateX(-50%) !important; 2805 } 2806 .layer-slide-content .msHtmlOverlay .content { 2807 font-size: 1.8em !important; 2808 line-height: 1.2 !important; 2809 } 2810 `; 2811 2812 var htmlContent = '<div style="display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; box-sizing: border-box; background: #000;"><div class="layer-slide-content ' + uniqueAnimationClasses + '" style="position: relative; display: inline-block; max-width: 100%; max-height: 100vh;">' + scaledSlideContent + '</div></div>'; 2813 var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layer Content</title><style>body{margin:0;padding:0;background:#000;} ' + scaleCSS + '</style><link rel="stylesheet" href="' + window.location.origin + '/wp-content/plugins/metaslider/assets/animate/animate.css" type="text/css" /></head><body>' + htmlContent + '</body></html>'); 2814 2815 var attrs = { 2816 'data-src': dataUri, 2817 'data-thumb': getPlaceholderThumb(), 2818 'data-iframe': 'true', 2819 'data-width': '800', 2820 'data-height': '600' 2821 }; 2822 2823 var caption = extractCaption($slide); 2824 if (caption) { 2825 attrs['data-sub-html'] = caption; 2826 } 2827 2828 return { 2829 overlay: true, 2830 attrs: attrs 2831 }; 2832 } 2833 2834 /** 2835 * HTML escape function for safe caption display 2836 */ 2837 2838 /** 2839 * Check if captions should be shown based on settings 2840 */ 2841 function shouldShowCaptions() { 2842 if (typeof mlLightboxSettings !== 'undefined' && 2843 mlLightboxSettings.metaslider_options && 2844 typeof mlLightboxSettings.metaslider_options.show_captions !== 'undefined') { 2845 return mlLightboxSettings.metaslider_options.show_captions === true || mlLightboxSettings.metaslider_options.show_captions === 1; 2846 } 2847 return true; 2848 } 2849 2850 /** 2851 * Extract caption from various sources for an image or container 2852 */ 2853 function extractCaption($element) { 2854 // PHP sets data-sub-html on the slide's <a> for the built-in lightbox. 2855 // Read this before the shouldShowCaptions() guard so it is available when 2856 // global captions are off but a per-slider override enables them. 2857 // resolveSliderCaptions() is the final authority on removal. 2858 var $childLink = $element.find('a').first(); 2859 var childCaption = $childLink.length ? sanitizeCaptionHtml($childLink.attr('data-sub-html') || '') : ''; 2860 2861 if (!shouldShowCaptions()) { 2862 return childCaption; 2863 } 2864 2865 var caption = ''; 2866 var $img = $element.is('img') ? $element : $element.find('img').first(); 2867 2868 var $figcaption = $element.closest('figure').find('figcaption'); 2869 if ($figcaption.length > 0) { 2870 caption = sanitizeCaptionHtml($figcaption.html()); 2871 } 2872 2873 if (!caption) { 2874 var $captionWrap = $element.find('.caption-wrap .caption'); 2875 if ($captionWrap.length === 0) { 2876 $captionWrap = $element.closest('li').find('.caption-wrap .caption'); 2877 } 2878 if ($captionWrap.length === 0) { 2879 $captionWrap = $element.closest('.slide').find('.caption-wrap .caption'); 2880 } 2881 if ($captionWrap.length > 0) { 2882 caption = sanitizeCaptionHtml($captionWrap.html()); 2883 } 2884 } 2885 2886 if (!caption && $img.length > 0) { 2887 caption = escapeHtml($img.attr('alt') || $img.attr('title') || ''); 2888 } 2889 2890 if (!caption) { 2891 caption = sanitizeCaptionHtml($element.attr('data-sub-html') || ''); 2892 } 2893 2894 if (!caption) { 2895 caption = childCaption; 2896 } 2897 2898 return caption.trim(); 2899 } 2900 2901 2902 /** 2903 * Get overlay data for regular images in direct-click mode 2904 */ 2905 function getImageOverlayData($slide) { 2906 var $img = $slide.find('img').first(); 2907 2908 if ($img.length === 0) return null; 2909 if (slideHasCustomLink($slide)) return null; 2910 2911 var imgSrc = $img.attr('src'); 2912 var fullSizeUrl = removeWordPressSizeSuffix(imgSrc); 2913 var needsOverlay = (getSlideImageLink($slide).length === 0); 2914 2915 var attrs = { 2916 'data-src': fullSizeUrl,
2917 'data-thumb': imgSrc 2918 }; 2919 2920 var caption = extractCaption($slide); 2921 if (caption) { 2922 attrs['data-sub-html'] = caption; 2923 } 2924 2925 return { 2926 overlay: needsOverlay, 2927 attrs: attrs 2928 }; 2929 } 2930 2931 /** 2932 * Get overlay data for YouTube videos in direct-click mode 2933 */ 2934 function getYouTubeOverlayData($slide) { 2935 var $youtubeDiv = $slide.find('div.youtube[data-id]'); 2936 if ($youtubeDiv.length === 0) return null; 2937 2938 var youtubeId = $youtubeDiv.attr('data-id'); 2939 if (!youtubeId) return null; 2940 2941 var youtubeUrl = 'https://www.youtube.com/watch?v=' + youtubeId; 2942 var thumbUrl = 'https://img.youtube.com/vi/' + youtubeId + '/maxresdefault.jpg'; 2943 2944 var attrs = { 2945 'data-src': youtubeUrl, 2946 'data-thumb': thumbUrl 2947 }; 2948 2949 var caption = extractCaption($slide); 2950 if (caption) { 2951 attrs['data-sub-html'] = caption; 2952 } 2953 2954 return { 2955 overlay: true, 2956 attrs: attrs 2957 }; 2958 } 2959 2960 /** 2961 * Get overlay data for Vimeo videos in direct-click mode 2962 */ 2963 function getVimeoOverlayData($slide) { 2964 var $vimeoDiv = $slide.find('div.vimeo[data-id]'); 2965 if ($vimeoDiv.length === 0) return null; 2966 2967 var vimeoId = $vimeoDiv.attr('data-id'); 2968 2969 if (!vimeoId) { 2970 vimeoId = $vimeoDiv.attr('data-slide-id'); 2971 } 2972 if (!vimeoId) { 2973 var $existingLink = $slide.find('a[href*="vimeo.com"]').first(); 2974 if ($existingLink.length > 0) { 2975 var vimeoUrl = $existingLink.attr('href'); 2976 var vimeoMatch = vimeoUrl.match(/vimeo\.com\/(\d+)/); 2977 if (vimeoMatch) { 2978 vimeoId = vimeoMatch[1]; 2979 } 2980 } 2981 } 2982 2983 if (!vimeoId) return null; 2984 2985 var vimeoUrl = 'https://vimeo.com/' + vimeoId; 2986 var vimeoPoster = 'https://vumbnail.com/' + vimeoId + '.jpg'; 2987 2988 var attrs = { 2989 'data-src': vimeoUrl, 2990 'data-thumb': vimeoPoster 2991 }; 2992 2993 var caption = extractCaption($slide); 2994 if (caption) { 2995 attrs['data-sub-html'] = caption; 2996 } 2997 2998 return { 2999 overlay: true, 3000 attrs: attrs 3001 }; 3002 } 3003 3004 /** 3005 * Get overlay data for external videos in direct-click mode 3006 */ 3007 function getExternalVideoOverlayData($slide) { 3008 var $videoDiv = $slide.find('div.external-video'); 3009 if ($videoDiv.length === 0) return null; 3010 3011 var dataSources = $videoDiv.attr('data-sources'); 3012 var videoSources = []; 3013 var videoUrl = null; 3014 3015 if (dataSources) { 3016 try { 3017 var sources = JSON.parse(dataSources); 3018 if (sources && sources.length > 0) { 3019 videoUrl = sources[0].src; 3020 videoSources = sources.map(function (source) { 3021 return { 3022 'src': source.src, 3023 'type': source.type || getVideoType(source.src) 3024 }; 3025 }); 3026 } 3027 } catch (e) { 3028 console.error('MetaSlider Lightbox: Error parsing video sources', e); 3029 return null; 3030 } 3031 } 3032 3033 if (!videoUrl || videoSources.length === 0) return null; 3034 3035 var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb(); 3036 var videoData = { 3037 'source': videoSources, 3038 'attributes': { 3039 'preload': false, 3040 'controls': true 3041 } 3042 }; 3043 3044 var attrs = { 3045 'data-src': '', 3046 'data-video': JSON.stringify(videoData), 3047 'data-thumb': posterUrl 3048 }; 3049 3050 var caption = extractCaption($slide); 3051 if (caption) { 3052 attrs['data-sub-html'] = caption; 3053 } 3054 3055 return { 3056 overlay: true, 3057 attrs: attrs 3058 }; 3059 } 3060 3061 /** 3062 * Get overlay data for local videos in direct-click mode 3063 */ 3064 function getLocalVideoOverlayData($slide) { 3065 var $videoDiv = $slide.find('div.local-video'); 3066 if ($videoDiv.length === 0) return null; 3067 3068 var dataSources = $videoDiv.attr('data-sources'); 3069 var videoSources = []; 3070 var videoUrl = null; 3071 3072 if (dataSources) { 3073 try { 3074 var sources = JSON.parse(dataSources); 3075 if (sources && sources.length > 0) { 3076 videoUrl = sources[0].src; 3077 videoSources = sources.map(function (source) { 3078 return { 3079 'src': source.src, 3080 'type': source.type || getVideoType(source.src) 3081 }; 3082 }); 3083 } 3084 } catch (e) { 3085 console.error('MetaSlider Lightbox: Error parsing video sources', e); 3086 return null; 3087 } 3088 } 3089 3090 if (!videoUrl || videoSources.length === 0) return null; 3091 3092 var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb(); 3093 var videoData = { 3094 'source': videoSources, 3095 'attributes': { 3096 'preload': false, 3097 'controls': true 3098 } 3099 }; 3100 3101 var attrs = { 3102 'data-src': '',
3103 'data-video': JSON.stringify(videoData), 3104 'data-thumb': posterUrl 3105 }; 3106 3107 var caption = extractCaption($slide); 3108 if (caption) { 3109 attrs['data-sub-html'] = caption; 3110 } 3111 3112 return { 3113 overlay: true, 3114 attrs: attrs 3115 }; 3116 } 3117 3118 /** 3119 * Create lightbox button for Custom HTML slides using iframe data URI approach (similar to Layer slides) 3120 */ 3121 function createCustomHtmlButton($slide) { 3122 var slideContent = $slide.html(); 3123 if (!slideContent || !slideContent.trim()) return null; 3124 3125 var responsiveCSS = ` 3126 .custom-html-content { 3127 width: 100% !important; 3128 max-width: 100% !important; 3129 height: 100vh !important; 3130 display: flex !important; 3131 align-items: center !important; 3132 justify-content: center !important; 3133 padding: 20px !important; 3134 box-sizing: border-box !important; 3135 background: #000 !important; 3136 color: white !important; 3137 font-family: Arial, sans-serif !important; 3138 overflow: auto !important; 3139 } 3140 .custom-html-content > div { 3141 max-width: 100% !important; 3142 max-height: 100% !important; 3143 } 3144 @media (max-width: 768px) { 3145 .custom-html-content { 3146 padding: 10px !important; 3147 } 3148 } 3149 `; 3150 3151 var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>'; 3152 var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Custom HTML</title><style>body{margin:0;padding:0;} ' + responsiveCSS + '</style></head><body>' + htmlContent + '</body></html>'); 3153 3154 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-custom-html" href="#">' + getButtonText() + '</a>'); 3155 3156 $button.attr({ 3157 'data-src': dataUri, 3158 'data-thumb': getPlaceholderThumb(), 3159 'data-iframe': 'true', 3160 'data-width': '90%', 3161 'data-height': '90%' 3162 }); 3163 3164 return $button; 3165 } 3166 3167 /** 3168 * Get overlay data for Custom HTML slides in direct-click mode using iframe data URI approach 3169 */ 3170 function getCustomHtmlOverlayData($slide) { 3171 var slideContent = $slide.html(); 3172 if (!slideContent || !slideContent.trim()) return null; 3173 3174 var responsiveCSS = ` 3175 .custom-html-content { 3176 width: 100% !important; 3177 max-width: 100% !important; 3178 height: 100vh !important; 3179 display: flex !important; 3180 align-items: center !important; 3181 justify-content: center !important; 3182 padding: 20px !important; 3183 box-sizing: border-box !important; 3184 background: #000 !important; 3185 color: white !important; 3186 font-family: Arial, sans-serif !important; 3187 overflow: auto !important; 3188 } 3189 .custom-html-content > div { 3190 max-width: 100% !important; 3191 max-height: 100% !important; 3192 } 3193 @media (max-width: 768px) { 3194 .custom-html-content { 3195 padding: 10px !important; 3196 } 3197 } 3198 `; 3199 3200 var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>'; 3201 var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Custom HTML</title><style>body{margin:0;padding:0;} ' + responsiveCSS + '</style></head><body>' + htmlContent + '</body></html>'); 3202 3203 var attrs = { 3204 'data-src': dataUri, 3205 'data-thumb': getPlaceholderThumb(), 3206 'data-iframe': 'true', 3207 'data-width': '90%', 3208 'data-height': '90%' 3209 }; 3210 3211 var caption = extractCaption($slide); 3212 if (caption) { 3213 attrs['data-sub-html'] = caption; 3214 } 3215 3216 return { 3217 overlay: true, 3218 attrs: attrs 3219 }; 3220 } 3221 3222 /** 3223 * Initialize "Enlarge on Click" gallery - similar to initPlainImageGallery but for button-based galleries 3224 */ 3225 function initEnlargeOnClickGallery($gallery, useButtonMode) { 3226 var $wpContainers = $gallery.find('.wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger'); 3227 3228 if ($wpContainers.length <= 1) return; 3229 3230 if (useButtonMode) { 3231 $wpContainers.each(function() { 3232 var $container = $(this); 3233 var $img = $container.find('img').first(); 3234 var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 3235 3236 if ($img.length && $wpButton.length) { 3237 var imgSrc = $img.attr('src'); 3238 if (!imgSrc) return; 3239 3240 var fullUrl = removeWordPressSizeSuffix(imgSrc); 3241 3242 $container.attr({ 3243 'data-src': fullUrl,
3244 'data-thumb': imgSrc 3245 }); 3246 3247 var caption = extractCaption($img); 3248 if (caption) { 3249 $container.attr('data-sub-html', caption); 3250 } 3251 3252 var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-override" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>'); 3253 if (caption) { 3254 $button.attr('data-sub-html', caption); 3255 } 3256 3257 $wpButton.remove(); 3258 $container.css('position', 'relative').append($button); 3259 } 3260 }); 3261 3262 var gallerySettings = getLightboxSettings(true); 3263 gallerySettings.selector = '.ml-lightbox-button'; 3264 3265 } else { 3266 $wpContainers.each(function() { 3267 var $container = $(this); 3268 var $img = $container.find('img').first(); 3269 var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 3270 3271 if ($img.length && $wpButton.length) { 3272 var imgSrc = $img.attr('src'); 3273 if (!imgSrc) return; 3274 3275 var fullUrl = removeWordPressSizeSuffix(imgSrc); 3276 3277 var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>'); 3278 3279 var caption = extractCaption($img); 3280 if (caption) { 3281 $wrapper.attr('data-sub-html', caption); 3282 } 3283 3284 $img.wrap($wrapper); 3285 $wpButton.remove(); 3286 } 3287 }); 3288 3289 var gallerySettings = getLightboxSettings(true); 3290 gallerySettings.selector = 'a[data-src]'; 3291 } 3292 3293 try { 3294 var instance = lightGallery($gallery[0], gallerySettings); 3295 $gallery.addClass('lg-initialized'); 3296 } catch (error) { 3297 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click gallery:', error); 3298 } 3299 } 3300 3301 /** 3302 * Initialize "Link to Media File" gallery when manual override is enabled - similar to automatic processing but runs independently 3303 */ 3304 function initLinkToMediaManualGallery($gallery, useButtonMode) { 3305 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 3306 return $(this).find('img').length > 0; 3307 }); 3308 3309 if ($mediaLinks.length <= 1) return; 3310 3311 if (useButtonMode) { 3312 $mediaLinks.each(function() { 3313 var $link = $(this); 3314 var href = $link.attr('href'); 3315 var $img = $link.find('img').first(); 3316 var imgSrc = $img.attr('src'); 3317 3318 if (!href || !imgSrc) return; 3319 3320 var $container = $link.closest('.wp-block-image'); 3321 if ($container.length === 0) { 3322 $container = $link.parent(); 3323 } 3324 3325 $container.attr({ 3326 'data-src': href, 3327 'data-thumb': imgSrc 3328 }); 3329 3330 var caption = extractCaption($img); 3331 if (caption) { 3332 $container.attr('data-sub-html', caption); 3333 } 3334 3335 var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media" data-src="' + href + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>'); 3336 if (caption) { 3337 $button.attr('data-sub-html', caption); 3338 } 3339 3340 $container.css('position', 'relative').append($button); 3341 }); 3342 3343 var gallerySettings = getLightboxSettings(true); 3344 gallerySettings.selector = '.ml-lightbox-button'; 3345 3346 } else { 3347 $mediaLinks.each(function() { 3348 var $link = $(this); 3349 var href = $link.attr('href'); 3350 var $img = $link.find('img').first(); 3351 var imgSrc = $img.attr('src'); 3352 3353 if (!href || !imgSrc) return; 3354 3355 $link.attr({ 3356 'data-src': href,
3357 'data-thumb': imgSrc 3358 }); 3359 3360 var caption = extractCaption($img); 3361 if (caption) { 3362 $link.attr('data-sub-html', caption); 3363 } 3364 }); 3365 3366 var gallerySettings = getLightboxSettings(true); 3367 gallerySettings.selector = 'a[data-src]'; 3368 } 3369 3370 try { 3371 var instance = lightGallery($gallery[0], gallerySettings); 3372 $gallery.addClass('lg-initialized'); 3373 } catch (error) { 3374 console.error('MetaSlider Lightbox: Error initializing Link to Media File manual gallery:', error); 3375 } 3376 } 3377 3378 function initWooCommerceGallery() { 3379 var $gallery = $('.woocommerce-product-gallery'); 3380 3381 if ($gallery.length === 0) { 3382 return; 3383 } 3384 3385 var useButtonMode = false; 3386 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.metaslider_options) { 3387 useButtonMode = mlLightboxSettings.metaslider_options.show_lightbox_button !== false; 3388 } 3389 3390 var $images = $gallery.find('.woocommerce-product-gallery__image'); 3391 3392 if ($images.length === 0) { 3393 return; 3394 } 3395 3396 if (!useButtonMode) { 3397 $gallery.off('.wc-product-gallery-zoom'); 3398 $gallery.find('img').off('.wc-product-gallery-zoom'); 3399 $gallery.find('a').off('.wc-product-gallery-zoom'); 3400 3401 $images.each(function() { 3402 var $imageWrapper = $(this); 3403 var $img = $imageWrapper.find('img').first(); 3404 var $link = $imageWrapper.find('a').first(); 3405 3406 $img.css('pointer-events', 'none'); 3407 $link.css('cursor', 'pointer'); 3408 }); 3409 3410 $gallery.find('.zoomImg').remove(); 3411 3412 $images.find('a.ml-woo-product-image').each(function() { 3413 var $link = $(this); 3414 if ($link.hasClass('lg-initialized')) { 3415 var lgInstance = $link.data('lightGallery'); 3416 if (lgInstance) { 3417 lgInstance.destroy(true); 3418 } 3419 $link.removeClass('lg-initialized').removeAttr('data-lg-id'); 3420 } 3421 }); 3422 } 3423 3424 if ($gallery.hasClass('lg-initialized')) { 3425 var galleryInstance = $gallery.data('lightGallery'); 3426 if (galleryInstance) { 3427 galleryInstance.destroy(true); 3428 } 3429 $gallery.removeClass('lg-initialized'); 3430 } 3431 3432 $images.each(function() { 3433 var $imageWrapper = $(this); 3434 var $img = $imageWrapper.find('img').first(); 3435 var $link = $imageWrapper.find('a').first(); 3436 3437 if ($imageWrapper.find('.ml-lightbox-button').length > 0 && useButtonMode) { 3438 return; 3439 } 3440 3441 if (useButtonMode) { 3442 var fullUrl = $img.attr('data-full-url'); 3443 var thumbUrl = $img.attr('data-thumb-url'); 3444 3445 if (!fullUrl || !thumbUrl) { 3446 return; 3447 } 3448 3449 var $button = $('<a href="#" class="ml-lightbox-button ml-button-woo">' + getButtonText() + '</a>'); 3450 $button.attr({ 3451 'data-src': fullUrl, 3452 'data-thumb': thumbUrl 3453 }); 3454 3455 $imageWrapper.css('position', 'relative').append($button); 3456 } 3457 }); 3458 3459 var gallerySettings = getLightboxSettings(true); 3460 3461 if (useButtonMode) { 3462 gallerySettings.selector = '.ml-lightbox-button'; 3463 } else { 3464 gallerySettings.selector = '.ml-woo-product-image'; 3465 } 3466 3467 gallerySettings.galleryId = 'ml-woo-gallery-' + Date.now(); 3468 3469 try { 3470 var instance = lightGallery($gallery[0], gallerySettings); 3471 $gallery.addClass('lg-initialized'); 3472 3473 $gallery.on('found_variation', function(event, variation) { 3474 if (instance && typeof instance.refresh === 'function') { 3475 setTimeout(function() { 3476 instance.refresh(); 3477 }, 100); 3478 } 3479 }); 3480 3481 $gallery.on('reset_data', function() { 3482 if (instance && typeof instance.refresh === 'function') { 3483 setTimeout(function() { 3484 instance.refresh(); 3485 }, 100); 3486 } 3487 }); 3488 3489 } catch (error) { 3490 console.error('MetaSlider Lightbox: Error initializing WooCommerce gallery:', error); 3491 } 3492 } 3493 3494 window.mlInitWooCommerceGallery = initWooCommerceGallery; 3495 3496 // Exposed for unit tests only â not a public API. 3497 window.__mlLightboxTestable = { 3498 CONSTANTS: CONSTANTS, 3499 escapeHtml: escapeHtml, 3500 ensureImageAltAttribute: ensureImageAltAttribute, 3501 removeConflictingAttributes: removeConflictingAttributes, 3502 slideHasCustomLink: slideHasCustomLink, 3503 getImageOverlayData: getImageOverlayData, 3504 createImageButton: createImageButton 3505 }; 3506 3507})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.