PageSourceSearch

https://www.boiserealestateplus.com/wp-content/plugins/nextgen-gal…/Lightbox/nextgen_pro_lightbox.js?ver=4.5.1

js boiserealestateplus.com collected 2026-10-04 20:05:36 UTC 60,433 bytes, 1,268 lines download raw bytes

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.