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