1// ngg_mosaic_methods does not use any jQuery so it can begin loading gallery images before 2// jQuery's init / window.ready is fired 3var ngg_mosaic_methods = { 4 5 high_dpi: false, 6 7 init: function() { 8 this.high_dpi = this.getDPIRatio() > 1; 9 10 _.each(document.getElementsByClassName('ngg-pro-mosaic-container'), function(self) { 11 var gallery_id = self.getAttribute('data-ngg-pro-mosaic-id'); 12 var gallery = ngg_mosaic_methods.get_gallery(gallery_id); 13 14 const container = self.parentElement; 15 const styleName = 'firstload'; 16 17 // Images in the template are deferred from loading until here 18 if (ngg_mosaic_methods.get_setting(gallery_id, 'lazy_load_enable', false, 'bool')) { 19 var initial_load = ngg_mosaic_methods.get_setting(gallery.ID, 'lazy_load_initial', 35, 'int'); 20 gallery.mosaic_loaded = []; 21 _.each( 22 gallery.images_list, 23 function(image, index) { 24 if (index < initial_load) { 25 gallery.mosaic_loaded.push(image.image_id); 26 self.appendChild(ngg_mosaic_methods.create_image( 27 gallery, 28 image, 29 undefined, 30 '1' === self.parentElement.dataset['firstloadEnabled'] 31 )); 32 } 33 } 34 ); 35 } else { 36 _.each( 37 gallery.images_list, 38 function(image) { 39 self.appendChild(ngg_mosaic_methods.create_image( 40 gallery, 41 image, 42 undefined, 43 '1' === self.parentElement.dataset['firstloadEnabled'] 44 )); 45 } 46 ); 47 } 48 49 // Add support for the Image Animations feature; first load and pagination. 50 if ('1' === container.dataset[`${styleName}Enabled`] ) { 51 const style = container.dataset[`${styleName}Style`]; 52 const duration = parseInt(container.dataset[`${styleName}Duration`]); 53 const delay = parseInt(container.dataset[`${styleName}Delay`]); 54 const items = self.querySelectorAll('.ngg-pro-mosaic-item'); 55 items.forEach( 56 function(image, index) { 57 image.style.setProperty('--animate-duration', `${duration}ms`); 58 if (index === (items.length - 1)) { //} && '1' === container.dataset['paginationEnabled']) { 59 setTimeout(() => { 60 animateNextGENImage(image, style).then((message) => { 61 // Images in the template are deferred from loading until here 62 if (ngg_mosaic_methods.get_setting(gallery_id, 'lazy_load_enable', false, 'bool')) { 63 jQuery(window).on('scroll', function() { 64 window.ngg_mosaic_methods_two.scroll_action(gallery.ID, self); 65 }); 66 jQuery(window).trigger('scroll'); 67 } 68 return "Message received"; 69 }); 70 }, (0 === index ? 0 : index * delay)); 71 } else { 72 setTimeout(() => { 73 animateNextGENImage(image, style); 74 }, (0 === index ? 0 : index * delay)); 75 } 76 } 77 ); 78 } else { 79 if ('1' === container.dataset['paginationEnabled']) { 80 // Images in the template are deferred from loading until here 81 if (ngg_mosaic_methods.get_setting(gallery_id, 'lazy_load_enable', false, 'bool')) { 82 jQuery(window).on('scroll', function() { 83 window.ngg_mosaic_methods_two.scroll_action(gallery.ID, self); 84 }); 85 jQuery(window).trigger('scroll'); 86 } 87 } 88 } 89 }); 90 }, 91 92 getDPIRatio: function() { 93 var ratio = 1; 94 95 // To account for zoom, change to use deviceXDPI instead of systemXDPI 96 if (window.screen.systemXDPI !== undefined 97 && window.screen.logicalXDPI !== undefined 98 && window.screen.systemXDPI > window.screen.logicalXDPI) { 99 // Only allow for values > 1 100 ratio = window.screen.systemXDPI / window.screen.logicalXDPI; 101 } else if (window.devicePixelRatio !== undefined) { 102 ratio = window.devicePixelRatio; 103 } 104 105 return ratio; 106 }, 107 108 get_gallery: function (gallery_id) { 109 var result = null; 110 111 if ('undefined' == typeof window.galleries) { 112 return result; 113 } 114 115 return _.find(galleries, function(gallery) { 116 return (gallery.ID == gallery_id); 117 }); 118 }, 119 120 get_setting: function(gallery_id, name, def, type) { 121 type = type || 'bool'; 122 var gallery = this.get_gallery(gallery_id); 123 124 if (gallery && typeof gallery.display_settings[name] != 'undefined') 125 def = gallery.display_settings[name]; 126 127 if (type == 'bool') { 128 if (def == 1 || def == '1') 129 def = true; 130 if (def == 0 || def == '0') 131 def = false;
132 } else if (type == 'int') { 133 def = parseInt(def); 134 } else if (type == 'string') { 135 // don't need to do anything, for now 136 } 137 138 return def; 139 }, 140 141 create_image: function(gallery, image, onload, addAnimationClass) { 142 var self = this; 143 144 var div = document.createElement('div'); 145 div.setAttribute('class', 'ngg-pro-mosaic-item'); 146 147 if (addAnimationClass) { 148 div.classList.add('nextgen-pro-animate-images-loading'); 149 } 150 151 var img = document.createElement('img'); 152 var url = image.image; 153 if (self.high_dpi && image.use_hdpi) { 154 url = image.srcsets.hdpi; 155 } 156 157 img.setAttribute('title', sanitizeToPlainText(image.title)); 158 img.setAttribute('alt', sanitizeToPlainText(image.title)); 159 img.setAttribute('src', url); 160 img.setAttribute('width', image.width); 161 img.setAttribute('height', image.height); 162 163 // Yes, it's messy, but it's a decently clean way to inject our effect code text into the element 164 var anchor = document.createElement('a'); 165 var clauses = gallery.mosaic_effect_code.match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/ig); 166 if (clauses) { 167 for (var i = 0; i < clauses.length; i++) { 168 var claus = clauses[i].match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/i); 169 anchor.setAttribute(claus[1], claus[2]); 170 } 171 } 172 173 anchor.setAttribute('href', image.full_image); 174 anchor.setAttribute('title', image.description); 175 anchor.setAttribute('data-image-id', image.image_id); 176 anchor.setAttribute('data-src', image.image); 177 anchor.setAttribute('data-thumbnail', image.thumb); 178 anchor.setAttribute('data-title', image.title); 179 anchor.setAttribute('data-description', image.description); 180 anchor.setAttribute('data-image-name', image.full_image ? image.full_image.split('/').pop().split('?')[0] : (image.title || '')); 181 anchor.setAttribute('data-ngg-captions-nostylecopy', '1'); 182 183 if (image.tiktok_play_url) { 184 anchor.setAttribute('data-tiktok-play-url', image.tiktok_play_url); 185 } 186 if (image.tiktok_share_url) { 187 anchor.setAttribute('data-tiktok-share-url', image.tiktok_share_url); 188 } 189 if (image.tiktok_embed_url || image.tiktok_embed_link) { 190 anchor.setAttribute('data-tiktok-embed-url', image.tiktok_embed_url || image.tiktok_embed_link); 191 } 192 if (image.video_link) { 193 anchor.setAttribute('data-video-url', image.video_link); 194 } 195 196 // Add Dribbble link data attributes if this is a Dribbble image with external link. 197 if (image.is_dribbble === 1 || image.is_dribbble === '1' || image.is_dribbble === true) { 198 anchor.setAttribute('data-ngg-dribbble', '1'); 199 anchor.setAttribute('data-dribbble-link', image.dribbble_link); 200 if (image.dribbble_target && image.dribbble_target === '_blank') { 201 anchor.setAttribute('data-dribbble-target', '_blank'); 202 } 203 } 204 205 if (typeof onload != 'undefined') { 206 anchor.setAttribute('onload', onload); 207 } 208 209 anchor.appendChild(img); 210 211 if ((image.is_tiktok == 1 || image.is_tiktok == '1' || image.is_tiktok === true) 212 && (image.show_play_button == 1 || image.show_play_button == '1' || image.show_play_button === true)) { 213 var overlay = document.createElement('span'); 214 overlay.setAttribute('class', 'ngg-video-play-overlay'); 215 overlay.setAttribute('aria-hidden', 'true'); 216 anchor.appendChild(overlay); 217 } else if ((image.is_video_link == 1 || image.is_video_link == '1' || image.is_video_link === true) 218 && image.video_link && image.video_link.length > 0) { 219 // Generic video: add play overlay 220 var overlay = document.createElement('span'); 221 overlay.setAttribute('class', 'ngg-video-play-overlay'); 222 overlay.setAttribute('aria-hidden', 'true'); 223 anchor.appendChild(overlay); 224 } 225 div.appendChild(anchor); 226 227 return div; 228 } 229}; 230 231function sanitizeToPlainText(value) { 232 // Create a temporary element to decode HTML entities and remove HTML tags. 233 const tempDiv = jQuery("<div>").html(value); 234 let plainText = tempDiv.text(); // Get plain text (decodes entities and removes tags). 235 tempDiv.remove(); // Clean up the temporary element. 236 237 // Remove special characters, including quotes and backticks. 238 plainText = plainText.replace(/['"`~!@#$%^&*()_+=\[\]{}|;:,.<>?/\\-]/g, ""); 239 240 // Trim any extra spaces. 241 return plainText.trim(); 242} 243 244(function($) { 245 "use strict"; 246 247 var mosaic_methods = { 248 // This image has loaded: remove it from the pending queue 249 image_loaded: function(image_id) { 250 this.scroll_pending = _.without(this.scroll_pending, image_id); 251 }, 252 253 // Prevent the listener action from running concurrently 254 scroll_action_running: null, 255 256 // Once scroll_action() fires we don't want to run again for 300ms 257 scroll_timer: null, 258 259 // how many more images to fetch 260 scroll_step: 5, 261 262 // a queue of pending/downloading images 263 scroll_pending: [], 264 265 scroll_more_to_find: true, 266 267 scroll_action: function(gallery_id, myself) { 268 // Just bail now 269 if (this.scroll_action_running == true 270 || this.scroll_timer 271 || !this.scroll_more_to_find 272 || (this.scroll_pending.length >= 1)) { 273 return; 274 } 275 276 this.scroll_action_running = true; 277 var self = this; 278 279 this.scroll_timer = setTimeout(function() { 280 clearTimeout(self.scroll_timer); 281 self.scroll_timer = false;
282 }, 500); 283 284 setTimeout(function() { 285 var $self = $(myself); 286 var window_height = $(window).height(); 287 var window_position = $(window).scrollTop(); 288 var window_bottom = window_height + window_position; 289 var container_bottom = $self.height() + $self.offset().top; 290 var row_height = $self.find('.ngg-pro-mosaic-item.entry-visible').first().height(); 291 var last_height = $self.find('.ngg-pro-mosaic-item.entry-visible').last().height(); 292 293 if (window_bottom <= (container_bottom - last_height - (row_height * 2))) { 294 self.scroll_action_running = false; 295 return; 296 } 297 298 var gallery = ngg_mosaic_methods.get_gallery(gallery_id); 299 300 var scroll_step = ngg_mosaic_methods.get_setting(gallery_id, 'lazy_load_batch', this.scroll_step, 'int'); 301 302 // If there's no more images to find: stop checking in the future 303 if ((gallery.images_list.length - gallery.mosaic_loaded.length) < scroll_step) { 304 scroll_step = gallery.images_list.length - gallery.mosaic_loaded.length; 305 self.scroll_more_to_find = false; 306 } 307 308 let newImages = []; 309 const container = myself.parentElement; 310 const styleName = 'pagination'; 311 312 // Find X images not already added and append them 313 for (var i = 0; i < scroll_step; i++) { 314 var image = _.find(gallery.images_list, function(image) { 315 return (_.indexOf(gallery.mosaic_loaded, image.image_id) == -1) 316 }); 317 318 gallery.mosaic_loaded.push(image.image_id); 319 let newImage = ngg_mosaic_methods.create_image( 320 gallery, 321 image, 322 self.image_loaded(image.image_id), 323 '1' === container.dataset[`${styleName}Enabled`] 324 ); 325 newImages.push(newImage); 326 $self.append(newImage); 327 } 328 329 // Have JustifiedGallery update *new* images only 330 $self.justifiedGallery('norewind'); 331 332 // Add support for the 'pagination' Image Animations feature. 333 if ('1' === container.dataset[`${styleName}Enabled`] ) { 334 const style = container.dataset[`${styleName}Style`]; 335 const duration = parseInt(container.dataset[`${styleName}Duration`]); 336 const delay = parseInt(container.dataset[`${styleName}Delay`]); 337 338 newImages.forEach( 339 function(image, index) { 340 image.style.setProperty('--animate-duration', `${duration}ms`); 341 setTimeout(() => { 342 animateNextGENImage(image, style); 343 }, (0 === index ? 0 : index * delay)); 344 } 345 ); 346 } 347 348 // Allow the scroll method to be caught again 349 self.scroll_action_running = false; 350 }, 60); 351 } 352 }; 353 354 window.ngg_mosaic_methods_two = mosaic_methods; 355 356 ngg_mosaic_methods.init(); 357 358 _.each($('.ngg-pro-mosaic-container'), function(self) { 359 360 var $self = $(self); 361 var gallery_id = $self.data('ngg-pro-mosaic-id'); 362 363 var jgoptions = { 364 captions: false, 365 cssAnimation: true, 366 waitThumbnailsLoad: false, 367 justifyThreshold: 0.9, 368 rowHeight: ngg_mosaic_methods.get_setting(gallery_id, 'row_height', 200, 'int'), 369 margins: ngg_mosaic_methods.get_setting(gallery_id, 'margins', 15, 'int'), 370 lastRow: ngg_mosaic_methods.get_setting(gallery_id, 'last_row', 'justify', 'string') 371 }; 372 373 var gallery = ngg_mosaic_methods.get_gallery(gallery_id); 374 375 // Guarantee the container has a base size 376 $self.css({ 377 'min-height': ngg_mosaic_methods.get_setting(gallery_id, 'row_height', 200, 'int') + 'px' 378 }); 379 380 if ('undefined' === typeof self.parentElement.dataset['paginationEnabled'] 381 || '0' === self.parentElement.dataset['paginationEnabled']) { 382 // Images in the template are deferred from loading until here
383 if (ngg_mosaic_methods.get_setting(gallery_id, 'lazy_load_enable', false, 'bool')) { 384 $(window).on('scroll', function() { 385 mosaic_methods.scroll_action(gallery.ID, self); 386 }); 387 } 388 } 389 390 // wait until JG has processed images before trying to update lightboxes 391 $self.on('jg.complete', function() { 392 $(window).trigger('ngg.mosaic.complete', [$self]); 393 $(window).trigger('resize', [$self]); 394 395 // Our captions can't process correctly if the images haven't loaded yet 396 $self.waitForImages(function() { 397 $(window).trigger('refreshed'); 398 }); 399 }); 400 401 $self.justifiedGallery(jgoptions); 402 403 // The below disableRefresh / enableRefresh commands have been altered by Imagely and are not part of 404 // the standard Justified-Gallery distribution. The following prevents JG from recalculating gallery sizes 405 // and thus changing the scroll position when our Pro Lightbox is active. 406 $(window).on('npl_initialization_complete', function() { 407 $('#npl_content').on('npl_opening', function() { 408 $self.justifiedGallery('disableRefresh'); 409 }); 410 $('#npl_content').on('npl_closing', function() { 411 $self.justifiedGallery('enableRefresh'); 412 }); 413 }); 414 415 }); 416 417})(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.