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