1/** 2 * Photo Swipe on Product Images 3 * 4 * @package woostify 5 */ 6 7'use strict'; 8 9function initPhotoSwipe(gallerySelector) { 10 var added = false; 11 12 // Helper function to extract YouTube video ID 13 var getYoutubeId = function (url) { 14 var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|shorts\/)([^#\&\?]*).*/; 15 var match = url.match(regExp); 16 if (match && match[2].length == 11) { 17 return match[2]; 18 } else { 19 return null; 20 } 21 }; 22 23 // parse slide data (url, title, size ...) from DOM elements 24 // (children of gallerySelector). 25 var parseThumbnailElements = function (el) { 26 var thumbElements = el.childNodes, 27 numNodes = thumbElements.length, 28 items = [], 29 figureEl, 30 linkEl, 31 size, 32 item; 33 34 for (var i = 0; i < numNodes; i++) { 35 36 figureEl = thumbElements[i]; // <figure> element. 37 38 // include only element nodes. 39 if (1 !== figureEl.nodeType) { 40 continue; 41 } 42 43 // Check if this is a video item 44 var isVideo = figureEl.classList.contains('has-video'); 45 var videoSource = figureEl.getAttribute('data-video-source'); 46 var videoUrl = figureEl.getAttribute('data-video-url'); 47 var videoAutoplay = figureEl.getAttribute('data-video-autoplay'); 48 var videoMute = figureEl.getAttribute('data-video-mute'); 49 50 linkEl = figureEl.children[0]; // <a> element. 51 if (!linkEl.getAttribute('href') && !isVideo) { 52 continue; 53 } 54 55 if (isVideo && videoUrl) { 56 // Video item 57 item = { 58 html: '', // Will be populated dynamically 59 w: 1280, 60 h: 720, 61 isVideo: true, 62 videoSource: videoSource || 'youtube', 63 videoUrl: videoUrl, 64 videoAutoplay: videoAutoplay, 65 videoMute: videoMute 66 }; 67 68 if (linkEl.children.length > 0) { 69 item.msrc = linkEl.children[0].getAttribute('src'); 70 } 71 } else { 72 // Image item 73 size = linkEl.getAttribute('data-size').split('x'); 74 75 item = { 76 src: linkEl.getAttribute('href'), 77 w: parseInt(size[0], 10), 78 h: parseInt(size[1], 10) 79 }; 80 81 if (linkEl.children.length > 0) { 82 item.msrc = linkEl.children[0].getAttribute('src'); 83 } 84 } 85 86 item.el = figureEl; // save link to element for getThumbBoundsFn. 87 items.push(item); 88 } 89 90 return items; 91 }; 92 93 // find nearest parent element. 94 var closest = function closest(el, fn) { 95 return el && (fn(el) ? el : closest(el.parentNode, fn)); 96 }; 97 98 var onToggleButtonClick = function (e) { 99 e = e || window.event; 100 e.preventDefault ? e.preventDefault() : e.returnValue = false; 101 102 var eTarget = e.target || e.srcElement; 103 104 var productImages = eTarget.closest('.product-images'); 105 106 var clickedListItem = productImages.querySelectorAll('.image-item')[0]; 107 108 // find root element of slide. 109 var slider = productImages.querySelector('.flickity-slider'); 110 if (slider) { 111 clickedListItem = productImages.querySelector('.image-item.is-selected'); 112 } 113 114 if (!clickedListItem) { 115 return; 116 } 117 118 // find index of clicked item by looping through all child nodes 119 // alternatively, you may define index via data- attribute. 120 var clickedGallery = clickedListItem.parentNode, 121 childNodes = clickedListItem.parentNode.childNodes, 122 numChildNodes = childNodes.length, 123 nodeIndex = 0, 124 index; 125 126 for (var i = 0; i < numChildNodes; i++) { 127 if (childNodes[i].nodeType !== 1) { 128 continue; 129 } 130 131 if (childNodes[i] === clickedListItem) { 132 index = nodeIndex; 133 break; 134 } 135 nodeIndex++; 136 } 137 138 if (index >= 0) { 139 // open PhotoSwipe if valid index found. 140 openPhotoSwipe(index, clickedGallery); 141 } 142 return false; 143 } 144 145 // triggers when user clicks on thumbnail. 146 var onThumbnailsClick = function (e) { 147 e = e || window.event; 148 e.preventDefault ? e.preventDefault() : e.returnValue = false; 149 150 var eTarget = e.target || e.srcElement; 151 152 if ('A' === eTarget.tagName.toUpperCase()) { 153 return; 154 } 155 156 // find root element of slide. 157 var clickedListItem = closest( 158 eTarget, 159 function (el) { 160 return (el.tagName && 'FIGURE' === el.tagName.toUpperCase()); 161 } 162 ); 163 164 if (!clickedListItem) { 165 return; 166 } 167 168 // find index of clicked item by looping through all child nodes 169 // alternatively, you may define index via data- attribute. 170 var clickedGallery = clickedListItem.parentNode, 171 childNodes = clickedListItem.parentNode.childNodes, 172 numChildNodes = childNodes.length, 173 nodeIndex = 0, 174 index; 175 176 for (var i = 0; i < numChildNodes; i++) { 177 if (childNodes[i].nodeType !== 1) { 178 continue; 179 } 180 181 if (childNodes[i] === clickedListItem) { 182 index = nodeIndex; 183 break; 184 } 185 nodeIndex++; 186 } 187 188 if (index >= 0) {
189 // open PhotoSwipe if valid index found. 190 openPhotoSwipe(index, clickedGallery); 191 } 192 return false; 193 }; 194 195 var openPhotoSwipe = function (index, galleryElement, disableAnimation, fromURL) { 196 var productGallery = galleryElement.closest('.product-gallery'); 197 198 var pswpElement, 199 gallery, 200 options, 201 items; 202 203 if (productGallery.querySelector('.pswp')) { 204 pswpElement = productGallery.querySelector('.pswp'); 205 } else { 206 pswpElement = productGallery.nextElementSibling; 207 } 208 209 items = parseThumbnailElements(galleryElement); 210 211 // define options (if needed). 212 options = { 213 214 // define gallery index (for URL). 215 galleryUID: galleryElement.getAttribute('data-pswp-uid'), 216 217 getThumbBoundsFn: function (index) { 218 // See Options -> getThumbBoundsFn section of documentation for more info. 219 var thumbnail = items[index].el.getElementsByTagName('img')[0], // find thumbnail. 220 pageYScroll = window.pageYOffset || document.documentElement.scrollTop, 221 rect = thumbnail.getBoundingClientRect(); 222 223 return { 224 x: rect.left, 225 y: rect.top + pageYScroll, 226 w: rect.width 227 }; 228 } 229 230 }; 231 232 // PhotoSwipe opened from URL. 233 if (fromURL) { 234 if (options.galleryPIDs) { 235 // parse real index when custom PIDs are used 236 // http://photoswipe.com/documentation/faq.html#custom-pid-in-url. 237 for (var j = 0, ji = items.length; j < ji; j++) { 238 if (items[j].pid == index) { 239 options.index = j; 240 break; 241 } 242 } 243 } else { 244 // in URL indexes start from 1. 245 options.index = parseInt(index, 10) - 1; 246 } 247 } else { 248 options.index = parseInt(index, 10); 249 } 250 251 // exit if index not found. 252 if (isNaN(options.index)) { 253 return; 254 } 255 256 if (disableAnimation) { 257 options.showAnimationDuration = 0; 258 } 259 260 // Pass data to PhotoSwipe and initialize it. 261 gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options); 262 gallery.init(); 263 264 // Handle video rendering 265 gallery.listen('gettingData', function (index, item) { 266 if (item.isVideo && !item.html) { 267 var videoHtml = ''; 268 269 if (item.videoSource === 'mp4') { 270 // MP4 Video 271 videoHtml = '<div class="pswp__video-container">'; 272 videoHtml += '<video controls autoplay'; 273 274 if (item.videoMute == 1 || item.videoAutoplay == 1) { 275 videoHtml += ' muted'; 276 } 277 278 videoHtml += ' playsinline style="width:100%;height:100%;object-fit:contain;">'; 279 videoHtml += '<source src="' + item.videoUrl + '" type="video/mp4">'; 280 videoHtml += 'Your browser does not support the video tag.'; 281 videoHtml += '</video>'; 282 videoHtml += '</div>'; 283 } else { 284 // YouTube Video 285 var videoId = getYoutubeId(item.videoUrl); 286 if (videoId) { 287 var embedUrl = 'https://www.youtube.com/embed/' + videoId + '?autoplay=1&rel=0&enablejsapi=1'; 288 289 if (item.videoMute == 1 || item.videoAutoplay == 1) { 290 embedUrl += '&mute=1'; 291 } 292 293 videoHtml = '<div class="pswp__video-container">'; 294 videoHtml += '<iframe src="' + embedUrl + '" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="width:100%;height:100%;"></iframe>'; 295 videoHtml += '</div>'; 296 } 297 } 298 299 item.html = videoHtml; 300 } 301 }); 302 303 var selector = '.pswp__thumbnails'; 304 305 gallery.listen( 306 'gettingData', 307 function () { 308 if (added) { 309 return; 310 } 311 added = true; 312 313 var oldThumbnailEls = document.querySelectorAll(selector); 314 if (oldThumbnailEls.length) { 315 oldThumbnailEls.forEach( 316 function (odlThumb) { 317 odlThumb.remove(); 318 } 319 ) 320 } 321 setTimeout( 322 function () { 323 addPreviews(gallery); 324 }, 325 200 326 ); 327 } 328 ) 329 330 gallery.listen( 331 'close', 332 function () { 333 var scrollWrap = gallery.scrollWrap; 334 var pswpThumbEl = scrollWrap.closest('.pswp').querySelector('.pswp__thumbnails'); 335 336 // Stop all videos when closing lightbox 337 var videos = scrollWrap.querySelectorAll('video'); 338 for (var i = 0; i < videos.length; i++) { 339 videos[i].pause(); 340 } 341 342 var iframes = scrollWrap.querySelectorAll('iframe'); 343 for (var j = 0; j < iframes.length; j++) { 344 iframes[j].src = ''; 345 } 346 347 if (!pswpThumbEl) { 348 return; 349 } 350 351 pswpThumbEl.remove(); 352 added = false;
353 } 354 ) 355 gallery.listen( 356 'afterChange', 357 function () { 358 var scrollWrap = gallery.scrollWrap; 359 var pswpThumbEl = scrollWrap.closest('.pswp').querySelector('.pswp__thumbnails'); 360 361 if (!pswpThumbEl) { 362 return; 363 } 364 365 Object.keys(gallery.items).forEach( 366 function (k) { 367 var currThumbItem = pswpThumbEl.children[k]; 368 369 currThumbItem.classList.remove('active'); 370 371 if (gallery.getCurrentIndex() == k) { 372 currThumbItem.classList.add('active'); 373 } 374 } 375 ) 376 } 377 ) 378 }; 379 380 function addPreviews(gallery) { 381 var scrollWrap = gallery.scrollWrap; 382 var productImagesWrapperEl = document.querySelector('.product-gallery'); 383 var thumbnailEl = document.createElement('div'); 384 thumbnailEl.classList.add('pswp__thumbnails'); 385 386 if (!productImagesWrapperEl) { 387 return; 388 } 389 390 var productThumbWrapperEl = productImagesWrapperEl.querySelector('.product-thumbnail-images-container'); 391 392 if (!productThumbWrapperEl) { 393 Object.keys(gallery.items).forEach( 394 function (k) { 395 var currItem = gallery.items[k]; 396 var newThumbEl = document.createElement('div'); 397 var newImgEl = document.createElement('img'); 398 399 newImgEl.setAttribute('src', currItem.msrc); 400 newThumbEl.classList.add('thumbnail-item'); 401 newThumbEl.appendChild(newImgEl) 402 thumbnailEl.appendChild(newThumbEl); 403 } 404 ) 405 } else { 406 var thumbSlider = productThumbWrapperEl.querySelector('.flickity-slider'); 407 if (thumbSlider) { 408 thumbnailEl.innerHTML = thumbSlider.innerHTML; 409 } else { 410 thumbnailEl.innerHTML = productThumbWrapperEl.innerHTML; 411 } 412 } 413 414 Object.keys(gallery.items).forEach( 415 function (k) { 416 var currThumbItem = thumbnailEl.children[k]; 417 currThumbItem.removeAttribute('style'); 418 currThumbItem.classList.remove('is-selected', 'is-nav-selected'); 419 420 if (gallery.getCurrentIndex() == k) { 421 currThumbItem.classList.add('active'); 422 } 423 424 // Add play icon for video items 425 if (gallery.items[k].isVideo) { 426 currThumbItem.classList.add('has-video-thumb'); 427 var playIcon = document.createElement('div'); 428 playIcon.className = 'pswp__thumb-play-icon'; 429 playIcon.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>'; 430 currThumbItem.appendChild(playIcon); 431 } 432 433 currThumbItem.addEventListener( 434 'click', 435 function () { 436 gallery.goTo(gallery.items.indexOf(gallery.items[k])) 437 } 438 ) 439 } 440 ) 441 442 scrollWrap.parentNode.insertBefore(thumbnailEl, scrollWrap.nextSibling); 443 } 444 445 // loop through all gallery elements and bind events. 446 var galleryElements = document.querySelectorAll(gallerySelector); 447 for (var i = 0, l = galleryElements.length; i < l; i++) { 448 var buttonEl = galleryElements[i].closest('.product-images').querySelector('.photoswipe-toggle-button'); 449 450 galleryElements[i].setAttribute('data-pswp-uid', i + 1); 451 452 buttonEl.onclick = onToggleButtonClick; 453 galleryElements[i].onclick = onThumbnailsClick; 454 } 455} 456 457initPhotoSwipe('.product-images-container');
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.