1(function($) { 2 "use strict"; 3 4 function nplModal() { 5 6 var core = { 7 debug: false, 8 debug_level: 1, // 1-4 9 state: { 10 slug: null, 11 gallery_id: null, 12 image_id: null, 13 sidebar: null 14 }, 15 selector: nextgen_lightbox_filter_selector($, $(".nextgen_pro_lightbox")), 16 options: {}, 17 defaults: { 18 speed: 'medium', // see jQuery docs for setting 19 20 // the url is restored to this location when the lightbox closes 21 initial_url: window.location.toString().split('#')[0], 22 23 ajax_url: photocrati_ajax.rest_url, 24 router_slug: 'gallery' 25 }, 26 27 init: function(parameters) { 28 core.methods.log('nplModal.init()', { 29 parameters: parameters, 30 defaults: core.defaults, 31 selector: core.selector, 32 debug: core.debug, 33 debug_level: core.debug_level 34 }); 35 36 var overlay = $("<div id='npl_overlay'></div>"); 37 var wrapper = $("<div id='npl_wrapper'></div>"); 38 var spinner = $("<div id='npl_spinner_container' class='npl-loading-spinner hidden'><i id='npl_spinner' class='fas fa-spin fa-spinner'></i></div>"); 39 var btn_close = $("<div id='npl_button_close' class='hidden'><i class='fas fa-xmark'></i></div>"); 40 var content = $("<div id='npl_content'></div>"); 41 var sidebar = $("<div id='npl_sidebar'></div>"); 42 var s_overlay = $("<div id='npl_sidebar_overlay'><i class='fas fa-spin fa-spinner'/></div>"); 43 var toggle = $("<i id='npl_sidebar_toggle' class='fas fa-arrow-right'/>"); 44 45 if (core.methods.mobile.browser.ios()) { 46 overlay.addClass('npl_ios_no_opacity'); 47 wrapper.addClass('npl_ios_hack'); 48 } 49 if (core.methods.is_ie9()) { 50 wrapper.addClass('npl_ie9_flex_hack'); 51 } 52 53 // Provide a hook for third-parties to add their own methods 54 $(window).trigger('override_nplModal_methods', core.methods); 55 56 overlay.css({background: core.methods.get_setting('background_color')}); 57 spinner.css({color: core.methods.get_setting('icon_color')}); 58 s_overlay.css({color: core.methods.get_setting('icon_color')}); 59 btn_close.css({color: core.methods.get_setting('overlay_icon_color')}); 60 sidebar.css({background: core.methods.get_setting('sidebar_background_color')}); 61 toggle.css({color: core.methods.get_setting('overlay_icon_color')}); 62 63 var body = $('body'); 64 body.append(overlay); 65 body.append(wrapper); 66 wrapper.append(spinner); 67 wrapper.append(btn_close); 68 wrapper.append(toggle); 69 wrapper.append(content); 70 wrapper.append(sidebar); 71 wrapper.append(s_overlay); 72 73 // get_setting() isn't available when declaring the base defaults 74 parameters = $.extend(parameters, {router_slug: core.methods.get_setting('router_slug')}); 75 core.options = $.extend(core.defaults, parameters); 76 77 core.methods.bind_images(); 78 core.methods.set_events(); 79 core.methods.mobile.init(); 80 81 // Restrict the padding so that we at *least* have half of the screen to display on 82 if (parseInt(core.methods.get_setting('padding', '0')) > 0) { 83 var space = core.methods.get_setting('padding', '0'); 84 var space_unit = core.methods.get_setting('padding_unit', 'px'); 85 var maxWidth = window.innerWidth / 4; 86 var maxHeight = window.innerHeight / 4; 87 var maxDimension = Math.min(maxWidth, maxHeight); 88 89 if (space_unit === 'px' && space > maxDimension) { 90 space = maxDimension; 91 }
92 else if (space_unit === '%' && space > 25) { 93 space = 25; 94 } 95 96 space = space + space_unit; 97 98 $("<style type='text/css'>#npl_wrapper.npl_open_with_padding {" 99 + 'top: ' + space + ';' 100 + 'bottom: ' + space + ';' 101 + 'left: ' + space + ';' 102 + 'right: ' + space + ';' 103 + " } </style>").appendTo("head"); 104 } 105 106 core.methods.router.routes.push({ 107 re: new RegExp('^' + core.options.router_slug + '\/(.*)\/(.*)\/(.*)$', 'i'), 108 handler: core.methods.url_handler 109 }); 110 111 core.methods.router.routes.push({ 112 re: new RegExp('^' + core.options.router_slug + '\/(.*)\/(.*)$', 'i'), 113 handler: core.methods.url_handler 114 }); 115 116 core.methods.router.routes.push({ 117 re: new RegExp('^' + core.options.router_slug + '$', 'i'), 118 handler: core.methods.close_modal 119 }); 120 121 core.methods.router.routes.push({ 122 re: '', 123 handler: core.methods.close_modal 124 }); 125 126 // Ask the router to listen for routing requests 127 // Safari and Opera don't fire the load event when the cache 128 // has been cached, and the pageshow event should be used instead 129 /** 130 SEE: 131 ------ 132 Webkit: http://webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ 133 Firefox: https://developer.mozilla.org/En/Using_Firefox_1.5_caching. 134 Chrome: https://code.google.com/p/chromium/issues/detail?id=2879 135 */ 136 ['load', 'pageshow'].map(function(eventType){ 137 setTimeout(function() { 138 core.methods.router.listen(); 139 }, 200); 140 }) 141 142 // Hack for iOS and some Android browsers that can't handle position:fixed when the keyboard is open 143 if (core.methods.mobile.browser.ios() 144 || core.methods.mobile.browser.android()) { 145 wrapper.addClass('npl_mobile'); 146 } 147 148 $(window).trigger('npl_initialization_complete'); 149 }, 150 151 methods: { 152 _pre_open_callbacks: [], 153 _is_open: false, 154 155 getDPIRatio: function() { 156 var ratio = 1; 157 158 // To account for zoom, change to use deviceXDPI instead of systemXDPI 159 if (window.screen.systemXDPI !== undefined 160 && window.screen.logicalXDPI !== undefined 161 && window.screen.systemXDPI > window.screen.logicalXDPI) { 162 // Only allow for values > 1 163 ratio = window.screen.systemXDPI / window.screen.logicalXDPI; 164 } else if (window.devicePixelRatio !== undefined) { 165 ratio = window.devicePixelRatio; 166 } 167 168 core.methods.log('nplModal.getDPIRatio()', { 169 result: ratio 170 }); 171 return ratio; 172 }, 173 174 url_handler: function() { 175 var slug = arguments[0]; 176 var image_id = arguments[1]; 177 var sidebar = null; 178 if (arguments.length === 3) { 179 sidebar = arguments[2]; 180 if (sidebar === '1') { 181 sidebar = 'comments'; 182 } 183 } 184 185 // need to get slug, image_id, and sidebar 186 // determine the ID from our slug. if nothing comes back, assume we're already looking at the ID 187 var gallery_id = this.get_id_from_slug(slug); 188 if (!gallery_id) { 189 gallery_id = slug; 190 } 191 192 // Image_ID should remain an integer for all NGG provided images
193 // but it's passed to this method as a string 194 var old_state = this.get_state(); 195 if (parseInt(image_id) === old_state.image_id) { 196 image_id = parseInt(image_id); 197 } 198 199 var state = { 200 gallery_id: gallery_id, 201 image_id: image_id, 202 sidebar: sidebar, 203 slug: slug 204 }; 205 206 this.set_state(state); 207 208 $('#npl_content').trigger('npl_url_handler', [state]); 209 210 if (this.is_open() && gallery_id === core.state.gallery_id) { 211 return; 212 } 213 214 this.open_modal(gallery_id, image_id, sidebar); 215 }, 216 217 run_pre_open_lightbox_callbacks: function(link, params) { 218 for (var i = 0; i < this._pre_open_callbacks.length; i++) { 219 var callback = this._pre_open_callbacks[i]; 220 params = callback(link, params); 221 } 222 return params; 223 }, 224 225 add_pre_open_callback: function(callback) { 226 this._pre_open_callbacks.push(callback); 227 }, 228 229 get_state: function() { 230 return core.state; 231 }, 232 233 set_state: function(state) { 234 core.methods.log("nplModal.set_state()", { 235 state: state 236 }); 237 core.state = state; 238 }, 239 240 // The REST nonce, but only when it can help: core rejects a stale nonce with a 403 241 // instead of falling back to anonymous, and this page's HTML may be served from a 242 // cache for far longer than the 12-24h a wp_rest nonce lives. Logged-in viewers need 243 // it (without it their request is treated as anonymous); visitors must not send it. 244 rest_nonce: function () { 245 var auth = window.nextgen_pro_lightbox_authentication; 246 if (!auth || !auth.nonce || !auth.logged_in) { 247 return null; 248 } 249 return auth.nonce; 250 }, 251 252 get_setting: function (name, def) { 253 var tmp = ''; 254 if (typeof nplModalSettings !== 'undefined' 255 && typeof nplModalSettings[name] !== 'undefined' 256 && nplModalSettings[name] !== '') { 257 tmp = window.nplModalSettings[name]; 258 } else { 259 tmp = def; 260 } 261 if (tmp === 1) tmp = true; 262 if (tmp === 0) tmp = false; 263 if (tmp === '1') tmp = true; 264 if (tmp === '0') tmp = false; 265 266 core.methods.log('nplModal.get_setting()', { 267 name: name, 268 result: tmp 269 }, 3); 270 return tmp; 271 }, 272 273 get_slug: function (gallery_id) { 274 var slug = gallery_id; 275 if ('undefined' === typeof window.galleries) { return slug; } 276 277 $.each(galleries, function(index, gallery) { 278 if (gallery.slug && gallery.ID === gallery_id) { 279 slug = gallery.slug; 280 } 281 }); 282 283 core.methods.log('nplModal.get_slug()', { 284 gallery_id: gallery_id, 285 result: slug 286 }); 287 return slug; 288 }, 289 290 open: function($el) { 291 core.methods.log('nplModal.open()', { 292 el: $el 293 }); 294 295 // Define parameters for opening the Pro Lightbox 296 var params = { 297 show_sidebar: '', 298 gallery_id: '!', 299 image_id: '!', 300 slug: null, 301 revert_image_id: '!', 302 open_the_lightbox: true 303 }; 304 305 // Determine if we should show the comment sidebar 306 if ($el.data('nplmodal-show-comments')) { 307 params.show_sidebar = '/comments'; 308 } 309 310 // Determine the gallery id 311 if ($el.data('nplmodal-gallery-id')) { 312 params.gallery_id = $el.data('nplmodal-gallery-id'); 313 } 314 315 // Determine the image id 316 if ($el.data('nplmodal-image-id')) { 317 params.image_id = parseInt($el.data('nplmodal-image-id')); 318 } else if ($el.data('image-id')) { 319 params.image_id = parseInt($el.data('image-id')); 320 } else if (params.gallery_id === '!') { 321 322 var $image = $el.find('img'); 323 params.image_id = $image.attr('src'); 324 if ($image.attr('srcset')) { 325 var sizes = parseSrcset($image.attr('srcset')); 326 var largest_w = 0; 327 _.each(sizes, function (row) { 328 if (typeof row.w !== 'undefined' && row.w > largest_w) { 329 largest_w = row.w; 330 params.image_id = row.url; 331 } 332 }); 333 } 334 } 335 336 // Determine the slug 337 if (params.gallery_id !== '!') { 338 params.slug = this.get_slug(params.gallery_id); 339 } 340 341 // Run any registered callbacks for modifying lightbox params 342 params = this.run_pre_open_lightbox_callbacks($el, params); 343 344 // Are we to still open the lightbox? 345 if (params.open_the_lightbox) { 346 // open the pro-lightbox manually 347 if (params.gallery_id === '!' || !this.get_setting('enable_routing')) { 348 this.open_modal(params.gallery_id, params.image_id, null); 349 } else { 350 // open the pro-lightbox through our backbone.js router 351 core.methods.router.front_page_pushstate(params.gallery_id, params.image_id); 352 353 core.methods.set_state({ 354 gallery_id: params.gallery_id, 355 image_id: params.image_id, 356 sidebar: params.show_sidebar, 357 slug: params.slug 358 }); 359 360 this.router.navigate( 361 core.options.router_slug 362 + '/' + params.slug 363 + '/' + params.image_id 364 + params.show_sidebar 365 ); 366 367 // some displays (random widgets) may need to disable routing 368 // but still pass an image-id to display on startup 369 if (params.revert_image_id !== '!') { 370 core.state.image_id = params.revert_image_id; 371 } 372 } 373 } 374 }, 375 376 bind_images: function() { 377 // to handle ajax-pagination events this method is called on the 'refreshed' signal 378 var selector = nextgen_lightbox_filter_selector($, $(".nextgen_pro_lightbox")); 379 380 // Modify the selector to exclude any Photocrati Lightboxes 381 var new_selector = []; 382 for (var index = 0; index < selector.length; index++) { 383 var el = selector[index]; 384 if (!$(el).hasClass('photocrati_lightbox_always') && !$(el).hasClass('decoy')) { 385 new_selector.push(el); 386 } 387 } 388 389 // Ensure that we're attaching the event to the A tag, not the img, if 390 // there is one. 391 // TODO: Should this be moved to nextgen_lightbox_filter_selector() ? 392 new_selector = new_selector.map(function(el) { 393 return $(el).hasClass('nextgen_pro_lightbox') && el.nodeName != 'A' && $(el).parents('a').length 394 ? $(el).parents('a')[0] 395 : el; 396 }); 397 398 core.selector = $(new_selector); 399 core.selector.on('click', function (event) { 400 // pass these by 401 if ($.inArray($(this).attr('target'), ['_blank', '_parent', '_top']) > -1) { 402 return; 403 } 404 405 // NextGEN Basic Thumbnails has an option to link to an imagebrowser display; this disables the effect
406 // code (we have no gallery-id) but we may be asked to open it anyway if lightboxes are set to apply 407 // to all images. Check for and do nothing in that scenario: 408 if ($(this).data('src') 409 && $(this).data('src').indexOf(core.methods.get_setting('router_slug') + '/image') !== -1 410 && !$(this).data('nplmodal-gallery-id')) { 411 return; 412 } 413 414 event.stopPropagation(); 415 event.preventDefault(); 416 417 if (event.handled !== true) { 418 event.handled = true; 419 core.methods.open($(this)); 420 } 421 }); 422 }, 423 424 // establishes bindings of events to actions 425 set_events: function() { 426 var self = this; 427 428 $(window).on('refreshed', self.bind_images); 429 $(window).on('keydown', self.handle_keyboard_input); 430 431 $('#npl_content').on('npl_images_ready', self.open_modal_final); 432 433 // handle exit clicks/touch events 434 $('#npl_overlay, #npl_button_close').on('touchstart click', function(event) { 435 event.stopPropagation(); 436 event.preventDefault(); 437 if (event.handled !== true) { 438 event.handled = true; 439 self.close_modal(); 440 } 441 }); 442 }, 443 444 open_modal: function(gallery_id, image_id, sidebar) { 445 core.methods.log('nplModal.open_modal()', { 446 gallery_id: gallery_id, 447 image_id: image_id, 448 sidebar: sidebar 449 }); 450 451 this._is_open = true; 452 453 // allow for cleanup handlers to run 454 $('#npl_content').trigger('npl_opening'); 455 456 // disables browser scrollbar display 457 $('html, body').toggleClass('nextgen_pro_lightbox_open'); 458 core.state.image_id = image_id; 459 core.state.gallery_id = gallery_id; 460 461 // Clear leftover content and any prior fail notice before this open. 462 $('#npl_content').empty(); 463 $('.npl-load-error').remove(); 464 465 $('#npl_spinner_container').removeClass('hidden'); 466 $('#npl_button_close').removeClass('hidden'); 467 468 this.fullsize.exit(); 469 this.mobile.open(); 470 471 // fetch_images(), when done building a list of images to feed to Galleria, will trigger 472 // an event on #npl_content that is handled by open_modal_final() 473 core.methods.fetch_images.fetch_images(gallery_id, image_id); 474 }, 475 476 preload_image: function(image_obj) { 477 return new Promise(resolve => { 478 const image = new Image(); 479 image.addEventListener('load', () => { 480 resolve(); 481 }); 482 image.src = image_obj.use_hdpi ? image_obj.srcsets.hdpi : image_obj.image; 483 }); 484 }, 485 486 open_modal_final: function(event, gallery_id) { 487 var show_ndx = 0; 488 var show_hdpi = core.methods.getDPIRatio() > 1; 489 var images = core.methods.fetch_images.gallery_image_cache[core.state.gallery_id]; 490 var sidebar = core.state.sidebar; 491 492 $.each(images, function(index, element) { 493 // Mark the requested image as the one to show at startup 494 if (parseInt(core.state.image_id) === parseInt(element.image_id)) { 495 show_ndx = index; 496 } 497 498 // A WP media library or 'other' / non-nextgen image 499 if (typeof core.state.image_id == 'string' && typeof element.image_id == 'string') { 500 if (core.state.image_id === element.image_id) { 501 show_ndx = index; 502 } 503 } 504 505 // In case we're viewing a WP or non-NGG image 506 if (typeof element.full_use_hdpi !== 'undefined') { 507 // Massage our data for High-DPI screens 508 if (show_hdpi && element.full_use_hdpi) { 509 element.image = element.full_srcsets.hdpi; 510 } else { 511 // In case the plain 'image' is a dynamically-shrunk version: 512 // we always want to use the 'full' size image when available 513 element.image = element.full_image; 514 } 515 } 516 }); 517 518 // Determine if carousel thumbnails should be enabled 519 var thumbnails = core.methods.get_setting('enable_carousel', 'always'); 520 var thumbnails_limit = core.methods.get_setting('carousel_thumbnails_limit', 250); 521 var gallery = core.methods.get_gallery_from_id(gallery_id); 522 if (thumbnails === 'always') { 523 thumbnails = 'lazy'; 524 } else if (thumbnails === 'never') { 525 thumbnails = false;
526 } else if (thumbnails === 'nomobile') { 527 if (core.methods.mobile.browser.any()) { 528 thumbnails = false; 529 } else { 530 thumbnails = 'lazy'; 531 } 532 } 533 534 // Very large galleries can severely impact performance. Disabling carousel thumbnails 535 // can greatly relieve that impact however, so we automatically change for very large galleries 536 if (gallery && gallery.images_list_count >= thumbnails_limit) { 537 thumbnails = false; 538 } 539 540 if (!thumbnails) { 541 $('#npl_wrapper').addClass('nggpl-carousel-hidden'); 542 } 543 544 if (sidebar 545 || (core.methods.get_setting('display_cart', false) && core.methods.get_displayed_gallery_setting(gallery_id, 'is_ecommerce_enabled', false)) 546 || (core.methods.get_setting('display_comments', false) && core.methods.get_setting('enable_comments', false))) { 547 $('#npl_wrapper').addClass('npl-sidebar-open npl-sidebar-overlay-open'); 548 } 549 550 if (core.methods.get_setting('display_captions', false)) { 551 $('#npl_wrapper').addClass('npl-info-open'); 552 } 553 554 if (core.methods.get_setting('style')) { 555 $('#npl_wrapper').addClass('npl-variant-' + core.methods.get_setting('style')) 556 } 557 558 if (!core.methods.get_setting('display_carousel', true) || core.methods.mobile.browser.any()) { 559 $('#npl_wrapper').addClass('npl-carousel-closed'); 560 } 561 562 if (images.length >= thumbnails_limit) { 563 thumbnails = false; 564 } 565 566 core.methods.log('nplModal.open_modal() about to invoke Galleria.run()', { 567 thumbnails: thumbnails, 568 images: images, 569 show: show_ndx, 570 sidebar: sidebar 571 }); 572 573 // Preload the first six images ourselves, THEN start Galleria. This is done to mitigate Galleria.js 574 // positioning images incorrectly when they are slow to load at startup and during transitions. 575 const preloads = images.slice((show_ndx - 3), (show_ndx + 3)); 576 const promises = preloads.map(image => core.methods.preload_image(image)); 577 578 Promise.all(promises).then((values) => { 579 Galleria.run('#npl_content', { 580 responsive: true, 581 thumbQuality: false, 582 thumbnails: thumbnails, 583 preload: 0, 584 theme: 'nextgen_pro_lightbox', 585 dataSource: images, 586 show: show_ndx, 587 variation: 'nggpl-variant-' + core.methods.get_setting('style', ''), 588 transition: core.methods.get_setting('transition_effect', 'slide'), 589 touchTransition: core.methods.get_setting('touch_transition_effect', 'slide'), 590 imagePan: core.methods.get_setting('image_pan', false), 591 pauseOnInteraction: core.methods.get_setting('interaction_pause', true), 592 imageCrop: core.methods.get_setting('image_crop', true), 593 transitionSpeed: (core.methods.get_setting('transition_speed', 0.4) * 1000), 594 nggSidebar: sidebar 595 }); 596 597 // Set the background color right away, so that we don't have to wait for the theme init 598 $('#npl_content .galleria-container').css({background: core.methods.get_setting('background_color')}); 599 }); 600 601 }, 602 603 // When rotaning or opening the keyboard some mobile browsers increase the user zoom level beyond the default. 604 // To handle this we update the viewport setting to disable zooming when open_modal is run and restore it to 605 // the original value when calling close_modal() 606 mobile: { 607 meta: null, 608 original: null, // original viewport setting; it's restored at closing 609 adjust: true, 610 ontouch: ('ontouchstart' in window), 611 init: function() { 612 // suppress a warning in desktop chrome (provided no touch input devices are attached) that the following 613 // content meta-attribute we're about to set is invali
613d. it technically is, but it's the only way 614 // to make every mobile browser happy without ridiculous user agent matching that I've come across so far 615 if (!this.ontouch) { 616 this.adjust = false; 617 } 618 var doc = window.document; 619 if (!doc.querySelector) { return; } // this isn't available on pre 3.2 safari 620 this.meta = doc.querySelector("meta[name=viewport]"); 621 this.original = this.meta && this.meta.getAttribute("content"); 622 }, 623 open: function() { 624 if (this.adjust && this.meta) { 625 core.methods.log('nplModal.mobile.open()'); 626 this.meta.setAttribute("content", this.original + ', width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no'); 627 } 628 }, 629 close: function() { 630 if (this.adjust && this.meta) { 631 core.methods.log('nplModal.mobile.close()'); 632 this.meta.setAttribute("content", this.original); 633 } 634 }, 635 browser: { 636 any: function() { 637 return core.methods.mobile.browser.android() 638 || core.methods.mobile.browser.ios() 639 || core.methods.mobile.browser.windowsphone() 640 || core.methods.mobile.browser.blackberry(); 641 }, 642 android: function() { 643 return /Android/i.test(navigator.userAgent); 644 }, 645 ios: function() { 646 return /crios|iP(hone|od|ad)/i.test(navigator.userAgent); 647 }, 648 windowsphone: function() { 649 return /(iemobile|Windows Phone)/i.test(navigator.userAgent); 650 }, 651 blackberry: function() { 652 return /(blackberry|RIM Tablet|BB10; )/i.test(navigator.userAgent); 653 } 654 } 655 }, 656 657 is_ie9: function() { 658 return navigator.appVersion.indexOf("MSIE 9.") !== -1; 659 }, 660 661 // hide our content and close up 662 close_modal: function() { 663 if (!this._is_open) { 664 return; 665 } 666 667 core.methods.log('nplModal.close_modal()'); 668 669 var content = $('#npl_content'); 670 671 // allow for cleanup handlers to run 672 content.trigger('npl_closing'); 673 674 // kill Galleria so it won't suck up memory in the background 675 const innerGalleria = content.data('galleria'); 676 // Truthy check, not typeof: destroy() sets data('galleria') to null, which would pass a typeof-undefined guard and throw. 677 if (innerGalleria) { 678 innerGalleria.destroy(); 679 } 680 681 this.fullsize.enter(); 682 683 // for use with Galleria it is important that npl_content never have display:none set 684 $('#npl_spinner_container, #npl_button_close').addClass('hidden'); 685 686 // enables displaying browser scrollbars 687 $('html, body').toggleClass('nextgen_pro_lightbox_open'); 688 689 // Reset all statuses such as whether the sidebar has been triggered 690 $('#npl_wrapper').removeClass(); 691 692 this.mobile.close(); 693 694 // reset our modified url to our original state 695 if (this.get_setting('enable_routing')) { 696 if (history.pushState) { 697 history.pushState('', document.title, window.location.pathname + window.location.search); 698 } else { 699 window.location.hash = ''; 700 } 701 if (this.get_setting('is_front_page') && history.pushState) { 702 history.pushState({}, document.title, core.options.initial_url); 703 } 704 } 705 706 this._is_open = false;
707 }, 708 709 fullsize: { 710 _is_fullsize: false, 711 712 active: function() { 713 return this.fullsize._is_fullsize; 714 }, 715 716 enter: function() { 717 core.methods.log('nplModal.fullsize.enter()'); 718 719 $('#npl_wrapper').removeClass('npl_open_with_padding'); 720 this._is_fullsize = true; 721 }, 722 723 exit: function() { 724 core.methods.log('nplModal.fullsize.exit()'); 725 726 if (parseInt(core.methods.get_setting('padding', '0')) > 0 727 && !core.methods.mobile.browser.ios()) { 728 $('#npl_wrapper').addClass('npl_open_with_padding'); 729 } 730 731 this._is_fullsize = false; 732 }, 733 734 toggle: function() { 735 core.methods.log('nplModal.fullsize.toggle()'); 736 737 if (this.fullsize._is_fullsize) { 738 this.fullsize.exit(); 739 } else { 740 this.fullsize.enter(); 741 } 742 $(window).trigger('resize'); 743 } 744 }, 745 746 handle_keyboard_input: function(event) { 747 if (core.methods.is_open()) { 748 // escape key closes the modal 749 if (event.which === 27) { 750 core.methods.close_modal(); 751 } 752 } 753 }, 754 755 is_open: function() { 756 return this._is_open; 757 }, 758 759 log: function(message, table, level) { 760 if (!core.debug) { 761 return; 762 } 763 764 if (typeof level === 'undefined') { 765 level = 1; 766 } 767 768 if (level > core.debug_level) { 769 return; 770 } 771 772 if (typeof table === 'undefined') { 773 console.log("NPL Debug:", message) 774 } else { 775 console.log("NPL Debug:", message, table); 776 } 777 778 }, 779 780 logging: { 781 enable: function() { 782 console.log("Enabling NextGen Pro Lightbox debugging"); 783 core.debug = true; 784 }, 785 disable: function() { 786 console.log("Disabling NextGen Pro Lightbox debugging"); 787 core.debug = false; 788 }, 789 set_level: function(level) { 790 console.log("Setting NextGen Pro Lightbox debug level to " + level); 791 core.level = parseInt(level); 792 } 793 }, 794 795 fetch_images: { 796 gallery_image_cache: [], 797 ajax_info: [], 798 ajax_interval: null, 799 ajax_delay: 1400, 800 801 is_cached: function(gallery_id, image_id) { 802 var found = false; 803 $.each(this.gallery_image_cache[gallery_id], function (ndx, image) { 804 if (image_id === image.image_id) { 805 found = true; 806 } 807 }); 808 809 core.methods.log("nplModal.fetch_images.is_cached()", { 810 gallery_id: gallery_id, 811 image_id: image_id, 812 gallery_cache: this.gallery_image_cache[gallery_id], 813 found: found 814 }, 4); 815 816 return found; 817 }, 818 819 fetch_images: function(gallery_id, image_id) { 820 core.methods.log('nplModal.fetch_images.fetch_images() beginning'); 821 var self = this; 822 823 this.ajax_delay = core.methods.get_setting('ajax_delay', 1400); 824 825 if (typeof this.gallery_image_cache[gallery_id] === 'undefined') { 826 this.gallery_image_cache[gallery_id] = []; 827 } 828 829 var gallery = core.methods.get_gallery_from_id(gallery_id); 830 831 if (gallery === null) { 832 // It's not a NextGen gallery, just read from the page 833 return this.fetch_non_ngg_images(gallery_id); 834 } 835 836 // Gallery is already cached - just trigger the ready event 837 if (this.gallery_image_cache[gallery_id].length === gallery.images_list_count) { 838 return $('#npl_content').trigger('npl_images_ready', [gallery_id]); 839 } 840 841 // Build the cache from the list 'localized' on the page 842 $.each(gallery.images_list, function(ndx, image) { 843 if (!self.is_cached(gallery_id, image.image_id)) { 844 self.gallery_image_cache[gallery_id].push(image); 845 } 846 }); 847 848 // Again check if our cache is full 849 if (this.gallery_image_cache[gallery_id].length === gallery.images_list_count) { 850 return $('#npl_content').trigger('npl_images_ready', [gallery_id]); 851 } 852 853 // Not all images are on this page: fetch by XHR - which handles the ready event 854 if (this.gallery_image_cache[gallery_id].length < gallery.images_list_count) { 855 return this.fetch_images_from_ajax(gallery_id); 856 } 857 }, 858 859 fetch_non_ngg_images: function (gallery_id) { 860 core.methods.log('nplModal.fetch_images.fetch_non_ngg_images() beginning', { 861 gallery_id: gallery_id 862 }); 863 864 var self = this; 865 866 core.selector.each(function() { 867 var anchor = $(this); 868 869 if (anchor.hasClass('ngg-trigger')) { 870 return true; // exclude NextGEN trigger icons 871 } 872 873 if (gallery_id !== '!' && gallery_id !== anchor.data('nplmodal-gallery-id')) { 874 return true; // exclude images from other galleries 875 } 876 877 878 if (gallery_id !== core.methods.get_state().gallery_id) { 879 return true; // exclude images from other galleries 880 } 881 882 if (core.methods.get_state().gallery_id === '!' && anchor.data('nplmodal-gallery-id')) { 883 return true; // when viewing non-nextgen images; exclude nextgen-images 884 } 885 886 var image = $(this).find('img').first(); 887 var gallery_image = {}; 888 var expr = /\.(jpeg|jpg|gif|png|bmp|webp|avif)(\?.*)?$/i; 889 890 gallery_image.image = (anchor.data('fullsize') === undefined) ? anchor.attr('href') : anchor.data('fullsize'); 891 892 if (typeof gallery_image.image !== 'undefined' 893 && !gallery_image.image.match(expr) 894 && image.attr('srcset')) { 895 var sizes = parseSrcset(image.attr('srcset')); 896 var largest_w = 0; 897 898 _.each(sizes, function (row) { 899 if (typeof row.w !== 'undefined' && row.w > largest_w) { 900 largest_w = row.w; 901 gallery_image.image = row.url; 902 } 903 }); 904 } 905 906 // Workaround WP' "link to attachment page" feature 907 if (!gallery_image.image.match(expr)) { 908 gallery_image.image = image.attr('src'); 909 } 910 911 // When in doubt we id images by their href 912 gallery_image.image_id = (anchor.data('image-id') === undefined) ? gallery_image.image : anchor.data('image-id'); 913 914 // no need to continue 915 if (self.is_cached(gallery_id, gallery_image.image_id)) { 916 return true; 917 } 918 919 // optional attributes 920 if (anchor.data('thumb') !== undefined) gallery_image.thumb = anchor.data('thumb'); 921 else if (anchor.data('thumbnail') !== 'undefined') gallery_image.thumb = anchor.data('thumbnail'); 922 923 if (anchor.data('title') !== undefined) {
924 gallery_image.title = anchor.data('title'); 925 } else if (typeof image.attr('title') !== 'undefined') { 926 gallery_image.title = image.attr('title'); 927 } else if (typeof anchor.siblings('.wp-caption-text').html() !== 'undefined') { 928 gallery_image.title = anchor.siblings('.wp-caption-text').html(); 929 } 930 931 if (anchor.data('description') !== undefined) { 932 gallery_image.description = anchor.data('description'); 933 } else { 934 gallery_image.description = image.attr('alt'); 935 } 936 937 self.gallery_image_cache[gallery_id].push(gallery_image); 938 }); 939 940 core.methods.log('nplModal.fetch_images.fetch_non_ngg_images() result', { 941 result: self.gallery_image_cache[gallery_id] 942 }); 943 944 $('#npl_content').trigger('npl_images_ready', [gallery_id]) 945 }, 946 947 create_deferred_ajax: function(gallery_id, gallery, page, results, status) { 948 // Resolve after one retry so the aggregate settles; a batch that never lands leaves the cache short and re-fetches next open 949 var settled = $.Deferred(); 950 951 var attempt = function(retries_left) { 952 $.ajax({ 953 async: true, 954 url: core.options.ajax_url + 'nggpro/v1/Galleria/GetImages', 955 method: 'POST', 956 beforeSend: function(xhr) { 957 // Only for logged-in viewers: without the nonce the REST API treats a 958 // logged-in request as anonymous, so editors previewing an unpublished 959 // gallery would be refused their own images by the route's permission 960 // check. For a visitor the nonce is worse than useless -- a page cache 961 // can serve this HTML long after the 12-24h nonce window, and core 403s 962 // a stale nonce instead of falling back to anonymous. 963 var nonce = core.methods.rest_nonce(); 964 if (nonce) { 965 xhr.setRequestHeader('X-WP-Nonce', nonce); 966 } 967 }, 968 xhrFields: { 969 withCredentials: true 970 }, 971 data: { 972 id: gallery_id, 973 gallery: gallery, 974 lang: core.methods.get_setting('lang', null), 975 page: page 976 }, 977 dataType: 'json', 978 // Default 60s, overridable via the ngg_pro_lightbox_ajax_timeout PHP filter. parseInt because get_setting coerces 0/1 to booleans. 979 timeout: parseInt(core.methods.get_setting('ajax_timeout', 60000), 10) || 60000 980 }).done(function(data) { 981 core.methods.log('nplModal.fetch_images.create_deferred_ajax() response', { 982 response: data 983 }); 984 // Store by page index;
984 assembled in page order later (not completion order) 985 results[page] = data; 986 settled.resolve(); 987 }).fail(function(jqXHR, textStatus, errorThrown) { 988 // Retry once on any failure (including timeout), after a short randomised back-off, before giving up. 989 if (retries_left > 0) { 990 setTimeout(function() { attempt(retries_left - 1); }, 500 + Math.random() * 1000); 991 return; 992 } 993 status.failed = true; 994 if (window.console && window.console.warn) { 995 window.console.warn('NextGEN Pro Lightbox: image batch ' + page + ' failed to load (' + textStatus + ', HTTP ' + (jqXHR && jqXHR.status) + '); it will be retried the next time the lightbox opens.'); 996 } 997 settled.resolve(); 998 }); 999 }; 1000 1001 attempt(1); 1002 return settled.promise(); 1003 }, 1004 1005 fetch_images_from_ajax: function(gallery_id, image_id) { 1006 if (!core.methods.is_open()) { 1007 return; 1008 } 1009 1010 core.methods.log('nplModal.fetch_images.fetch_images_from_ajax()', { 1011 gallery_id: gallery_id, 1012 image_id: image_id 1013 }); 1014 1015 var self = this; 1016 var original_gallery = core.methods.get_gallery_from_id(gallery_id); 1017 var gallery = $.extend({}, original_gallery); 1018 delete gallery.images_list; 1019 delete gallery.display_settings; 1020 1021 // Collect batches keyed by page index; assemble after all resolve. status.failed flips if any batch gives up. 1022 var results = {}; 1023 var status = { failed: false }; 1024 var defers = []; 1025 // Page 0 duplicates the localized first page (server clamps its offset to 0), so start at 1. 1026 for (var i = 1; i <= Math.ceil(gallery.images_list_count / core.methods.get_setting('localize_limit')); i++) { 1027 defers.push(core.methods.fetch_images.create_deferred_ajax(gallery_id, gallery, i, results, status)); 1028 } 1029 1030 // Every deferred above resolves even on failure, so this always settles. 1031 $.when.apply($, defers).then(function() { 1032 // Rebuild fresh each time so a partial earlier attempt can't freeze a wrong order 1033 var assembled = []; 1034 var seen = {}; 1035 var add = function(image) { 1036 if (image && !seen[image.image_id]) { 1037 seen[image.image_id] = true; 1038 assembled.push(image); 1039 } 1040 }; 1041 1042 // Localized first page (original_gallery still has images_list; the request copy deleted it) 1043 $.each(original_gallery.images_list || [], function(ndx, image) { 1044 add(image); 1045 }); 1046 1047 // Then each fetched page, in ascending page order. 1048 Object.keys(results) 1049 .map(function(page) { return parseInt(page, 10); }) 1050 .sort(function(a, b) { return a - b; }) 1051 .forEach(function(page) { 1052 $.each(results[page] || [], function(ndx, image) { 1053 add(image); 1054 }); 1055 delete results[page]; 1056 }); 1057 1058 // Replace cache contents in place; the array reference is handed out and read by open_modal_final, so don't reassign 1059 var cache = self.gallery_image_cache[gallery_id]; 1060 cache.length = 0; 1061 Array.prototype.push.apply(cache, assembled); 1062 1063 // Only act if still open on the same gallery (a late batch must not touch another gallery). 1064 if (core.methods.is_open() && gallery_id === core.state.gallery_id) { 1065 // Always render what arrived, in order, so a failed batch degrades to a 1066 // partial gallery (spinner cleared by Galleria) instead of nothing. 1067 $('#npl_content').trigger('npl_images_ready', [gallery_id]); 1068 1069 if (status.failed) { 1070 // Some batches never loaded: overlay a dismissible notice. Cache stays short, so reopening re-fetches the rest. 1071 var i18n = core.methods.get_setting('i18n', {}) || {}; 1072 var msg = i18n.load_error || 'Some images could not be loaded.'; 1073 $('.npl-load-error').remove();
1074 var notice = $('<div class="npl-load-error" role="alert"></div>'); 1075 notice.append($('<span></span>').text(msg)); 1076 notice.append( 1077 $('<button type="button" class="npl-load-error-dismiss" aria-label="Dismiss"></button>') 1078 .text('Ã') 1079 .on('click', function() { notice.remove(); }) 1080 ); 1081 $('#npl_wrapper').append(notice); 1082 } 1083 } 1084 }); 1085 1086 return self.gallery_image_cache[gallery_id]; 1087 } 1088 }, 1089 1090 get_gallery_from_id: function (gallery_id) { 1091 if ('undefined' === typeof window.galleries) { return null; } 1092 var retval = null; 1093 $.each(window.galleries, function(index, gallery) { 1094 if (gallery.ID === gallery_id || gallery.ID === parseInt(gallery_id)) { 1095 retval = gallery; 1096 } 1097 }); 1098 return retval; 1099 }, 1100 1101 get_id_from_slug: function (slug) { 1102 var id = slug; 1103 if ('undefined' === typeof window.galleries) { return id; } 1104 1105 $.each(window.galleries, function(index, gallery) { 1106 if (gallery.slug === slug) { 1107 id = gallery.ID; 1108 } 1109 }); 1110 return id; 1111 }, 1112 1113 get_displayed_gallery_setting: function(gallery_id, name, def) { 1114 var tmp = def; 1115 var gallery = this.get_gallery_from_id(gallery_id); 1116 if (gallery && gallery.display_settings) { 1117 if (typeof gallery.display_settings[name] !== 'undefined') { 1118 tmp = gallery.display_settings[name]; 1119 } 1120 var original = gallery.display_settings.original_settings; 1121 if (original && typeof original[name] !== 'undefined') { 1122 tmp = original[name]; 1123 } 1124 } 1125 if (tmp === '1') tmp = true; 1126 if (tmp === '0') tmp = false; 1127 if (tmp === 1) tmp = true; 1128 if (tmp === 0) tmp = false; 1129 1130 core.methods.log('nplModal.get_displayed_gallery_setting()', { 1131 gallery_id: gallery_id, 1132 name: name, 1133 result: tmp 1134 }); 1135 1136 return tmp; 1137 }, 1138 1139 router: { 1140 routes: [], 1141 interval: null, 1142 1143 listen: function(current) { 1144 var self = this; 1145 current = current || ''; 1146 1147 var listener = function() { 1148 if (current !== self.get_fragment()) { 1149 current = self.get_fragment(); 1150 self.match(current); 1151 } 1152 }; 1153 clearInterval(this.interval); 1154 this.interval = setInterval(listener, 50); 1155 }, 1156 1157 get_fragment: function(url) { 1158 url = url || window.location.href; 1159 var match = url.match(/#(.*)$/); 1160 var fragment = match ? match[1] : ''; 1161 return fragment.toString().replace(/\/$/, '').replace(/^\//, ''); 1162 }, 1163 1164 navigate: function(path, notrigger) { 1165 notrigger = notrigger || false; 1166 if (notrigger) { 1167 clearInterval(this.interval); 1168 } 1169 1170 if (!core.methods.get_setting('enable_routing', false)) { 1171 return; 1172 } 1173 1174 path = path ? path : ''; 1175 window.location.href.match(/#(.*)$/); 1176 window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path; 1177 1178 if (notrigger) { 1179 this.router.listen(this.router.get_fragment()) 1180 } 1181 }, 1182 1183 match: function(f) { 1184 var fragment = f || this.get_fragment(); 1185 for (var i = 0; i < this.routes.length; i++) { 1186 var match = fragment.match(this.routes[i].re); 1187 if (match) { 1188 match.shift(); 1189 this.routes[i].handler.apply(core.methods, match); 1190 return this; 1191 } 1192 } 1193 }, 1194 1195 front_page_pushstate: function(gallery_id, image_id) { 1196 if (!core.methods.get_setting('is_front_page') || gallery_id === undefined) { 1197 return false;
1198 } 1199 1200 if ('undefined' === typeof window.galleries) { 1201 return false; 1202 } 1203 1204 core.methods.log("beginning nplModal.router.front_page_pushstate()", { 1205 gallery_id: gallery_id, 1206 image_id: image_id 1207 }); 1208 1209 var url = ''; 1210 var slug = gallery_id; 1211 1212 $.each(galleries, function(index, gallery) { 1213 if (gallery.ID === gallery_id && typeof gallery.wordpress_page_root !== 'undefined') { 1214 url = gallery.wordpress_page_root; 1215 if (gallery.slug) { 1216 slug = gallery.slug; 1217 } 1218 } 1219 }); 1220 1221 url += '#' + this.get_fragment(core.methods.get_setting('router_slug') + '/' + slug + '/' + image_id); 1222 1223 // redirect those browsers that don't support history.pushState 1224 if (history.pushState) { 1225 history.pushState({}, document.title, url); 1226 return true; 1227 } else { 1228 window.location = url; 1229 return false; 1230 } 1231 } 1232 } 1233 } 1234 }; 1235 1236 this.core = core; 1237 } 1238 1239 var nplModalObj = new nplModal(); 1240 1241 $.nplModal = function(param) { 1242 function getDescendantProp(obj, desc) { 1243 var arr = desc.split("."); 1244 while(arr.length) { 1245 obj = obj[arr.shift()]; 1246 } 1247 return obj; 1248 } 1249 1250 if (typeof param === 'undefined') { 1251 return nplModalObj.core.init.apply(nplModalObj, {}); 1252 } else if (typeof param === 'object') { 1253 return nplModalObj.core.init.apply(nplModalObj, param); 1254 } else { 1255 var method = getDescendantProp(nplModalObj.core.methods, param); 1256 if (method) { 1257 return method.apply(nplModalObj.core.methods, Array.prototype.slice.call(arguments, 1)); 1258 } else { 1259 console.error('Method ' + param + ' does not exist on jQuery.nplModal'); 1260 } 1261 } 1262 }; 1263 1264})(jQuery); 1265 1266jQuery(document).ready(function($) { 1267 $.nplModal(); 1268});
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.