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 var inlineLayoutPlugins = { 730 carousel: [] 731 }; 732 733 var $mlInlineFsGallery = null; 734 $(document).off('.mlInlineFs').on( 735 'fullscreenchange.mlInlineFs webkitfullscreenchange.mlInlineFs mozfullscreenchange.mlInlineFs MSFullscreenChange.mlInlineFs', 736 function() { 737 if (!$mlInlineFsGallery) { return; } 738 var inFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || 739 document.mozFullScreenElement || document.msFullscreenElement); 740 var $gallery = $mlInlineFsGallery; 741 $gallery.find('.lg-container').first().toggleClass('lg-inline', !inFullscreen); 742 var inst = $gallery[0]._mlLgInstance; 743 if (inst && typeof inst.refreshOnResize === 'function') { inst.refreshOnResize(); } 744 if (!inFullscreen) { $mlInlineFsGallery = null; } 745 } 746 ); 747 748 $('.ml-gallery-container[data-ml-gallery]').filter(function() { 749 return !$(this).hasClass('lg-initialized') && $(this).data('ml-lightbox') !== 0; 750 }).each(function() { 751 var $container = $(this); 752 753 var mlLayout = $container.data('ml-layout'); 754 755 if ( inlineLayoutPlugins.hasOwnProperty( mlLayout ) ) { 756 var dynamicEl = []; 757 $container.find('a[data-src]').each(function () { 758 var $a = $(this); 759 var el = { src: $a.attr('data-src') }; 760 if ($a.attr('data-thumb')) { el.thumb = $a.attr('data-thumb'); } 761 if ($a.attr('data-sub-html')) { el.subHtml = wrapMlCaptionText($a.attr('data-sub-html')); } 762 dynamicEl.push(el); 763 }); 764 765 if (dynamicEl.length) { 766 var inlineSettings = applyGallerySettings({ 767 container : $container[0], 768 dynamic : true, 769 dynamicEl : dynamicEl, 770 plugins : inlineLayoutPlugins[ mlLayout ], 771 hash : false, 772 closable : false, 773 showMaximizeIcon: true, 774 appendSubHtmlTo : '.lg-item', 775 }, $container); 776 777 try { 778 var inlineGallery = lightGallery($container[0], inlineSettings); 779 inlineGallery.openGallery(); 780 $container[0]._mlLgInstance = inlineGallery; 781 $container.addClass('lg-initialized'); 782 783 $container.on('click.mlInlineFs', '.lg-fullscreen', function() { 784 $mlInlineFsGallery = $container; 785 }); 786 } catch (error) { 787 console.error('MetaSlider Lightbox: inline gallery init error:', error); 788 } 789 } 790 return; 791 } 792
793 var settings = getLightboxSettings(true); 794 applyGallerySettings(settings, $container); 795 796 var perGalleryButton = $container.attr('data-ml-show-button') === '1'; 797 798 if (perGalleryButton) { 799 var perIcon = $container.attr('data-ml-button-icon') === '1'; 800 var perText = $container.attr('data-ml-button-text') || ''; 801 802 $container.find('a[data-src]').each(function() { 803 var $a = $(this); 804 var dataSrc = $a.attr('data-src'); 805 var $img = $a.find('img').first(); 806 var imgSrc = $img.attr('src') || dataSrc; 807 var alt = $img.attr('alt') || ''; 808 809 var btnLabel; 810 if (perIcon) { 811 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>'; 812 } else { 813 btnLabel = perText || 'Open in Gallery'; 814 } 815 816 var $btn = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-gallery" href="#">' + btnLabel + '</a>'); 817 $btn.attr({ 818 'data-src' : dataSrc, 819 'data-thumb': $a.attr('data-thumb') || imgSrc, 820 'aria-label': mlLightboxSettings.view_image_label + (alt ? ': ' + alt : ''), 821 }); 822 // The button becomes lightGallery's slide source (selector below), 823 // so carry the caption over or it would be dropped. 824 var subHtml = $a.attr('data-sub-html'); 825 if (subHtml) { 826 $btn.attr('data-sub-html', subHtml); 827 } 828 $a.css('position', 'relative').append($btn); 829 830 // Button mode: only the button should open the lightbox. 831 // Strip the wrapper link's own attributes so clicking the 832 // image itself doesn't navigate to the image URL. 833 $a.removeAttr('href').removeAttr('data-src').removeAttr('data-thumb'); 834 }); 835 settings.selector = '.ml-lightbox-button'; 836 } else { 837 settings.selector = 'a[data-src]'; 838 } 839 840 // Wrap caption text so the caption transition animates the text, 841 // not the bar. Only when a transition is active. 842 var galleryCaptionTransition = $container.data('lg-caption-transition'); 843 if (galleryCaptionTransition && galleryCaptionTransition !== 'none') { 844 $container.find('a[data-sub-html]').each(function() { 845 var $a = $(this); 846 $a.attr('data-sub-html', wrapMlCaptionText($a.attr('data-sub-html'))); 847 }); 848 } 849 850 try { 851 $container[0]._mlLgInstance = lightGallery($container[0], settings); 852 $container.addClass('lg-initialized'); 853 } catch (error) { 854 console.error('MetaSlider Lightbox: Error initializing gallery shortcode:', error); 855 } 856 }); 857 } 858 } 859 860 function initLightboxEnabled($container, useButtonMode) { 861 useButtonMode = useButtonMode || false; 862 863 if ($container.hasClass('lg-initialized') || $container.data('lightgallery')) { 864 return; 865 } 866 var $imageLinks = $container.find('a[data-src], a[data-video]'); 867 868 if ($imageLinks.length === 0) { 869 var $images = $container.find('img'); 870 871 if ($images.length > 1) { 872 $images.each(function() { 873 var $img = $(this); 874 var $figure = $img.closest('figure, div'); 875 if ($figure.length === 0) $figure = $img.parent(); 876 877 if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) { 878 return; 879 } 880 881 var src = $img.attr('src'); 882 if (!src) return; 883 884 var fullUrl = removeWordPressSizeSuffix(src); 885 886 if (useButtonMode) { 887 var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>'); 888 $button.attr({ 889 'data-src': fullUrl,
890 'data-thumb': src 891 }); 892 $figure.css('position', 'relative').append($button); 893 } else { 894 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 895 $overlay.attr({ 896 'data-src': fullUrl, 897 'data-thumb': src 898 }); 899 900 // Add accessibility attributes 901 var altText = $img.attr('alt') || ''; 902 addOverlayAccessibility($overlay, 'image', altText); 903 904 $figure.css('position', 'relative').prepend($overlay); 905 } 906 }); 907 908 var gallerySettings = getLightboxSettings(true); 909 gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay'; 910 911 try { 912 var instance = lightGallery($container[0], gallerySettings); 913 $container.addClass('lg-initialized'); 914 } catch (error) { 915 console.error('MetaSlider Lightbox: Error initializing manual gallery:', error); 916 } 917 return; 918 } 919 920 if (useButtonMode) { 921 handleSingleElementWithButton($container); 922 } else { 923 handleSingleElement($container); 924 } 925 return; 926 } 927 928 var isGallery = $imageLinks.length > 1; 929 930 931 var settings = getLightboxSettings(isGallery); 932 933 try { 934 var instance = lightGallery($container[0], settings); 935 $container.addClass('lg-initialized'); 936 937 } catch (error) { 938 console.error('MetaSlider Lightbox: Error initializing LightGallery:', error); 939 } 940 } 941 942 /** 943 * Handle single elements that don't have PHP-processed data-src links 944 */ 945 function handleSingleElement($element) { 946 var $target = null; 947 var dataSrc = ''; 948 var dataThumb = ''; 949 var isVideo = false; 950 951 if ($element.is('img')) { 952 var src = $element.attr('src'); 953 if (!src) return; 954 955 var $wrapper = $('<div class="ml-lightbox-wrapper"></div>'); 956 $element.wrap($wrapper); 957 $target = $element.parent(); 958 959 var fullUrl = removeWordPressSizeSuffix(src); 960 961 $target.attr({ 962 'data-src': fullUrl, 963 'data-thumb': src 964 }); 965 966 var caption = extractCaption($element); 967 if (caption) { 968 $target.attr('data-sub-html', caption); 969 } 970 971 972 } else if ($element.is('a')) { 973 var href = $element.attr('href'); 974 if (!href) return; 975 976 $target = $element; 977 var $img = $element.find('img').first(); 978 var imgSrc = $img.length ? $img.attr('src') : href; 979 980 isVideo = isVideoUrl(href); 981 982 if (isVideo) { 983 var videoUrl = getVideoUrl(href); 984 $target.attr({ 985 'data-src': videoUrl, 986 'data-thumb': imgSrc || getVideoThumbnail(href) 987 }); 988 } else { 989 $target.attr({ 990 'data-src': href, 991 'data-thumb': imgSrc 992 }); 993 } 994 995 var caption = extractCaption($element); 996 if (caption) { 997 $target.attr('data-sub-html', caption); 998 } 999 1000 } else { 1001 1002 var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first(); 1003 var $video = $element.find('video').first(); 1004 1005 if ($iframe.length > 0) { 1006 var iframeSrc = $iframe.attr('src'); 1007
1008 var $overlay = $('<a href="#" class="ml-video-overlay"></a>'); 1009 1010 var videoUrl = getVideoUrl(iframeSrc); 1011 $overlay.attr({ 1012 'data-src': videoUrl, 1013 'data-thumb': getVideoThumbnail(iframeSrc) 1014 }); 1015 1016 var caption = extractCaption($element); 1017 if (caption) { 1018 $overlay.attr('data-sub-html', caption); 1019 } 1020 1021 $element.css('position', 'relative').prepend($overlay); 1022 $target = $element; 1023 1024 isVideo = true; 1025 1026 } else if ($video.length > 0) { 1027 var videoSrc = $video.attr('src') || $video.find('source').first().attr('src'); 1028 if (!videoSrc) return; 1029 1030 var videoData = { 1031 source: [{ src: videoSrc, type: getVideoType(videoSrc) }], 1032 attributes: { preload: false, controls: true } 1033 }; 1034 1035 var $overlay = $('<a href="#" class="ml-video-overlay"></a>'); 1036 1037 $overlay.attr({ 1038 'data-src': '', 1039 'data-video': JSON.stringify(videoData), 1040 'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+' 1041 }); 1042 1043 var caption = extractCaption($element); 1044 if (caption) { 1045 $overlay.attr('data-sub-html', caption); 1046 } 1047 1048 $element.css('position', 'relative').prepend($overlay); 1049 $target = $element; 1050 1051 isVideo = true; 1052 1053 } else { 1054 var $img = $element.find('img').first(); 1055 if (!$img.length) return; 1056 1057 var src = $img.attr('src'); 1058 if (!src) return; 1059 1060 var fullUrl = removeWordPressSizeSuffix(src); 1061 1062 if ($element.hasClass('wp-lightbox-container')) {
1063 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1064 1065 $overlay.attr({ 1066 'data-src': fullUrl, 1067 'data-thumb': src 1068 }); 1069 1070 var caption = extractCaption($element); 1071 if (caption) { 1072 $overlay.attr('data-sub-html', caption); 1073 } 1074 1075 // Add accessibility attributes 1076 var altText = $img.attr('alt') || ''; 1077 addOverlayAccessibility($overlay, 'image', altText); 1078 1079 $element.css('position', 'relative').prepend($overlay); 1080 $target = $element; 1081 1082 } else { 1083 $target = $element; 1084 $target.attr({ 1085 'data-src': fullUrl, 1086 'data-thumb': src 1087 }); 1088 1089 var caption = extractCaption($element); 1090 if (caption) { 1091 $target.attr('data-sub-html', caption); 1092 } 1093 1094 } 1095 } 1096 } 1097 1098 if (!$target) return; 1099 1100 var settings = getLightboxSettings(false); 1101 1102 if (isVideo && $target.find('.ml-video-overlay').length > 0) { 1103 settings.selector = '.ml-video-overlay'; 1104 } else if (!isVideo && $target.find('.ml-image-overlay').length > 0) { 1105 settings.selector = '.ml-image-overlay'; 1106 } else { 1107 settings.selector = 'this'; 1108 } 1109 1110 try { 1111 var instance = lightGallery($target[0], settings); 1112 $target.addClass('lg-initialized'); 1113 1114 } catch (error) { 1115 console.error('MetaSlider Lightbox: Error initializing single lightbox:', error); 1116 } 1117 } 1118 1119 /** 1120 * Handle single elements that need button mode instead of overlay mode 1121 */ 1122 function handleSingleElementWithButton($element) { 1123 if ($element.hasClass('lg-initialized') || $element.data('lightgallery')) { 1124 return; 1125 } 1126 1127 var $button = null; 1128 1129 if ($element.is('img')) { 1130 var src = $element.attr('src'); 1131 if (!src) return; 1132 1133 var fullUrl = removeWordPressSizeSuffix(src); 1134 1135 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-img" href="#">' + getButtonText() + '</a>'); 1136 1137 $button.attr({ 1138 'data-src': fullUrl, 1139 'data-thumb': src 1140 }); 1141 1142 var caption = extractCaption($element); 1143 if (caption) { 1144 $button.attr('data-sub-html', caption); 1145 } 1146 1147 $element.wrap('<div style="position: relative; display: inline-block;"></div>'); 1148 $element.parent().append($button); 1149 1150 var settings = getLightboxSettings(false); 1151 settings.selector = '.ml-lightbox-button'; 1152 1153 try { 1154 var instance = lightGallery($element.parent()[0], settings); 1155 $element.parent().addClass('lg-initialized'); 1156 } catch (error) { 1157 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1158 } 1159 1160 } else if ($element.is('a')) { 1161 var href = $element.attr('href'); 1162 if (!href) return; 1163 1164 var $img = $element.find('img').first(); 1165 var imgSrc = $img.length > 0 ? $img.attr('src') : ''; 1166 1167 var isVideo = isVideoUrl(href); 1168 1169 if (isVideo) { 1170 var videoUrl = getVideoUrl(href); 1171 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-video" href="#">' + getButtonText() + '</a>'); 1172 1173 $button.attr({ 1174 'data-src': videoUrl, 1175 'data-thumb': imgSrc || getVideoThumbnail(href) 1176 }); 1177 } else { 1178 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-link" href="#">' + getButtonText() + '</a>'); 1179 1180 $button.attr({ 1181 'data-src': href,
1182 'data-thumb': imgSrc 1183 }); 1184 } 1185 1186 var caption = extractCaption($element); 1187 if (caption) { 1188 $button.attr('data-sub-html', caption); 1189 } 1190 1191 $element.css('position', 'relative').append($button); 1192 1193 var settings = getLightboxSettings(false); 1194 settings.selector = '.ml-lightbox-button'; 1195 1196 try { 1197 var instance = lightGallery($element[0], settings); 1198 $element.addClass('lg-initialized'); 1199 } catch (error) { 1200 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1201 } 1202 1203 } else { 1204 1205 var $existingButton = $element.find('.ml-lightbox-button').first(); 1206 if ($existingButton.length > 0) { 1207 var settings = getLightboxSettings(false); 1208 settings.selector = '.ml-lightbox-button'; 1209 1210 try { 1211 var instance = lightGallery($element[0], settings); 1212 $element.addClass('lg-initialized'); 1213 } catch (error) { 1214 console.error('MetaSlider Lightbox: Error initializing existing button:', error); 1215 } 1216 return; 1217 } 1218 1219 var $img = $element.find('img').first(); 1220 var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first(); 1221 var $video = $element.find('video').first(); 1222 1223 if ($img.length > 0) { 1224 var src = $img.attr('src'); 1225 if (src) { 1226 var fullUrl = removeWordPressSizeSuffix(src); 1227 1228 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-img" href="#">' + getButtonText() + '</a>'); 1229 1230 $button.attr({ 1231 'data-src': fullUrl, 1232 'data-thumb': src 1233 }); 1234 } 1235 } else if ($iframe.length > 0) { 1236 var iframeSrc = $iframe.attr('src'); 1237 if (iframeSrc) { 1238 var videoUrl = getVideoUrl(iframeSrc); 1239 1240 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-iframe" href="#">' + getButtonText() + '</a>'); 1241 1242 $button.attr({ 1243 'data-src': videoUrl, 1244 'data-thumb': getVideoThumbnail(iframeSrc) 1245 }); 1246 } 1247 } else if ($video.length > 0) { 1248 var videoSrc = $video.attr('src') || $video.find('source').first().attr('src'); 1249 if (videoSrc) { 1250 $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-video" href="#">' + getButtonText() + '</a>'); 1251 1252 var videoData = { 1253 source: [{ 1254 src: videoSrc, 1255 type: getVideoType(videoSrc) 1256 }], 1257 attributes: { 1258 preload: false, 1259 controls: true 1260 } 1261 }; 1262 1263 $button.attr({ 1264 'data-src': '', 1265 'data-video': JSON.stringify(videoData), 1266 'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+' 1267 }); 1268 } 1269 } 1270 1271 if ($button) { 1272 var caption = extractCaption($element); 1273 if (caption) { 1274 $button.attr('data-sub-html', caption); 1275 } 1276 1277 $element.css('position', 'relative').append($button); 1278
1279 var settings = getLightboxSettings(false); 1280 settings.selector = '.ml-lightbox-button'; 1281 1282 try { 1283 var instance = lightGallery($element[0], settings); 1284 $element.addClass('lg-initialized'); 1285 } catch (error) { 1286 console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error); 1287 } 1288 } 1289 } 1290 } 1291 1292 /** 1293 * Get LightGallery settings based on admin configuration 1294 */ 1295 function getLightboxSettings(isGallery) { 1296 var metasliderOptions = mlLightboxSettings.metaslider_options || {}; 1297 1298 var settings = { 1299 selector: 'a[data-src]', 1300 mode: 'lg-fade', 1301 speed: 350, 1302 download: false, 1303 counter: isGallery, 1304 closable: true, 1305 closeOnTap: true, 1306 controls: !!metasliderOptions.show_arrows, 1307 plugins: [], 1308 // Accessibility settings 1309 escapeKey: true, // Allow Escape key to close lightbox 1310 keyPress: true, // Enable keyboard navigation (arrows, Esc) 1311 mousewheel: true, // Allow mousewheel navigation 1312 allowMediaOverlap: false, // Prevent overlap for better screen reader support 1313 ariaLabelledby: '', // Will be set dynamically per image 1314 ariaDescribedby: '' // Will be set dynamically per image 1315 }; 1316 1317 // Feed WordPress-translated labels into lightGallery's built-in UI 1318 // (close, prev/next, download, etc.), which are hardcoded English by default. 1319 if (mlLightboxSettings.lg_strings) { 1320 settings.strings = mlLightboxSettings.lg_strings; 1321 } 1322 1323 if (typeof _mlLk !== 'undefined') { 1324 settings.licenseKey = _mlLk; 1325 } 1326 1327 if (!shouldShowCaptions()) { 1328 settings.subHtml = ''; 1329 settings.getCaptionFromTitleOrAlt = false; 1330 } 1331 1332 if (isGallery && metasliderOptions.show_thumbnails && typeof lgThumbnail !== 'undefined') { 1333 settings.plugins.push(lgThumbnail); 1334 settings.thumbWidth = 100; 1335 settings.thumbHeight = '80px'; 1336 settings.thumbMargin = 4; 1337 settings.exThumbImage = 'data-thumb'; 1338 } 1339 1340 if (typeof lgVideo !== 'undefined') { 1341 settings.plugins.push(lgVideo); 1342 settings.videoMaxSize = '1280-720'; 1343 } 1344 1345 if (typeof window.MetaSliderLightboxPro !== 'undefined' && 1346 typeof window.MetaSliderLightboxPro.enhanceSettings === 'function') { 1347 settings = window.MetaSliderLightboxPro.enhanceSettings(settings, null); 1348 } 1349 1350 return settings; 1351 } 1352 1353 /** 1354 * Remove WordPress image size suffix to get full-size image URL 1355 */ 1356 function removeWordPressSizeSuffix(url) { 1357 if (!url) return ''; 1358 return url.replace(/-\d+x\d+(\.[^.]+)$/, '$1'); 1359 } 1360 1361 function getSlideImageLink($slide) { 1362 return $slide.find('a').has('img').first(); 1363 } 1364 1365 function slideHasCustomLink($slide) { 1366 var href = $.trim(getSlideImageLink($slide).attr('href') || ''); 1367 1368 return href !== '' && href !== '#' && href.toLowerCase().indexOf('javascript:') !== 0; 1369 } 1370 1371 /** 1372 * Check if URL is a video URL 1373 */ 1374 function isVideoUrl(url) { 1375 if (!url) return false; 1376 1377 return /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/|vimeo\.com\/(?:video\/)?|\.(?:mp4|webm|ogg|mov|avi)(?:\?|$))/i.test(url); 1378 } 1379 1380 /** 1381 * Get video URL in format that LightGallery expects 1382 * LightGallery automatically detects and handles YouTube/Vimeo URLs 1383 */ 1384 function getVideoUrl(url) { 1385 if (!url) return ''; 1386 1387 var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/); 1388 if (youtubeMatch) { 1389 return 'https://www.youtube.com/watch?v=' + youtubeMatch[1]; 1390 } 1391 1392 var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/); 1393 if (vimeoMatch) { 1394 return 'https://vimeo.com/' + vimeoMatch[1]; 1395 } 1396 1397 return url; 1398 } 1399 1400 /** 1401 * Get video thumbnail URL 1402 */ 1403 function getVideoThumbnail(url) { 1404 if (!url) return ''; 1405 1406 var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/); 1407 if (youtubeMatch) { 1408 return 'https://img.youtube.com/vi/' + youtubeMatch[1] + '/maxresdefault.jpg'; 1409 } 1410 1411 var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/); 1412 if (vimeoMatch) { 1413 return 'https://vumbnail.com/' + vimeoMatch[1] + '.jpg'; 1414 } 1415 1416 return ''; 1417 } 1418 1419 /** 1420 * Initialize a WordPress "Enlarge on Click" gallery 1421 */ 1422 function initWordPressGallery($gallery, useButtonMode) { 1423 useButtonMode = useButtonMode || false; 1424 1425 var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]'); 1426 var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]'); 1427 1428 var $allContainers = $(); 1429 1430 $wpImagesOld.each(function() { 1431 $allContainers = $allContainers.add($(this).closest('.wp-lightbox-container')); 1432 }); 1433 1434 $allContainers = $allContainers.add($wpContainersNew); 1435 1436 if ($allContainers.length <= 1) return; 1437 1438 $allContainers.each(function() { 1439 var $figure = $(this); 1440 var $img = $figure.find('img').first(); 1441 1442 if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) { 1443 return; 1444 } 1445 1446 var src = $img.attr('src'); 1447 if (!src) return; 1448 1449 var fullUrl = removeWordPressSizeSuffix(src); 1450 1451 if (useButtonMode) { 1452 var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>'); 1453 $button.attr({ 1454 'data-src': fullUrl,
1455 'data-thumb': src 1456 }); 1457 $figure.css('position', 'relative').append($button); 1458 } else { 1459 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1460 $overlay.attr({ 1461 'data-src': fullUrl, 1462 'data-thumb': src 1463 }); 1464 1465 // Add accessibility attributes 1466 var altText = $img.attr('alt') || ''; 1467 addOverlayAccessibility($overlay, 'image', altText); 1468 1469 $figure.css('position', 'relative').prepend($overlay); 1470 } 1471 }); 1472 1473 var gallerySettings = getLightboxSettings(true); 1474 gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay'; 1475 1476 gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); 1477 1478 try { 1479 var instance = lightGallery($gallery[0], gallerySettings); 1480 $gallery.addClass('lg-initialized'); 1481 1482 } catch (error) { 1483 console.error('MetaSlider Lightbox: Error initializing WordPress gallery:', error); 1484 } 1485 } 1486 1487 1488 /** 1489 * Initialize a classic [gallery] shortcode gallery 1490 */ 1491 function initClassicGallery($gallery) { 1492 var $galleryLinks = $gallery.find('a.ml-gallery-lightbox'); 1493 1494 if ($galleryLinks.length <= 1) return; 1495 1496 $galleryLinks.each(function() { 1497 var $link = $(this); 1498 var $img = $link.find('img').first(); 1499 1500 if ($link.attr('data-src')) { 1501 return; 1502 } 1503 1504 var href = $link.attr('href'); 1505 var src = $img.attr('src'); 1506 1507 if (!href) return; 1508 1509 $link.attr({ 1510 'data-src': href, 1511 'data-thumb': src || href 1512 }); 1513 1514 var caption = escapeHtml($img.attr('alt') || $img.attr('title') || ''); 1515 var $figcaption = $link.closest('.gallery-item').find('figcaption, .wp-caption-text'); 1516 if ($figcaption.length > 0) { 1517 caption = escapeHtml($figcaption.text().trim()); 1518 } 1519 1520 if (caption) { 1521 $link.attr('data-sub-html', '<div class="lg-sub-html"><p>' + caption + '</p></div>'); 1522 } 1523 }); 1524 1525 var gallerySettings = getLightboxSettings(true); 1526 gallerySettings.selector = 'a.ml-gallery-lightbox'; 1527 1528 gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); 1529 1530 try { 1531 var instance = lightGallery($gallery[0], gallerySettings); 1532 $gallery.addClass('lg-initialized'); 1533 1534 } catch (error) { 1535 console.error('MetaSlider Lightbox: Error initializing classic gallery:', error); 1536 } 1537 } 1538 1539 /** 1540 * Initialize a single WordPress "Enlarge on Click" image with overlay approach 1541 */ 1542 function initWordPressEnlargeOnClick($img) { 1543 var $figure = $img.closest('.wp-lightbox-container'); 1544 1545 var src = $img.attr('src'); 1546 if (!src) return; 1547 1548 var fullUrl = removeWordPressSizeSuffix(src); 1549
1550 var $overlay = $('<a href="#" class="ml-image-overlay"></a>'); 1551 1552 $overlay.attr({ 1553 'data-src': fullUrl, 1554 'data-thumb': src 1555 }); 1556 1557 var caption = extractCaption($img); 1558 if (caption) { 1559 $overlay.attr('data-sub-html', caption); 1560 } 1561 1562 // Add accessibility attributes 1563 var altText = $img.attr('alt') || ''; 1564 addOverlayAccessibility($overlay, 'image', altText); 1565 1566 var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 1567 $wpButton.remove(); 1568 1569 $figure.css('position', 'relative').prepend($overlay); 1570 1571 var settings = getLightboxSettings(false); 1572 settings.selector = '.ml-image-overlay'; 1573 1574 try { 1575 var instance = lightGallery($figure[0], settings); 1576 $figure.addClass('lg-initialized'); 1577 1578 } catch (error) { 1579 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click overlay:', error); 1580 } 1581 } 1582 1583 /** 1584 * Initialize a single WordPress "Enlarge on Click" image with button approach 1585 */ 1586 function initWordPressEnlargeOnClickWithButton($img) { 1587 var $figure = $img.closest('.wp-lightbox-container'); 1588 1589 var src = $img.attr('src'); 1590 if (!src) return; 1591 1592 var fullUrl = removeWordPressSizeSuffix(src); 1593 1594 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-single" href="#">' + getButtonText() + '</a>'); 1595 1596 $button.attr({ 1597 'data-src': fullUrl, 1598 'data-thumb': src 1599 }); 1600 1601 var caption = extractCaption($img); 1602 if (caption) { 1603 $button.attr('data-sub-html', caption); 1604 } 1605 1606 var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 1607 $wpButton.remove(); 1608 1609 $figure.css('position', 'relative').append($button); 1610 1611 var settings = getLightboxSettings(false); 1612 settings.selector = '.ml-lightbox-button'; 1613 1614 try { 1615 var instance = lightGallery($figure[0], settings); 1616 $figure.addClass('lg-initialized'); 1617 1618 } catch (error) { 1619 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click button:', error); 1620 } 1621 } 1622 1623 /** 1624 * Initialize a "Link to Media File" gallery 1625 */ 1626 function initLinkToMediaGallery($gallery, useButtonMode) { 1627 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 1628 return $(this).find('img').length > 0; 1629 }); 1630 1631 if ($mediaLinks.length <= 1) return; 1632 1633 if (useButtonMode) { 1634 $mediaLinks.each(function() { 1635 var $link = $(this); 1636 var href = $link.attr('href'); 1637 var $img = $link.find('img').first(); 1638 var imgSrc = $img.attr('src'); 1639 1640 if (!href || !imgSrc) return; 1641 1642 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single" href="#">' + getButtonText() + '</a>'); 1643 $button.attr({ 1644 'data-src': href, 1645 'data-thumb': imgSrc 1646 }); 1647 1648 var caption = extractCaption($img); 1649 if (caption) { 1650 $button.attr('data-sub-html', caption); 1651 } 1652 1653 $link.parent().append($button); 1654 }); 1655 1656 var gallerySettings = getLightboxSettings(true); 1657 gallerySettings.selector = '.ml-lightbox-button'; 1658 1659 } else { 1660 $mediaLinks.each(function() { 1661 var $link = $(this); 1662 var href = $link.attr('href'); 1663 var $img = $link.find('img').first(); 1664 var imgSrc = $img.attr('src'); 1665 1666 if (!href || !imgSrc) return; 1667 1668 $link.attr({ 1669 'data-src': href, 1670 'data-thumb': imgSrc 1671 }); 1672 1673 var caption = extractCaption($img); 1674 if (caption) { 1675 $link.attr('data-sub-html', caption); 1676 } 1677 }); 1678
1679 var gallerySettings = getLightboxSettings(true); 1680 gallerySettings.selector = 'a[data-src]'; 1681 } 1682 1683 try { 1684 var instance = lightGallery($gallery[0], gallerySettings); 1685 $gallery.addClass('lg-initialized'); 1686 1687 } catch (error) { 1688 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" gallery:', error); 1689 } 1690 } 1691 1692 /** 1693 * Initialize a plain image gallery (no links, no WordPress lightbox) 1694 */ 1695 function initPlainImageGallery($gallery, useButtonMode) { 1696 var $images = $gallery.find('img'); 1697 1698 if ($images.length <= 1) return; 1699 1700 if (useButtonMode) { 1701 $images.each(function() { 1702 var $img = $(this); 1703 var imgSrc = $img.attr('src'); 1704 1705 if (!imgSrc) return; 1706 1707 var fullUrl = removeWordPressSizeSuffix(imgSrc); 1708 1709 var $figure = $img.closest('.wp-block-image'); 1710 if ($figure.length === 0) { 1711 $figure = $img.parent(); 1712 } 1713 1714 $figure.attr({ 1715 'data-src': fullUrl, 1716 'data-thumb': imgSrc 1717 }); 1718 1719 var caption = extractCaption($img); 1720 if (caption) { 1721 $figure.attr('data-sub-html', caption); 1722 } 1723 1724 var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-plain-gallery" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>'); 1725 if (caption) { 1726 $button.attr('data-sub-html', caption); 1727 } 1728 1729 $figure.css('position', 'relative').append($button); 1730 }); 1731 1732 var gallerySettings = getLightboxSettings(true); 1733 gallerySettings.selector = '.ml-lightbox-button'; 1734 1735 } else { 1736 $images.each(function() { 1737 var $img = $(this); 1738 var imgSrc = $img.attr('src'); 1739 1740 if (!imgSrc) return; 1741 1742 var fullUrl = removeWordPressSizeSuffix(imgSrc); 1743 1744 var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>'); 1745 1746 var caption = extractCaption($img); 1747 if (caption) { 1748 $wrapper.attr('data-sub-html', caption); 1749 } 1750 1751 $img.wrap($wrapper); 1752 }); 1753 1754 var gallerySettings = getLightboxSettings(true); 1755 gallerySettings.selector = 'a[data-src]'; 1756 } 1757 1758 try { 1759 var instance = lightGallery($gallery[0], gallerySettings); 1760 $gallery.addClass('lg-initialized'); 1761 1762 } catch (error) { 1763 console.error('MetaSlider Lightbox: Error initializing plain image gallery:', error); 1764 } 1765 } 1766 1767 /** 1768 * Initialize a single "Link to Media File" image 1769 */ 1770 function initLinkToMediaFile($link) { 1771 var href = $link.attr('href'); 1772 var $img = $link.find('img').first(); 1773 var imgSrc = $img.attr('src'); 1774 1775 if (!href || !imgSrc) return; 1776 1777 // Get alt text for accessible name 1778 var altText = $img.attr('alt') || ''; 1779 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 1780 1781 $link.attr({ 1782 'data-src': href, 1783 'data-thumb': imgSrc, 1784 'aria-label': ariaLabel 1785 }); 1786 1787 var caption = extractCaption($img); 1788 if (caption) { 1789 $link.attr('data-sub-html', caption); 1790 } 1791 1792 var settings = getLightboxSettings(false); 1793 settings.selector = 'this'; 1794 1795 try { 1796 var instance = lightGallery($link[0], settings); 1797 $link.addClass('lg-initialized'); 1798 1799 } catch (error) { 1800 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" image:', error); 1801 } 1802 } 1803 1804 /** 1805 * Initialize a single "Link to Media File" image with button approach 1806 */ 1807 function initLinkToMediaFileWithButton($link) { 1808 var href = $link.attr('href'); 1809 var $img = $link.find('img').first(); 1810 var imgSrc = $img.attr('src'); 1811 1812 if (!href || !imgSrc) return; 1813 1814 // Get alt text for accessible name 1815 var altText = $img.attr('alt') || ''; 1816 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 1817 1818 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">
1818' + getButtonText() + '</a>'); 1819 1820 $button.attr({ 1821 'data-src': href, 1822 'data-thumb': imgSrc, 1823 'aria-label': ariaLabel 1824 }); 1825 1826 var caption = extractCaption($img); 1827 if (caption) { 1828 $button.attr('data-sub-html', caption); 1829 } 1830 1831 // Add aria-label to link as well 1832 $link.attr('aria-label', ariaLabel); 1833 1834 $link.css('position', 'relative').append($button); 1835 1836 var settings = getLightboxSettings(false); 1837 settings.selector = '.ml-lightbox-button'; 1838 1839 try { 1840 var instance = lightGallery($link[0], settings); 1841 $link.addClass('lg-initialized'); 1842 1843 } catch (error) { 1844 console.error('MetaSlider Lightbox: Error initializing "Link to Media File" button:', error); 1845 } 1846 } 1847 1848 /** 1849 * Initialize a featured image lightbox where the outer <a> links to the post 1850 * and data-src holds the image URL for the lightbox 1851 */ 1852 function initFeaturedImageWithButton($link) { 1853 var dataSrc = $link.attr('data-src'); 1854 var $img = $link.find('img').first(); 1855 var imgSrc = $img.attr('src'); 1856 1857 if (!dataSrc || !imgSrc) return; 1858 1859 var altText = $img.attr('alt') || ''; 1860 var ariaLabel = 'View image' + (altText ? ': ' + altText : ''); 1861 1862 var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">' + getButtonText() + '</a>'); 1863 $button.attr({ 1864 'data-src': dataSrc, 1865 'data-thumb': imgSrc, 1866 'aria-label': ariaLabel 1867 }); 1868 1869 $link.attr('aria-label', ariaLabel).css('position', 'relative').append($button); 1870 1871 var settings = getLightboxSettings(false); 1872 settings.selector = '.ml-lightbox-button'; 1873 1874 try { 1875 lightGallery($link[0], settings); 1876 $link.addClass('lg-initialized'); 1877 } catch (error) { 1878 console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error); 1879 } 1880 } 1881 1882 /** 1883 * Initialize a featured image lightbox without a button â clicking the link opens the lightbox directly 1884 */ 1885 function initFeaturedImage($link) { 1886 var dataSrc = $link.attr('data-src'); 1887 var $img = $link.find('img').first(); 1888 var imgSrc = $img.attr('src'); 1889 1890 if (!dataSrc || !imgSrc) return; 1891 1892 var altText = $img.attr('alt') || ''; 1893 $link.attr('aria-label', 'View image' + (altText ? ': ' + altText : '')); 1894 1895 var settings = getLightboxSettings(false); 1896 settings.selector = 'this'; 1897 1898 try { 1899 lightGallery($link[0], settings); 1900 $link.addClass('lg-initialized'); 1901 } catch (error) { 1902 console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error); 1903 } 1904 } 1905 1906 /** 1907 * Initialize MetaSlider lightbox functionality 1908 * Based on settings from ml-lightgallery-init.js but simplified for integration 1909 */ 1910 function initMetaSlider() { 1911 var $sliders = $('.metaslider, [class*="ml-slider-lightbox-"]'); 1912 1913 if ($sliders.length === 0) { 1914 return; 1915 } 1916 1917 1918 $sliders.each(function() { 1919 var $slider = $(this); 1920 1921 if ($slider.hasClass('lg-initialized')) { 1922 return; 1923 } 1924 1925 var sliderId = $slider.attr('id'); 1926 1927 if (!sliderId) { 1928 var $firstSlide = $slider.find('li').first(); 1929 var slideClass = $firstSlide.attr('class'); 1930 if (slideClass) { 1931 var classMatch = slideClass.match(/slider-(\d+)/); 1932 if (classMatch) { 1933 sliderId = classMatch[1]; 1934 } 1935 } 1936 1937 if (!sliderId) { 1938 var $firstImg = $firstSlide.find('img').first(); 1939 var imgClass = $firstImg.attr('class'); 1940 if (imgClass) { 1941 var imgClassMatch = imgClass.match(/slider-(\d+)/); 1942 if (imgClassMatch) { 1943 sliderId = imgClassMatch[1]; 1944 } 1945 } 1946 } 1947 } 1948 1949 if (sliderId) { 1950 var matches = sliderId.match(/(\d+)/); 1951 if (matches) { 1952 sliderId = matches[1]; 1953 } 1954 } 1955 1956
1957 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.slider_settings && sliderId) { 1958 var sliderSettings = mlLightboxSettings.slider_settings[sliderId]; 1959 if (sliderSettings && sliderSettings.lightbox_enabled === false) { 1960 return; 1961 } 1962 } 1963 1964 var showButton = resolveSliderButton(sliderId); 1965 1966 if (showButton) { 1967 initMetaSliderButtonMode($slider, sliderId); 1968 } else { 1969 initMetaSliderDirectMode($slider, sliderId); 1970 } 1971 }); 1972 } 1973 1974 /** 1975 * Read a per-slider boolean override (true/false) from slider_settings. 1976 * PHP sends true/false/null where null means "use global default". 1977 * Returns the per-slider value when set, otherwise calls globalFn(). 1978 */ 1979 function resolveSliderSetting(sliderId, key, globalFn) { 1980 var perSlider = mlLightboxSettings.slider_settings && 1981 sliderId && 1982 mlLightboxSettings.slider_settings[sliderId]; 1983 var val = perSlider ? perSlider[key] : null; 1984 if (val === true) { return true; } 1985 if (val === false) { return false; } 1986 return globalFn(); 1987 } 1988 1989 function resolveSliderCaptions(sliderId) { 1990 return resolveSliderSetting(sliderId, 'lightbox_captions', shouldShowCaptions); 1991 } 1992 1993 function resolveSliderNavigation(sliderId) { 1994 return resolveSliderSetting(sliderId, 'lightbox_navigation', function() { 1995 return (mlLightboxSettings.metaslider_options || {}).show_arrows !== false; 1996 }); 1997 } 1998 1999 /** 2000 * Unified "how visitors open images" mode for a slider: 'image'|'icon'|'button'. 2001 * PHP sends slider_settings[id].lightbox_open_with as one of those three 2002 * strings, or null to fall back to the global show/icon settings. 2003 * 2004 * @since 2.35 2005 */ 2006 function resolveSliderOpenWith(sliderId) { 2007 var perSlider = mlLightboxSettings.slider_settings && 2008 sliderId && 2009 mlLightboxSettings.slider_settings[sliderId]; 2010 var mode = perSlider ? perSlider.lightbox_open_with : null; 2011 if (mode === 'image' || mode === 'icon' || mode === 'button') { 2012 return mode; 2013 } 2014 2015 var opts = mlLightboxSettings.metaslider_options || {}; 2016 if (opts.show_lightbox_button === false) { 2017 return 'image'; 2018 } 2019 return opts.use_icon_instead_of_button ? 'icon' : 'button'; 2020 } 2021 2022 function resolveSliderButton(sliderId) { 2023 return resolveSliderOpenWith(sliderId) !== 'image'; 2024 } 2025 2026 function resolveSliderIcon(sliderId) { 2027 return resolveSliderOpenWith(sliderId) === 'icon'; 2028 } 2029 2030 /** 2031 * Initialize MetaSlider in button mode 2032 */ 2033 function initMetaSliderButtonMode($slider, sliderId) { 2034 _metasliderButtonSliderId = sliderId; 2035 try { 2036 var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb'); 2037 var buttonsAdded = 0; 2038 2039 $slides.each(function() { 2040 var $slide = $(this); 2041 var $button = null; 2042 2043 if ($slide.find('.ml-lightbox-button').length > 0) { 2044 return; 2045 } 2046 2047 if ($slide.hasClass('ms-youtube')) { 2048 $button = createYouTubeButton($slide); 2049 } else if ($slide.hasClass('ms-vimeo')) { 2050 $button = createVimeoButton($slide); 2051 } else if ($slide.hasClass('ms-external-video')) { 2052 $button = createExternalVideoButton($slide); 2053 } else if ($slide.hasClass('ms-local-video')) { 2054 $button = createLocalVideoButton($slide); 2055 } else if ($slide.hasClass('ms-postfeed')) { 2056 $button = createPostFeedButton($slide); 2057 } else if ($slide.hasClass('ms-layer')) { 2058 $button = createLayerButton($slide); 2059 } else if ($slide.hasClass('ms-custom-html')) { 2060 $button = createCustomHtmlButton($slide); 2061 } else { 2062 $button = createImageButton($slide); 2063 } 2064 2065 if ($button) { 2066 $slide.css('position', 'relative').append($button); 2067 buttonsAdded++; 2068 } 2069 }); 2070 2071 if (buttonsAdded > 0) {
2072 var settings = getLightboxSettings(buttonsAdded > 1); 2073 settings.selector = '.ml-lightbox-button'; 2074 2075 if (!resolveSliderCaptions(sliderId)) { 2076 settings.subHtml = ''; 2077 settings.getCaptionFromTitleOrAlt = false; 2078 $slider.find('.ml-lightbox-button').removeAttr('data-sub-html'); 2079 } 2080 2081 settings.controls = resolveSliderNavigation(sliderId); 2082 2083 try { 2084 lightGallery($slider[0], settings); 2085 $slider.addClass('lg-initialized'); 2086 } catch (error) { 2087 console.error('MetaSlider Lightbox: Error initializing button mode:', error); 2088 } 2089 } 2090 } finally { 2091 _metasliderButtonSliderId = null; 2092 } 2093 } 2094 2095 /** 2096 * Initialize MetaSlider in direct-click mode 2097 */ 2098 function initMetaSliderDirectMode($slider, sliderId) { 2099 2100 var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb'); 2101 var linksAdded = 0; 2102 2103 $slides.each(function() { 2104 var $slide = $(this); 2105 2106 if ($slide.find('.ml-metaslider-link, .ml-video-overlay').length > 0) return; 2107 2108 var dataAttributes = null; 2109 2110 if ($slide.hasClass('ms-youtube')) { 2111 dataAttributes = getYouTubeOverlayData($slide); 2112 } 2113 else if ($slide.hasClass('ms-vimeo')) { 2114 dataAttributes = getVimeoOverlayData($slide); 2115 } 2116 else if ($slide.hasClass('ms-external-video')) { 2117 dataAttributes = getExternalVideoOverlayData($slide); 2118 } 2119 else if ($slide.hasClass('ms-local-video')) { 2120 dataAttributes = getLocalVideoOverlayData($slide); 2121 } 2122 else if ($slide.hasClass('ms-postfeed')) { 2123 dataAttributes = getPostFeedOverlayData($slide); 2124 } 2125 else if ($slide.hasClass('ms-layer')) { 2126 dataAttributes = getLayerOverlayData($slide); 2127 } 2128 else if ($slide.hasClass('ms-custom-html')) { 2129 dataAttributes = getCustomHtmlOverlayData($slide); 2130 } 2131 else { 2132 dataAttributes = getImageOverlayData($slide); 2133 } 2134 2135 if (dataAttributes) { 2136 if (dataAttributes.overlay) { 2137 var overlayClass = 'ml-video-overlay'; 2138 var contentType = 'video'; 2139 if ($slide.hasClass('ms-image')) { 2140 overlayClass = 'ml-image-overlay'; 2141 contentType = 'image'; 2142 }
2143 var $overlay = $('<a href="#" class="' + overlayClass + '"></a>'); 2144 $overlay.attr(dataAttributes.attrs); 2145 2146 // Add accessibility attributes 2147 var $img = $slide.find('img').first(); 2148 var altText = $img.length > 0 ? ($img.attr('alt') || '') : ''; 2149 addOverlayAccessibility($overlay, contentType, altText); 2150 2151 $slide.css('position', 'relative').prepend($overlay); 2152 } else { 2153 var $link = $slide.find('a').first(); 2154 if ($link.length > 0) { 2155 $link.attr(dataAttributes.attrs).addClass('ml-metaslider-link'); 2156 } 2157 } 2158 linksAdded++; 2159 } 2160 }); 2161 2162 if (linksAdded > 0) { 2163 var settings = getLightboxSettings(linksAdded > 1); 2164 settings.selector = '.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay'; 2165 2166 if (!resolveSliderCaptions(sliderId)) { 2167 settings.subHtml = ''; 2168 settings.getCaptionFromTitleOrAlt = false; 2169 $slider.find('.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay').removeAttr('data-sub-html'); 2170 } 2171 2172 settings.controls = resolveSliderNavigation(sliderId); 2173 2174 try { 2175 lightGallery($slider[0], settings); 2176 $slider.addClass('lg-initialized'); 2177 } catch (error) { 2178 console.error('MetaSlider Lightbox: Error initializing direct-click mode:', error); 2179 } 2180 } 2181 } 2182 2183 2184 /** 2185 * Check if gallery is already processed 2186 */ 2187 function isGalleryAlreadyProcessed($gallery) { 2188 return $gallery.hasClass(CONSTANTS.CLASSES.ML_LIGHTBOX_ENABLED) || 2189 $gallery.hasClass(CONSTANTS.CLASSES.LG_INITIALIZED); 2190 } 2191 2192 /** 2193 * Extract video ID using various fallback methods 2194 */ 2195 function extractVideoId($slide, handler) { 2196 var $element = $slide.find(handler.selector); 2197 if ($element.length === 0) return null; 2198 2199 var id = $element.attr(handler.idAttr); 2200 2201 if (!id && handler.fallbackSelectors) { 2202 for (var i = 0; i < handler.fallbackSelectors.length; i++) { 2203 var fallbackSelector = handler.fallbackSelectors[i]; 2204 var $fallback = $slide.find(fallbackSelector); 2205 2206 if (fallbackSelector.startsWith('a[href*=')) { 2207 var url = $fallback.attr('href'); 2208 if (url) { 2209 var match = url.match(/vimeo\.com\/(\d+)/); 2210 if (match) { 2211 id = match[1]; 2212 break; 2213 } 2214 } 2215 } else { 2216 id = $fallback.attr(handler.idAttr); 2217 if (id) break; 2218 } 2219 } 2220 } 2221 2222 return id; 2223 } 2224 2225 /** 2226 * Unified video button creation function 2227 */ 2228 function createVideoButton($slide, videoType) { 2229 var handler = CONSTANTS.VIDEO_HANDLERS[videoType]; 2230 if (!handler) return null; 2231 2232 var videoData = null; 2233 2234 if (videoType === 'youtube' || videoType === 'vimeo') { 2235 var videoId = extractVideoId($slide, handler); 2236 if (!videoId) return null; 2237 2238 videoData = { 2239 url: handler.urlTemplate(videoId), 2240 thumb: handler.thumbTemplate(videoId) 2241 }; 2242 } else if (videoType === 'externalVideo' || videoType === 'localVideo') { 2243 var $element = $slide.find(handler.selector); 2244 if ($element.length === 0) return null; 2245 2246 var dataSources = $element.attr(handler.sourcesAttr); 2247 var videoSources = []; 2248 var videoUrl = null; 2249 2250 if (dataSources) { 2251 try { 2252 var sources = JSON.parse(dataSources); 2253 if (sources && sources.length > 0) { 2254 videoUrl = sources[0].src; 2255 videoSources = sources.map(function (source) { 2256 return { 2257 'src': source.src, 2258 'type': source.type || getVideoType(source.src) 2259 }; 2260 }); 2261 } 2262 } catch (e) { 2263 console.error('MetaSlider Lightbox: Error parsing video sources', e); 2264 return null; 2265 } 2266 } 2267 2268 if (!videoUrl || videoSources.length === 0) return null; 2269 2270 videoData = { 2271 url: '', 2272 thumb: $element.attr(handler.posterAttr) || getPlaceholderThumb(), 2273 videoData: { 2274 'source': videoSources, 2275 'attributes': { 2276 'preload': false, 2277 'controls': true 2278 } 2279 } 2280 }; 2281 } 2282 2283 if (!videoData) return null; 2284 2285 var diagnosticClass = 'ml-button-metaslider ml-button-metaslider-' + videoType.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, ''); 2286 var buttonText = getButtonText(); 2287 var $button = $('<a class="' + CONSTANTS.CLASSES.ML_LIGHTBOX_BUTTON + ' ' + diagnosticClass + '" href="#">
2287' + buttonText + '</a>'); 2288 2289 var baseAttrs = { 2290 'role': 'button', 2291 'aria-label': buttonText + ' - ' + videoType + ' video', 2292 'tabindex': '0' 2293 }; 2294 2295 if (videoData.videoData) { 2296 $button.attr($.extend({}, baseAttrs, { 2297 'data-src': '', 2298 'data-video': JSON.stringify(videoData.videoData), 2299 'data-thumb': videoData.thumb 2300 })); 2301 } else { 2302 if (!videoData.url) return null; 2303 $button.attr($.extend({}, baseAttrs, { 2304 'data-src': videoData.url, 2305 'data-thumb': videoData.thumb 2306 })); 2307 } 2308 2309 return $button; 2310 } 2311 2312 /** 2313 * Create lightbox button for regular image slides 2314 */ 2315 function createImageButton($slide) { 2316 var $img = $slide.find('img').first(); 2317 2318 if ($img.length === 0) return null; 2319 2320 var imgSrc = $img.attr('src'); 2321 var fullSizeUrl = removeWordPressSizeSuffix(imgSrc); 2322 2323 var altText = $img.attr('alt') || 'image'; 2324 var buttonText = getButtonText(); 2325 2326 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-image" href="#">' + buttonText + '</a>'); 2327 2328 $button.attr({ 2329 'data-src': fullSizeUrl, 2330 'data-thumb': imgSrc, 2331 'role': 'button', 2332 'aria-label': buttonText + ' - ' + altText, 2333 'tabindex': '0' 2334 }); 2335 2336 var caption = extractCaption($slide); 2337 if (caption) { 2338 $button.attr('data-sub-html', caption); 2339 } 2340 2341 return $button; 2342 } 2343 2344 /** 2345 * Create lightbox button for YouTube slides 2346 */ 2347 function createYouTubeButton($slide) { 2348 return createVideoButton($slide, 'youtube'); 2349 } 2350 2351 /** 2352 * Create lightbox button for Vimeo slides 2353 */ 2354 function createVimeoButton($slide) { 2355 return createVideoButton($slide, 'vimeo'); 2356 } 2357 2358 /** 2359 * Create lightbox button for external video slides 2360 */ 2361 function createExternalVideoButton($slide) { 2362 return createVideoButton($slide, 'externalVideo'); 2363 } 2364 2365 /** 2366 * Create lightbox button for local video slides 2367 */ 2368 function createLocalVideoButton($slide) { 2369 return createVideoButton($slide, 'localVideo'); 2370 } 2371 2372 /** 2373 * Helper function to get video type from URL 2374 */ 2375 function getVideoType(url) { 2376 if (url.includes('.mp4')) return 'video/mp4'; 2377 if (url.includes('.webm')) return 'video/webm'; 2378 if (url.includes('.ogg')) return 'video/ogg'; 2379 return 'video/mp4'; 2380 } 2381 2382 /** 2383 * Helper function to get placeholder thumbnail 2384 */ 2385 function getPlaceholderThumb() { 2386 return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+'; 2387 } 2388 2389 /** 2390 * Create lightbox button for PostFeed slides 2391 */ 2392 function createPostFeedButton($slide) { 2393 var $img = $slide.find('img').first(); 2394 if ($img.length === 0) return null; 2395 2396 var imgSrc = removeWordPressSizeSuffix($img.attr('src')); 2397 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-postfeed" href="#">' + getButtonText() + '</a>'); 2398 2399 $button.attr({ 2400 'data-src': imgSrc, 2401 'data-thumb': imgSrc 2402 }); 2403 2404 var caption = extractCaption($slide); 2405 if (caption) { 2406 $button.attr('data-sub-html', caption); 2407 } 2408 2409 return $button; 2410 } 2411 2412 /** 2413 * Get overlay data for PostFeed slides in direct-click mode 2414 */ 2415 function getPostFeedOverlayData($slide) { 2416 var $img = $slide.find('img').first(); 2417 if ($img.length === 0) return null; 2418 2419 var imgSrc = removeWordPressSizeSuffix($img.attr('src')); 2420 2421 var attrs = { 2422 'data-src': imgSrc,
2423 'data-thumb': imgSrc 2424 }; 2425 2426 var caption = extractCaption($slide); 2427 if (caption) { 2428 attrs['data-sub-html'] = caption; 2429 } 2430 2431 return { 2432 overlay: false, 2433 attrs: attrs 2434 }; 2435 } 2436 2437 /** 2438 * Create lightbox button for Layer slides using iframe data URI approach 2439 */ 2440 function createLayerButton($slide) { 2441 var slideContent = $slide.html(); 2442 if (!slideContent || !slideContent.trim()) return null; 2443 2444 var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) { 2445 var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) { 2446 var numValue = parseFloat(value); 2447 var scaledValue = Math.round(numValue * 2); 2448 return property + ': ' + scaledValue + unit; 2449 }); 2450 return match.replace(styleAttr, scaledStyle); 2451 }); 2452 2453 var $tempDiv = $('<div>').html(slideContent); 2454 var animationClasses = []; 2455 2456 $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() { 2457 var $elem = $(this); 2458 var classes = $elem.attr('class') || ''; 2459 var animationType = $elem.attr('data-animation') || ''; 2460 2461 if (classes) { 2462 animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) { 2463 return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i); 2464 })); 2465 } 2466 2467 if (animationType) { 2468 animationClasses.push(animationType); 2469 } 2470 }); 2471 2472 var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) { 2473 return self.indexOf(value) === index; 2474 }).join(' '); 2475 2476 var scaleCSS = ` 2477 .layer-slide-content { 2478 position: relative !important; 2479 display: inline-block !important; 2480 } 2481 .layer-slide-content .msDefaultImage { 2482 display: block !important; 2483 width: 1400px !important; 2484 height: 600px !important; 2485 max-width: 80% !important; 2486 margin: 0 auto !important; 2487 object-fit: cover !important; 2488 } 2489 .layer-slide-content .msHtmlOverlay { 2490 position: absolute !important; 2491 top: 0 !important; 2492 left: 50% !important; 2493 width: 80% !important; 2494 height: 100% !important; 2495 transform: translateX(-50%) !important; 2496 } 2497 .layer-slide-content .msHtmlOverlay .content { 2498 font-size: 1.8em !important; 2499 line-height: 1.2 !important; 2500 } 2501 `; 2502 2503 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>'; 2504 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>'); 2505 2506 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-layer" href="#">' + getButtonText() + '</a>'); 2507 2508 $button.attr({ 2509 'data-src': dataUri, 2510 'data-thumb': getPlaceholderThumb(), 2511 'data-iframe': 'true', 2512 'data-width': '800', 2513 'data-height': '600' 2514 }); 2515 2516 return $button; 2517 } 2518 2519 /** 2520 * Get overlay data for Layer slides in direct-click mode using iframe data URI approach 2521 */ 2522 function getLayerOverlayData($slide) { 2523 var slideContent = $slide.html(); 2524 if (!slideContent || !slideContent.trim()) return null; 2525 2526 var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) { 2527 var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) { 2528 var numValue = parseFloat(value); 2529 var scaledValue = Math.round(numValue * 2); 2530 return property + ': ' + scaledValue + unit; 2531 }); 2532 return match.replace(styleAttr, scaledStyle); 2533 }); 2534
2535 var $tempDiv = $('<div>').html(slideContent); 2536 var animationClasses = []; 2537 2538 $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() { 2539 var $elem = $(this); 2540 var classes = $elem.attr('class') || ''; 2541 var animationType = $elem.attr('data-animation') || ''; 2542 2543 if (classes) { 2544 animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) { 2545 return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i); 2546 })); 2547 } 2548 2549 if (animationType) { 2550 animationClasses.push(animationType); 2551 } 2552 }); 2553 2554 var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) { 2555 return self.indexOf(value) === index; 2556 }).join(' '); 2557 2558 var scaleCSS = ` 2559 .layer-slide-content { 2560 position: relative !important; 2561 display: inline-block !important; 2562 } 2563 .layer-slide-content .msDefaultImage { 2564 display: block !important; 2565 width: 1400px !important; 2566 height: 600px !important; 2567 max-width: 80% !important; 2568 margin: 0 auto !important; 2569 object-fit: cover !important; 2570 } 2571 .layer-slide-content .msHtmlOverlay { 2572 position: absolute !important; 2573 top: 0 !important; 2574 left: 50% !important; 2575 width: 80% !important; 2576 height: 100% !important; 2577 transform: translateX(-50%) !important; 2578 } 2579 .layer-slide-content .msHtmlOverlay .content { 2580 font-size: 1.8em !important; 2581 line-height: 1.2 !important; 2582 } 2583 `; 2584 2585 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>'; 2586 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>'); 2587 2588 var attrs = { 2589 'data-src': dataUri, 2590 'data-thumb': getPlaceholderThumb(), 2591 'data-iframe': 'true', 2592 'data-width': '800', 2593 'data-height': '600' 2594 }; 2595 2596 var caption = extractCaption($slide); 2597 if (caption) { 2598 attrs['data-sub-html'] = caption; 2599 } 2600 2601 return { 2602 overlay: true, 2603 attrs: attrs 2604 }; 2605 } 2606 2607 /** 2608 * HTML escape function for safe caption display 2609 */ 2610 2611 /** 2612 * Check if captions should be shown based on settings 2613 */ 2614 function shouldShowCaptions() { 2615 if (typeof mlLightboxSettings !== 'undefined' && 2616 mlLightboxSettings.metaslider_options && 2617 typeof mlLightboxSettings.metaslider_options.show_captions !== 'undefined') { 2618 return mlLightboxSettings.metaslider_options.show_captions === true || mlLightboxSettings.metaslider_options.show_captions === 1; 2619 } 2620 return true; 2621 } 2622 2623 /** 2624 * Extract caption from various sources for an image or container 2625 */ 2626 function extractCaption($element) { 2627 // PHP sets data-sub-html on the slide's <a> for the built-in lightbox. 2628 // Read this before the shouldShowCaptions() guard so it is available when 2629 // global captions are off but a per-slider override enables them. 2630 // resolveSliderCaptions() is the final authority on removal. 2631 var $childLink = $element.find('a').first(); 2632 var childCaption = $childLink.length ? ($childLink.attr('data-sub-html') || '').trim() : ''; 2633 2634 if (!shouldShowCaptions()) { 2635 return childCaption; 2636 } 2637 2638 var caption = ''; 2639 var $img = $element.is('img') ? $element : $element.find('img').first(); 2640 2641 var $figcaption = $element.closest('figure').find('figcaption'); 2642 if ($figcaption.length > 0) { 2643 caption = $figcaption.html(); 2644 } 2645 2646 if (!caption) { 2647 var $captionWrap = $element.find('.caption-wrap .caption'); 2648 if ($captionWrap.length === 0) { 2649 $captionWrap = $element.closest('li').find('.caption-wrap .caption'); 2650 } 2651 if ($captionWrap.length === 0) { 2652 $captionWrap = $element.closest('.slide').find('.caption-wrap .caption'); 2653 } 2654 if ($captionWrap.length > 0) { 2655 caption = $captionWrap.html(); 2656 } 2657 } 2658 2659 if (!caption && $img.length > 0) { 2660 caption = escapeHtml($img.attr('alt') || $img.attr('title') || ''); 2661 } 2662 2663 if (!caption) { 2664 caption = $element.attr('data-sub-html') || ''; 2665 } 2666 2667 if (!caption) { 2668 caption = childCaption; 2669 } 2670 2671 return caption.trim(); 2672 } 2673 2674 2675 /** 2676 * Get overlay data for regular images in direct-click mode 2677 */ 2678 function getImageOverlayData($slide) { 2679 var $img = $slide.find('img').first(); 2680 2681 if ($img.length === 0) return null; 2682 if (slideHasCustomLink($slide)) return null; 2683 2684 var imgSrc = $img.attr('src'); 2685 var fullSizeUrl = removeWordPressSizeSuffix(imgSrc); 2686 var needsOverlay = (getSlideImageLink($slide).length === 0); 2687 2688 var attrs = { 2689 'data-src': fullSizeUrl,
2690 'data-thumb': imgSrc 2691 }; 2692 2693 var caption = extractCaption($slide); 2694 if (caption) { 2695 attrs['data-sub-html'] = caption; 2696 } 2697 2698 return { 2699 overlay: needsOverlay, 2700 attrs: attrs 2701 }; 2702 } 2703 2704 /** 2705 * Get overlay data for YouTube videos in direct-click mode 2706 */ 2707 function getYouTubeOverlayData($slide) { 2708 var $youtubeDiv = $slide.find('div.youtube[data-id]'); 2709 if ($youtubeDiv.length === 0) return null; 2710 2711 var youtubeId = $youtubeDiv.attr('data-id'); 2712 if (!youtubeId) return null; 2713 2714 var youtubeUrl = 'https://www.youtube.com/watch?v=' + youtubeId; 2715 var thumbUrl = 'https://img.youtube.com/vi/' + youtubeId + '/maxresdefault.jpg'; 2716 2717 var attrs = { 2718 'data-src': youtubeUrl, 2719 'data-thumb': thumbUrl 2720 }; 2721 2722 var caption = extractCaption($slide); 2723 if (caption) { 2724 attrs['data-sub-html'] = caption; 2725 } 2726 2727 return { 2728 overlay: true, 2729 attrs: attrs 2730 }; 2731 } 2732 2733 /** 2734 * Get overlay data for Vimeo videos in direct-click mode 2735 */ 2736 function getVimeoOverlayData($slide) { 2737 var $vimeoDiv = $slide.find('div.vimeo[data-id]'); 2738 if ($vimeoDiv.length === 0) return null; 2739 2740 var vimeoId = $vimeoDiv.attr('data-id'); 2741 2742 if (!vimeoId) { 2743 vimeoId = $vimeoDiv.attr('data-slide-id'); 2744 } 2745 if (!vimeoId) { 2746 var $existingLink = $slide.find('a[href*="vimeo.com"]').first(); 2747 if ($existingLink.length > 0) { 2748 var vimeoUrl = $existingLink.attr('href'); 2749 var vimeoMatch = vimeoUrl.match(/vimeo\.com\/(\d+)/); 2750 if (vimeoMatch) { 2751 vimeoId = vimeoMatch[1]; 2752 } 2753 } 2754 } 2755 2756 if (!vimeoId) return null; 2757 2758 var vimeoUrl = 'https://vimeo.com/' + vimeoId; 2759 var vimeoPoster = 'https://vumbnail.com/' + vimeoId + '.jpg'; 2760 2761 var attrs = { 2762 'data-src': vimeoUrl, 2763 'data-thumb': vimeoPoster 2764 }; 2765 2766 var caption = extractCaption($slide); 2767 if (caption) { 2768 attrs['data-sub-html'] = caption; 2769 } 2770 2771 return { 2772 overlay: true, 2773 attrs: attrs 2774 }; 2775 } 2776 2777 /** 2778 * Get overlay data for external videos in direct-click mode 2779 */ 2780 function getExternalVideoOverlayData($slide) { 2781 var $videoDiv = $slide.find('div.external-video'); 2782 if ($videoDiv.length === 0) return null; 2783 2784 var dataSources = $videoDiv.attr('data-sources'); 2785 var videoSources = []; 2786 var videoUrl = null; 2787 2788 if (dataSources) { 2789 try { 2790 var sources = JSON.parse(dataSources); 2791 if (sources && sources.length > 0) { 2792 videoUrl = sources[0].src; 2793 videoSources = sources.map(function (source) { 2794 return { 2795 'src': source.src, 2796 'type': source.type || getVideoType(source.src) 2797 }; 2798 }); 2799 } 2800 } catch (e) { 2801 console.error('MetaSlider Lightbox: Error parsing video sources', e); 2802 return null; 2803 } 2804 } 2805 2806 if (!videoUrl || videoSources.length === 0) return null; 2807 2808 var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb(); 2809 var videoData = { 2810 'source': videoSources, 2811 'attributes': { 2812 'preload': false, 2813 'controls': true 2814 } 2815 }; 2816 2817 var attrs = { 2818 'data-src': '', 2819 'data-video': JSON.stringify(videoData), 2820 'data-thumb': posterUrl 2821 }; 2822 2823 var caption = extractCaption($slide); 2824 if (caption) { 2825 attrs['data-sub-html'] = caption; 2826 } 2827 2828 return { 2829 overlay: true, 2830 attrs: attrs 2831 }; 2832 } 2833 2834 /** 2835 * Get overlay data for local videos in direct-click mode 2836 */ 2837 function getLocalVideoOverlayData($slide) { 2838 var $videoDiv = $slide.find('div.local-video'); 2839 if ($videoDiv.length === 0) return null; 2840 2841 var dataSources = $videoDiv.attr('data-sources'); 2842 var videoSources = []; 2843 var videoUrl = null; 2844 2845 if (dataSources) { 2846 try { 2847 var sources = JSON.parse(dataSources); 2848 if (sources && sources.length > 0) { 2849 videoUrl = sources[0].src; 2850 videoSources = sources.map(function (source) { 2851 return { 2852 'src': source.src, 2853 'type': source.type || getVideoType(source.src) 2854 }; 2855 }); 2856 } 2857 } catch (e) { 2858 console.error('MetaSlider Lightbox: Error parsing video sources', e); 2859 return null; 2860 } 2861 } 2862 2863 if (!videoUrl || videoSources.length === 0) return null; 2864 2865 var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb(); 2866 var videoData = { 2867 'source': videoSources, 2868 'attributes': { 2869 'preload': false, 2870 'controls': true 2871 } 2872 }; 2873 2874 var attrs = { 2875 'data-src': '',
2876 'data-video': JSON.stringify(videoData), 2877 'data-thumb': posterUrl 2878 }; 2879 2880 var caption = extractCaption($slide); 2881 if (caption) { 2882 attrs['data-sub-html'] = caption; 2883 } 2884 2885 return { 2886 overlay: true, 2887 attrs: attrs 2888 }; 2889 } 2890 2891 /** 2892 * Create lightbox button for Custom HTML slides using iframe data URI approach (similar to Layer slides) 2893 */ 2894 function createCustomHtmlButton($slide) { 2895 var slideContent = $slide.html(); 2896 if (!slideContent || !slideContent.trim()) return null; 2897 2898 var responsiveCSS = ` 2899 .custom-html-content { 2900 width: 100% !important; 2901 max-width: 100% !important; 2902 height: 100vh !important; 2903 display: flex !important; 2904 align-items: center !important; 2905 justify-content: center !important; 2906 padding: 20px !important; 2907 box-sizing: border-box !important; 2908 background: #000 !important; 2909 color: white !important; 2910 font-family: Arial, sans-serif !important; 2911 overflow: auto !important; 2912 } 2913 .custom-html-content > div { 2914 max-width: 100% !important; 2915 max-height: 100% !important; 2916 } 2917 @media (max-width: 768px) { 2918 .custom-html-content { 2919 padding: 10px !important; 2920 } 2921 } 2922 `; 2923 2924 var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>'; 2925 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>'); 2926 2927 var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-custom-html" href="#">' + getButtonText() + '</a>'); 2928 2929 $button.attr({ 2930 'data-src': dataUri, 2931 'data-thumb': getPlaceholderThumb(), 2932 'data-iframe': 'true', 2933 'data-width': '90%', 2934 'data-height': '90%' 2935 }); 2936 2937 return $button; 2938 } 2939 2940 /** 2941 * Get overlay data for Custom HTML slides in direct-click mode using iframe data URI approach 2942 */ 2943 function getCustomHtmlOverlayData($slide) { 2944 var slideContent = $slide.html(); 2945 if (!slideContent || !slideContent.trim()) return null; 2946 2947 var responsiveCSS = ` 2948 .custom-html-content { 2949 width: 100% !important; 2950 max-width: 100% !important; 2951 height: 100vh !important; 2952 display: flex !important; 2953 align-items: center !important; 2954 justify-content: center !important; 2955 padding: 20px !important; 2956 box-sizing: border-box !important; 2957 background: #000 !important; 2958 color: white !important; 2959 font-family: Arial, sans-serif !important; 2960 overflow: auto !important; 2961 } 2962 .custom-html-content > div { 2963 max-width: 100% !important; 2964 max-height: 100% !important; 2965 } 2966 @media (max-width: 768px) { 2967 .custom-html-content { 2968 padding: 10px !important; 2969 } 2970 } 2971 `; 2972 2973 var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>'; 2974 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>'); 2975 2976 var attrs = { 2977 'data-src': dataUri, 2978 'data-thumb': getPlaceholderThumb(), 2979 'data-iframe': 'true', 2980 'data-width': '90%', 2981 'data-height': '90%' 2982 }; 2983 2984 var caption = extractCaption($slide); 2985 if (caption) { 2986 attrs['data-sub-html'] = caption; 2987 } 2988 2989 return { 2990 overlay: true, 2991 attrs: attrs 2992 }; 2993 } 2994 2995 /** 2996 * Initialize "Enlarge on Click" gallery - similar to initPlainImageGallery but for button-based galleries 2997 */ 2998 function initEnlargeOnClickGallery($gallery, useButtonMode) { 2999 var $wpContainers = $gallery.find('.wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger'); 3000 3001 if ($wpContainers.length <= 1) return; 3002 3003 if (useButtonMode) { 3004 $wpContainers.each(function() { 3005 var $container = $(this); 3006 var $img = $container.find('img').first(); 3007 var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 3008 3009 if ($img.length && $wpButton.length) { 3010 var imgSrc = $img.attr('src'); 3011 if (!imgSrc) return; 3012 3013 var fullUrl = removeWordPressSizeSuffix(imgSrc); 3014 3015 $container.attr({ 3016 'data-src': fullUrl,
3017 'data-thumb': imgSrc 3018 }); 3019 3020 var caption = extractCaption($img); 3021 if (caption) { 3022 $container.attr('data-sub-html', caption); 3023 } 3024 3025 var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-override" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>'); 3026 if (caption) { 3027 $button.attr('data-sub-html', caption); 3028 } 3029 3030 $wpButton.remove(); 3031 $container.css('position', 'relative').append($button); 3032 } 3033 }); 3034 3035 var gallerySettings = getLightboxSettings(true); 3036 gallerySettings.selector = '.ml-lightbox-button'; 3037 3038 } else { 3039 $wpContainers.each(function() { 3040 var $container = $(this); 3041 var $img = $container.find('img').first(); 3042 var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]'); 3043 3044 if ($img.length && $wpButton.length) { 3045 var imgSrc = $img.attr('src'); 3046 if (!imgSrc) return; 3047 3048 var fullUrl = removeWordPressSizeSuffix(imgSrc); 3049 3050 var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>'); 3051 3052 var caption = extractCaption($img); 3053 if (caption) { 3054 $wrapper.attr('data-sub-html', caption); 3055 } 3056 3057 $img.wrap($wrapper); 3058 $wpButton.remove(); 3059 } 3060 }); 3061 3062 var gallerySettings = getLightboxSettings(true); 3063 gallerySettings.selector = 'a[data-src]'; 3064 } 3065 3066 try { 3067 var instance = lightGallery($gallery[0], gallerySettings); 3068 $gallery.addClass('lg-initialized'); 3069 } catch (error) { 3070 console.error('MetaSlider Lightbox: Error initializing Enlarge on Click gallery:', error); 3071 } 3072 } 3073 3074 /** 3075 * Initialize "Link to Media File" gallery when manual override is enabled - similar to automatic processing but runs independently 3076 */ 3077 function initLinkToMediaManualGallery($gallery, useButtonMode) { 3078 var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() { 3079 return $(this).find('img').length > 0; 3080 }); 3081 3082 if ($mediaLinks.length <= 1) return; 3083 3084 if (useButtonMode) { 3085 $mediaLinks.each(function() { 3086 var $link = $(this); 3087 var href = $link.attr('href'); 3088 var $img = $link.find('img').first(); 3089 var imgSrc = $img.attr('src'); 3090 3091 if (!href || !imgSrc) return; 3092 3093 var $container = $link.closest('.wp-block-image'); 3094 if ($container.length === 0) { 3095 $container = $link.parent(); 3096 } 3097 3098 $container.attr({ 3099 'data-src': href, 3100 'data-thumb': imgSrc 3101 }); 3102 3103 var caption = extractCaption($img); 3104 if (caption) { 3105 $container.attr('data-sub-html', caption); 3106 } 3107 3108 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>'); 3109 if (caption) { 3110 $button.attr('data-sub-html', caption); 3111 } 3112 3113 $container.css('position', 'relative').append($button); 3114 }); 3115 3116 var gallerySettings = getLightboxSettings(true); 3117 gallerySettings.selector = '.ml-lightbox-button'; 3118 3119 } else { 3120 $mediaLinks.each(function() { 3121 var $link = $(this); 3122 var href = $link.attr('href'); 3123 var $img = $link.find('img').first(); 3124 var imgSrc = $img.attr('src'); 3125 3126 if (!href || !imgSrc) return; 3127 3128 $link.attr({ 3129 'data-src': href,
3130 'data-thumb': imgSrc 3131 }); 3132 3133 var caption = extractCaption($img); 3134 if (caption) { 3135 $link.attr('data-sub-html', caption); 3136 } 3137 }); 3138 3139 var gallerySettings = getLightboxSettings(true); 3140 gallerySettings.selector = 'a[data-src]'; 3141 } 3142 3143 try { 3144 var instance = lightGallery($gallery[0], gallerySettings); 3145 $gallery.addClass('lg-initialized'); 3146 } catch (error) { 3147 console.error('MetaSlider Lightbox: Error initializing Link to Media File manual gallery:', error); 3148 } 3149 } 3150 3151 function initWooCommerceGallery() { 3152 var $gallery = $('.woocommerce-product-gallery'); 3153 3154 if ($gallery.length === 0) { 3155 return; 3156 } 3157 3158 var useButtonMode = false; 3159 if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.metaslider_options) { 3160 useButtonMode = mlLightboxSettings.metaslider_options.show_lightbox_button !== false; 3161 } 3162 3163 var $images = $gallery.find('.woocommerce-product-gallery__image'); 3164 3165 if ($images.length === 0) { 3166 return; 3167 } 3168 3169 if (!useButtonMode) { 3170 $gallery.off('.wc-product-gallery-zoom'); 3171 $gallery.find('img').off('.wc-product-gallery-zoom'); 3172 $gallery.find('a').off('.wc-product-gallery-zoom'); 3173 3174 $images.each(function() { 3175 var $imageWrapper = $(this); 3176 var $img = $imageWrapper.find('img').first(); 3177 var $link = $imageWrapper.find('a').first(); 3178 3179 $img.css('pointer-events', 'none'); 3180 $link.css('cursor', 'pointer'); 3181 }); 3182 3183 $gallery.find('.zoomImg').remove(); 3184 3185 $images.find('a.ml-woo-product-image').each(function() { 3186 var $link = $(this); 3187 if ($link.hasClass('lg-initialized')) { 3188 var lgInstance = $link.data('lightGallery'); 3189 if (lgInstance) { 3190 lgInstance.destroy(true); 3191 } 3192 $link.removeClass('lg-initialized').removeAttr('data-lg-id'); 3193 } 3194 }); 3195 } 3196 3197 if ($gallery.hasClass('lg-initialized')) { 3198 var galleryInstance = $gallery.data('lightGallery'); 3199 if (galleryInstance) { 3200 galleryInstance.destroy(true); 3201 } 3202 $gallery.removeClass('lg-initialized'); 3203 } 3204 3205 $images.each(function() { 3206 var $imageWrapper = $(this); 3207 var $img = $imageWrapper.find('img').first(); 3208 var $link = $imageWrapper.find('a').first(); 3209 3210 if ($imageWrapper.find('.ml-lightbox-button').length > 0 && useButtonMode) { 3211 return; 3212 } 3213 3214 if (useButtonMode) { 3215 var fullUrl = $img.attr('data-full-url'); 3216 var thumbUrl = $img.attr('data-thumb-url'); 3217 3218 if (!fullUrl || !thumbUrl) { 3219 return; 3220 } 3221 3222 var $button = $('<a href="#" class="ml-lightbox-button ml-button-woo">' + getButtonText() + '</a>'); 3223 $button.attr({ 3224 'data-src': fullUrl, 3225 'data-thumb': thumbUrl 3226 }); 3227 3228 $imageWrapper.css('position', 'relative').append($button); 3229 } 3230 }); 3231 3232 var gallerySettings = getLightboxSettings(true); 3233 3234 if (useButtonMode) { 3235 gallerySettings.selector = '.ml-lightbox-button'; 3236 } else { 3237 gallerySettings.selector = '.ml-woo-product-image'; 3238 } 3239 3240 gallerySettings.galleryId = 'ml-woo-gallery-' + Date.now(); 3241 3242 try { 3243 var instance = lightGallery($gallery[0], gallerySettings); 3244 $gallery.addClass('lg-initialized'); 3245 3246 $gallery.on('found_variation', function(event, variation) { 3247 if (instance && typeof instance.refresh === 'function') { 3248 setTimeout(function() { 3249 instance.refresh(); 3250 }, 100); 3251 } 3252 }); 3253 3254 $gallery.on('reset_data', function() { 3255 if (instance && typeof instance.refresh === 'function') { 3256 setTimeout(function() { 3257 instance.refresh(); 3258 }, 100); 3259 } 3260 }); 3261 3262 } catch (error) { 3263 console.error('MetaSlider Lightbox: Error initializing WooCommerce gallery:', error); 3264 } 3265 } 3266 3267 window.mlInitWooCommerceGallery = initWooCommerceGallery; 3268 3269 // Exposed for unit tests only â not a public API. 3270 window.__mlLightboxTestable = { 3271 CONSTANTS: CONSTANTS, 3272 escapeHtml: escapeHtml, 3273 ensureImageAltAttribute: ensureImageAltAttribute, 3274 removeConflictingAttributes: removeConflictingAttributes, 3275 slideHasCustomLink: slideHasCustomLink, 3276 getImageOverlayData: getImageOverlayData, 3277 createImageButton: createImageButton 3278 }; 3279 3280})(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.