PageSourceSearch

https://ogtstore.com/wp-content/plugins/nm-woocommerce-wishlist/assets/js/nm-woocommerce-wishlist.js?ver=1.0.9

js ogtstore.com collected 2026-10-03 23:45:38 UTC 34,990 bytes, 878 lines download raw bytes

1(function($) {
2	
3    'use strict';
4	
5	function NMWW_Plugin() {
6		this.init();
7	};
8	
9	NMWW_Plugin.prototype = {
10		
11		/**
12		 * Init
13		 */
14		init: function() {
15			var self = this;
16            
17            self.$document = $(document);
18            self.$body = $('body');
19            self.$documentBody = $(document.body);
20            self.$panelWrapper = $('#nmww-panel-items');
21            self.$pageItemsTable = $('#nmww-items-table');
22            
23            self.isLoggedIn = (self.$body.hasClass('logged-in')) ? true : false;
24            
25            self.wishlistItems = [];
26            
27            self.cookieName = 'nmww-items';
28            self.cookieExpires = parseInt(nmww_data.cookie_expires);
29            
30            // WPML: Add language code prefix to cookie name when WPML is used
31            if (nmww_data.addLocaleSuffix != '0') {
32                var locale = $('html').attr('lang').toLowerCase();
33                locale = locale.replace(/-/g, '_'); // Replace "-" with "_" so language code matches with "get_locale" PHP function
34                self.cookieName = self.cookieName + '-' + locale;
35            }
36            
37            //self.itemsUpdateAjax = false;
38            
39            self.timestampsInterval = null;
40            
41            self.setUp();
42            
43            /* Bind: Buttons (add/remove items) */
44			self.$document.on('click', '.nmww-button', function(e) {
45				e.preventDefault();
46                
47                // Make sure wishlist data is done loading
48                if (! self.$body.hasClass('nmww-buttons-ready')) {
49                    return;
50                }
51                
52                // Is login required?
53                if ('0' != nmww_data.login_require && ! self.$body.hasClass('logged-in')) {
54                    self.$body.trigger('nmww_login_required');
55                    window.location.replace(nmww_data.login_url+'?nmw_login_required');
56                    return;
57                }
58                
59                // Is an Ajax request running?
60				/*if (self.itemsUpdateAjax) {
61                    self.itemsUpdateAjax.abort(); // Abort Ajax request
62                    self.itemsUpdateAjax = false;
63				}*/
64                
65                var $button = $(this);
66                
67                if (! $button.hasClass('loading')) {
68                    var itemId = $button.data('product-id');
69                    
70                    // Make sure a product ID exists
71                    if (typeof itemId == 'undefined') {
72                        return false;
73                    }
74                    
75                    self.buttonSetState(itemId, 'loading');
76                
77                    self.buttonToggle($button, itemId);
78                }
79			});
80            
81            // Is the Wishlist panel included?
82            if (self.$panelWrapper.length) {
83                /* Bind: Wishlist panel - Button (header menu etc.) */
84                self.$document.on('click', '.nmww-wishlist-button', function(e) {
85                    e.preventDefault();
86                    if (! self.$body.hasClass('nmww-panel-open')) {
87                        self.panelOpen();
88                    } else {
89                        self.panelClose();
90                    }
91                });
92
93                 /* Bind: Wishlist panel - Close button */
94                $('#nmww-panel-close').on('click', function(e) {
95                    e.preventDefault();
96                    self.panelClose();
97                });
98
99                /* Bind: Wishlist panel - Add to cart events */
100                self.$documentBody.on('adding_to_cart', function(e, $thisbutton, data) {
101                    var $wishlistItem = $thisbutton.closest('.nmww-panel-item');
102                    if ($wishlistItem.length) {
103                        self.panelItemSetState($wishlistItem, 'loading');
104                    }
105                });
106                self.$documentBody.on('added_to_cart', function(e, fragments, cart_hash, $thisbutton) {
107                    self.$panelWrapper.children('.loading').removeClass('loading');
108                });
109
110                /* Bind: Wishlist panel - Remove item buttons */
111                self.$document.on('click', '.nmww-panel-item-remove', function(e) {
112                    e.preventDefault();
113                    self.panelItemOnRemoveButton(this);
114                });
115
116                /* Bind: Wishlist panel - Prevent cart panel from showing after adding to cart */
117                self.$documentBody.on('added_to_cart', function() {
118                    if (self.$body.hasClass('nmww-panel-open')) {
119                        setTimeout(function() {
120                            // Make sure the wishlist panel hasn't been closed during the timeout
121                            if (self.$body.hasClass('nmww-panel-open')) {
122                                var $cartPanelCloseButton = $('.wc-block-mini-cart__drawer').find('.wc-block-components-drawer__close');
123                                if ($cartPanelCloseButton.length) {
124                                    // Trigger click on cart panel close button
125                                    $cartPanelCloseButton[0].dispatchEvent(new Event('click', {bubbles: true}));
126                                }
127                            }
128                        }, 200);
129                    }
130                });
131
132                /* Bind: Wishlist panel - Overlay */
133                $('#nmww-panel-overlay').on('click', function() {
134                    self.panelClose();
135                });
136                
137                /*
138                 * Bind: Wishlist panel - Item buttons - Prevent AJAX add-to-cart on Checkout page (page needs refresh to show added product)
139                 *
140                 * Note: The "should_send_ajax_request.adding_to_cart" event is triggered from the "../plugins/woocommerce/assets/js/frontend/add-to-cart.js" file
141                 */
142                self.$documentBody.on('should_send_ajax_request.adding_to_cart', function(e, $thisbutton) {
143                    if (self.$body.hasClass('nmww-panel-open') && self.$body.hasClass('woocommerce-checkout')) {
144                        window.location.href = $thisbutton.attr('href');
145                        return false;
146                    }
147                });
148            }
149            
150            // Is this the Wishlist page?
151            if (self.$pageItemsTable.length) {
152                /* Bind: Page - Item - Remove button */
153                $('#nmww-items-table').on('click', '.nmww-item-remove-button', function(e) {
154                    e.preventDefault();
155                    self.pageRemoveItem(this);
156                });
157                
158                /* Bind: Page - Item - Adding to cart event */
159                self.$documentBody.on('adding_to_cart', function(e, $thisbutton, data) {
160                    $thisbutton.closest('.nmww-item').addClass('adding');
161                });
162                
163                /* Bind: Page - Item - Added to cart event */
164                self.$documentBody.on('added_to_cart', function(e, fragments, cartHash, $thisbutton) {
165                    $thisbutton.closest('.nmww-item').removeClass('adding');
166                });
167            }
168            
169            /* Bind: Share - Button click */
170            $('#nmww-share').on('click', '.nmww-share-button', function() {
171                self.shareSocialButtonClick(this);
172            });
173		},
174        
175        /**
176         * Set up: Init
177         */
178        setUp: function() {
179            var self = this;
180            
181            // Get cookie items
182            if (undefined !== Cookies.getJSON) { // Note: Cookies.getJSON was removed from "Cookies" library in a recent version
183                var cookieItems = Cookies.getJSON(self.cookieName);
184            } else {
185                var cookieItems = Cookies.get(self.cookieName);
186                // Parse to JSON, if possible
187                try {
188                    cookieItems = JSON.parse(cookieItems);
189                } catch (error) {
190                    //console.error('Essential Wishlist - Unable to JSON parse cookie items: '+error);
191                }
192            }
193            
194            // Wishlist button (menus): Sett cookie count early to avoid delay
195            if (cookieItems && cookieItems.length) {
196                self.wishlistButtonUpdate(cookieItems.length);
197            }
198            
199            // Is this the Wishlist page?
200            if (self.$pageItemsTable.length) {
201                var $pageItemsTableTbody = self.$pageItemsTable.children('tbody'),
202                    itemsCount = $pageItemsTableTbody.children().length;
203                
204                if (itemsCount > 0) {
205                    // Get and set wishlist items
206                    var items = self.$pageItemsTable.data('items'),
207                        currentUrl = new URL(window.location.href),
208                        setCookie = (currentUrl.searchParams.has('nmwshare')) ? false : true; // Don't set cookie when share URL is used
209                    
210                    self.itemsSet(items, setCookie); // Args: items, set cookie
211                    
212                    self.timestampUpdateAll($pageItemsTableTbody, 'long');
213                    // Update item timestamps once every minute
214                    self.timestampsInterval = setInterval(function() {
215                        self.timestampUpdateAll($pageItemsTableTbody, 'long');
216                    }, 60000); // 60 seconds
217                }
218                
219                self.wishlistButtonUpdate(itemsCount);
220                
221                // No need to get data on Wishlist page
222                return;
223            }
224            
225            var ajaxLoadData = true,
226                data = {
227                    nonce: nmww_data.nonce
228                };
229            
230            if (self.isLoggedIn) { 
231                // Get items via AJAX when logged-in
232                data['get_items'] = 1;
233            } else {
234                // Does the Wishlist cookie exist?
235                if (! cookieItems || cookieItems.length == 0) {
236                    self.itemsSet([], true); // Args: items, set cookie
237                    
238                    // Buttons: Include and set states
239                    self.singleProductAddButton();
240                    self.buttonsSetState();
241                    self.wishlistButtonUpdate();
242                    self.shareUrlUpdate();
243                    
244                    self.panelSetState('empty');
245                    
246                    // No need to send an AJAX request if user is logged-out and no items are added
247                    ajaxLoadData = false;
248                } else {
249                    self.itemsSet(cookieItems, false); // Args: items, set cookie
250                }
251            }
252            
253            if (ajaxLoadData) {
254                var ajaxUrl = woocommerce_params.wc_ajax_url.toString().replace('%%endpoint%%', 'nmww_get_data');
255                
256                // Only get panel items when panel is enabled
257                if (self.$panelWrapper.length) {
258                    data['get_panel_items'] = 1;
259                }
260                
261                $.ajax({
262                    type: 'POST',
263                    url: ajaxUrl,
264                    data: data,
265                    dataType: 'json',
266                    cache: false,
267                    headers: {'cache-control': 'no-cache'},
268                    error: function(XMLHttpRequest, textStatus, errorThrown) {
269                        console.log('NMWW: AJAX error - setUp() - ' + errorThrown);
270                    },
271                    success: function(json) {
272                        if (self.isLoggedIn) { 
273                            // Set wishlist items
274                            if (json.hasOwnProperty('items') && json.items.length > 0) {
275                                self.itemsSet(json.items, true); // Args: items, set cookie
276                            }
277                        }
278                        
279                        // Buttons: Include and set states
280                        self.singleProductAddButton();
281                        self.buttonsSetState();
282                        self.wishlistButtonUpdate();
283                        self.shareUrlUpdate();
284                        
285                        // Panel: Include content and set state
286                        if (json.hasOwnProperty('panel_items') && Object.keys(json.panel_items).length > 0) {
287                            self.panelCountUpdate(json.count.number, json.count.text);
288                            self.panelAddItems(json.panel_items);
289                            self.panelSetState('has-items');
290                        } else {
291                            self.panelSetState('empty');
292                        }
293                    }
294                });
295            }
296        },
297        
298        /**
299         * Items: Set
300         */
301        itemsSet: function(items, setCookie) {
302            var self = this;
303            
304            self.wishlistItems = items; // Note: There's no need to set this when removing/updating (since "self.wishlistItems" has already been updated), but it makes no difference..
305            
306            if (setCookie) {
307                Cookies.set(self.cookieName, JSON.stringify(items), { expires: self.cookieExpires });
308            }
309        },
310        
311        /**
312         * Items - Array: Check if items exists
313         *
314         * Based on https://stackoverflow.com/a/21689286
315         */
316        itemsArrayItemExists: function(itemId) {
317            var self = this,
318                existingItem = self.wishlistItems.findIndex(existingItem => existingItem.id === itemId);
319            return existingItem;
320        },
321        
322        
323        /**
324         * Items - Array: Add item
325         */
326        itemsArrayAdd: function(item) {
327            var self = this;
328            self.wishlistItems.unshift(item); // Prepend product-id to array (display newly added products first)
329        },
330        
331        
332        /**
333         * Items - Array: Remove item
334         */
335        itemsArrayRemove: function(existingItem) {
336            var self = this;
337            self.wishlistItems.splice(existingItem, 1);
338        },
339        
340        /**
341         * Items: Update
342         */
343        itemsUpdate: function(item, isAdding) {
344            var self = this;
345            
346            self.itemsSet(self.wishlistItems, true); // Args: items, set cookie
347            
348            var data = {
349                action: 'nmww_update_items',
350                nonce: nmww_data.nonce,
351                is_adding: (isAdding) ? 1 : 0,
352                item_added: (isAdding) ? JSON.stringify(item) : 0,
353            };
354            
355            if (self.isLoggedIn) {
356                // Convert IDs array to string
357                data['items'] = JSON.stringify(self.wishlistItems);
358            }
359            
360            // AJAX: Update items in user-meta (if logged in) and add panel item
361            //self.itemsUpdateAjax = $.ajax({
362            $.ajax({
363                type: 'POST',
364                url: woocommerce_params.ajax_url,
365                data: data,
366                dataType: 'json',
367                cache: false,
368                headers: {'cache-control': 'no-cache'},
369                /*complete: function() {
370                    self.itemsUpdateAjax = false;
371                },*/
372                error: function(XMLHttpRequest, textStatus, errorThrown) {
373                    console.log('NMWW: AJAX error - itemsUpdate() - ' + errorThrown);
374                    
375                    self.buttonSetState(item.id, 'loaded');
376                },
377                success: function(json) {
378                    // Panel: Update count
379                    if (json.hasOwnProperty('count')) {
380                        self.panelCountUpdate(json.count.number, json.count.text);
381                    }
382                    
383                    // Panel - Adding: Prepend new item
384                    if (json.hasOwnProperty('item_added')) {
385                        self.panelAddItem(json.item_added);
386                    }
387                    
388                    // Panel - Removing: Remove item
389                    if (! isAdding) {
390                        self.panelRemoveItem(item.id);
391                    }
392                    
393                    self.buttonSetState(item.id, 'loaded');
394                }
395            });
396        },
397        
398        /**
399         * Wishlist button(s): Update menu button(s) (set class and count)
400         */
401        wishlistButtonUpdate: function(count) {
402            // Make sure menu button count is enabled
403            if ('0' == nmww_data.show_menu_count) {
404                return;
405            }
406            
407            var self  = this,
408                $wishlistButtons = $('.nmww-wishlist-button');
409            
410            if ($wishlistButtons.length) {
411                var $wishlistButton,
412                    $wishlistButtonCount,
413                    $wishlistButtonAnchor,
414                    wishlistButtonCountElem,
415                    count = (count) ? count : self.wishlistItems.length;
416                
417                $wishlistButtons.each(function() {
418                    $wishlistButton = $(this);
419                    
420                    // Add count element, or set its count if already added
421                    $wishlistButtonCount = $wishlistButton.find('.nmww-wishlist-button-count');
422                    if ($wishlistButtonCount.length) {
423                        $wishlistButtonCount.text(count);
424                    } else {
425                        // Append count element to child "a" tag, or button container if no tag is found
426                        $wishlistButtonAnchor = $wishlistButton.children('a');
427                        wishlistButtonCountElem = '<span class="nmww-wishlist-button-count">'+count+'</span>';
428                        if ($wishlistButtonAnchor.length) {
429                            $wishlistButtonAnchor.append(wishlistButtonCountElem);
430                        } else {
431                            $wishlistButton.append(wishlistButtonCountElem);
432                        }
433                    }
434                    
435                    // Toggle "has items" class for button element
436                    if (count > 0) {
437                        $wishlistButton.addClass('has-items');
438                    } else {
439                        $wishlistButton.removeClass('has-items');
440                    }
441                });
442            }
443        },
444        
445        /**
446         * Buttons (sitewide): Add button classes for added items (classes must be set with JS when page-caching is enabled)
447         */
448        buttonsSetState: function() {
449            var self = this;
450            
451            for (var i = 0; i < self.wishlistItems.length; i++) {
452                $('.nmww-item-' + self.wishlistItems[i].id + '-button').attr('title', nmww_data.button_title_remove).addClass('added');
453            }
454            
455            // Add class to show buttons
456            self.$body.addClass('nmww-buttons-ready');
457            
458            // All Products block: Set buttons states
459            var $allProductsBlock = $('.wc-block-all-products').first();
460            if ($allProductsBlock.length) {
461                // Bind MutationObserver for main block container
462                var allProductsBlockObserver = new MutationObserver((mutationList, observer) => {
463                    // Does the block contain any "loader" elements? If not, block should be done loading
464                    if (! $allProductsBlock.find('.is-loading').length) {
465                        // Set button states
466                        for (var i = 0; i < self.wishlistItems.length; i++) {
467                            $allProductsBlock.find('.nmww-item-' + self.wishlistItems[i].id + '-button').attr('title', nmww_data.button_title_remove).addClass('added');
468                        }
469                    }
470                });
471                allProductsBlockObserver.observe($allProductsBlock[0], {attributes: false, childList: true, subtree: true});
472            }
473        },
474		
475        /**
476         * Button (single): Set state (change button class and title)
477         */
478        buttonSetState: function(itemId, state) {
479            var self = this,
480                $buttons = $('.nmww-item-' + itemId + '-button'); // Get all wishlist buttons with the same product-id
481            
482            switch (state) {
483                case 'loading':
484                    $buttons.addClass('loading');
485                    break;
486                case 'adding':
487                    $buttons.addClass('added');
488                    $buttons.attr('title', nmww_data.button_title_remove);
489                    break;
490                case 'removing':
491                    $buttons.removeClass('added');
492                    $buttons.attr('title', nmww_data.button_title_add);
493                    break;
494                case 'loaded':
495                    $buttons.removeClass('loading');
496                    break;
497            }
498        },
499        
500        /**
501         * Button (single): Add/remove item
502         */
503        buttonToggle: function($button, itemId) {
504            var self = this,
505                item = {
506                    id: itemId,
507                    timestamp: Date.now()
508                },
509                isAdding = true;
510            
511            // Does the product-id exist in "self.wishlistItems"?
512            var existingItem = self.itemsArrayItemExists(itemId);
513            
514            if (-1 == existingItem) {
515                self.itemsArrayAdd(item);
516                
517                self.panelSetState('loading-has-items');
518            } else {
519                isAdding = false;
520                
521                self.itemsArrayRemove(existingItem);
522                
523                self.panelItemSetState(self.$panelWrapper.children('.nmww-panel-item-'+itemId), 'removing');
524            }
525            
526            self.itemsUpdate(item, isAdding);
527            
528            self.wishlistButtonUpdate();
529            self.shareUrlUpdate();
530            
531            var buttonState = (isAdding) ? 'adding' : 'removing';
532            self.buttonSetState(itemId, buttonState);
533            
534            // Open panel (if enabled)
535            if (isAdding && '0' != nmww_data.panel_open_when_adding) {
536                setTimeout(function() {
537                    self.panelOpen();
538                }, 250);
539            }
540        },
541        
542        /**
543         * Single product: Add gallery button
544         */
545        singleProductAddButton: function() {
546            var self = this;
547            
548            if (nmww_data.single_product_gallery_button !== '0' && self.$body.hasClass('single-product')) {
549                var productId = nmww_data.single_product_id;
550                
551                if (productId) {
552                    // Note: "Active" state is set separately with self.buttonsSetState() function
553                    var button = '<a class="nmww-button nmww-item-' + productId + '-button for-single-product-gallery" data-product-id="' + productId + '" title="' + nmww_data.button_title_add + '"><i class="' + nmww_data.button_icon_class + '"></i></a>';
554
555                    $('.woocommerce-product-gallery').prepend(button);
556                }
557            }
558        },
559        
560        /**
561         * Panel: Open
562         */
563        panelOpen: function() {
564            var self = this;
565            
566            // Set scrollbar width so an offset can be set via CSS when opening panel
567            document.documentElement.style.setProperty('--nmww--scrollbar-width', (window.innerWidth - document.documentElement.clientWidth) + 'px');
568            
569            self.$body.addClass('nmww-panel-overlay-show');
570            self.$body.addClass('nmww-panel-open');
571            
572            // Update item timestamps once every minute
573            self.timestampsInterval = setInterval(function() {
574                self.timestampUpdateAll(self.$panelWrapper);
575            }, 60000); // 60 seconds
576            
577            self.timestampUpdateAll(self.$panelWrapper);
578        },
579        
580        /**
581         * Panel: Close
582         */
583        panelClose: function() {
584            var self = this;
585            
586            // Stop item timestamp updating
587            clearInterval(self.timestampsInterval); 
588            
589            self.$body.removeClass('nmww-panel-open');
590            
591            self.$body.addClass('nmww-panel-closing'); // Start closing (fade out overlay)
592            setTimeout(function() {
593                self.$body.removeClass('nmww-panel-overlay-show nmww-panel-closing'); // Hide
594                
595                // Add to cart buttons: Remove "added" class
596                //self.$panelWrapper.children().find('a.added').removeClass('added');
597            }, 400);
598        },
599        
600        /**
601         * Panel: Set state
602         */
603        panelSetState: function(state) {
604            var states = {
605                    'has-items': 'has-items',
606                    'empty': 'is-empty',
607                    'loading': 'is-loading',
608                    'loading-has-items': 'has-items is-loading'
609                },            
610                panelClass = states[state];
611            
612            $('#nmww-panel').removeClass('is-loading is-empty has-items').addClass(panelClass);
613        },
614        
615        /**
616         * Panel: Set ready class
617         */
618        panelSetReadyClass: function() {
619            var self = this;
620            self.$body.addClass('nmww-panel-ready');
621        },
622        
623        /**
624         * Panel: Add multiple items
625         */
626        panelAddItems: function(items) {
627            var self = this;
628            
629            // Append items
630            for (var item in items) {
631                if (items.hasOwnProperty(item)) {
632                    self.$panelWrapper.append(items[item]);
633                }
634            }
635            
636            // Update timestamps
637            self.timestampUpdateAll(self.$panelWrapper);
638        },
639        
640        /**
641         * Panel: Add single item
642         */
643        panelAddItem: function(item) {
644            var self = this;
645            
646            // Prepend single item
647            self.$panelWrapper.prepend(item);
648            
649            self.panelSetState('has-items');
650            
651            // Set timestamp
652            var $item = self.$panelWrapper.children().first();
653            self.timestampUpdate($item);
654        },
655        
656        /**
657         * Panel: Remove single item
658         */
659        panelRemoveItem: function(itemId) {
660            var self = this;
661            self.$panelWrapper.children('.nmww-panel-item-' + itemId).remove();
662            
663            // Show "empty" container if not items are added
664            if (! self.$panelWrapper.children().length) {
665                self.panelSetState('empty');
666            }
667        },
668        
669        /**
670         * Panel: Item - Remove button action
671         */
672        panelItemOnRemoveButton: function(button) {
673            var self = this,
674                $button = $(button),
675                $itemWrapper = $button.closest('.nmww-panel-item'),
676                itemId = $itemWrapper.data('product-id'),
677                item = {
678                    id: itemId
679                };
680
681            if ($itemWrapper.hasClass('removing')) {
682                return;
683            }
684            
685            $itemWrapper.addClass('removing');
686            
687            // Does the product-id exist in "self.wishlistItems"?
688            var existingItem = self.itemsArrayItemExists(itemId);
689            if (-1 !== existingItem) {
690                self.itemsArrayRemove(existingItem);
691            }
692            self.itemsUpdate(item, false); // Args: item, isAdding
693            
694            self.wishlistButtonUpdate();
695            self.shareUrlUpdate();
696            
697            self.buttonSetState(itemId, 'removing');
698        },
699        
700        /**
701         * Panel: Item - Set state
702         */
703        panelItemSetState: function($item, state) {
704            var self = this,
705                states = {
706                    loading: 'loading',
707                    removing: 'removing'
708                },            
709                itemClass = states[state];
710            
711            $item.addClass(itemClass);
712        },
713        
714        /**
715         * Panel: Count - Update
716         */
717        panelCountUpdate: function(count, countText) {
718            var text = (count > 0) ? '(' + countText + ')' : '';
719            $('#nmww-panel-item-count').text(text);
720        },
721        
722        /**
723         * Page: Remove item
724         */
725        pageRemoveItem: function(button) {
726            // Show "loader" overlay
727            $('#nmww-overlay').addClass('show');
728
729            var	self = this,
730                $itemTr = $(button).closest('tr'),
731                itemId = $itemTr.data('product-id'),
732                item = {
733                    id: itemId
734                };
735            
736            $itemTr.addClass('removing');
737            
738            // Does the product-id exist in "self.wishlistItems"?
739            var existingItem = self.itemsArrayItemExists(itemId);
740            if (-1 !== existingItem) {
741                self.itemsArrayRemove(existingItem);
742            }
743            self.itemsUpdate(item, false); // Args: item, isAdding
744            
745            setTimeout(function() {
746                $itemTr.remove();
747                
748                self.wishlistButtonUpdate();
749                self.shareUrlUpdate();
750                
751                // Show "wishlist empty" container?
752                if (self.$pageItemsTable.children('tbody').children().length == 0) {
753                    self.$pageItemsTable.closest('.nmww-items').addClass('hide');
754                    $('#nmww-empty').addClass('show');
755                }
756                
757                // Hide "loader" overlay
758                $('#nmww-overlay').removeClass('show');
759            }, 500);
760        },
761        
762        /**
763         * Timestamp: Update for all items
764         */
765        timestampUpdateAll: function($itemsWrapper, style) {
766            var self = this;
767            $itemsWrapper.children().each(function() {
768                self.timestampUpdate($(this), style);
769            });
770        },
771        
772        /**
773         * Timestamp: Update for single item - Based on: https://stackoverflow.com/a/69122877
774         */
775        timestampUpdate: function($item, style) {
776            var $itemTimestampElem = $item.find('.nmww-item-timestamp'),
777                itemTimestamp = $itemTimestampElem.data('timestamp'),
778                secondsElapsed = (itemTimestamp - Date.now()) / 1000,
779                locale = $('html').attr('lang'),
780                style = (style) ? style : 'long', // Alt default: '
780short'
781                rtf = new Intl.RelativeTimeFormat(locale, {numeric: 'auto', style: style}),
782                units = {
783                    years: 3600 * 24 * 365,
784                    months: 3600 * 24 * 30,
785                    weeks: 3600 * 24 * 7,
786                    days: 3600 * 24,
787                    hours: 3600,
788                    minutes: 60,
789                    seconds: 1
790                },
791                delta,
792                timeFormatted = '';
793            
794            // Has less than a minute passed?
795            if (Math.abs(secondsElapsed) < 60) {
796                // Return "now"
797                timeFormatted = rtf.format(0, 'seconds');
798            } else {
799                for (var unit in units) {
800                    if (units[unit] < Math.abs(secondsElapsed)) {
801                        delta = secondsElapsed / units[unit];
802                        timeFormatted = rtf.format(Math.round(delta), unit);
803                        break;
804                    }
805                }
806            }
807            
808            $itemTimestampElem.text(timeFormatted);
809        },
810        
811        /**
812         * Share: Social button/icon click
813         */
814        shareSocialButtonClick: function(button) {
815            var $button = $(button),
816                href = $button.data('href'),
817                shareUrl = $('#nmww-share').attr('data-share-url'),
818                target = $button.attr('target');
819            
820            // Add Wishlist URL to share URL
821            href = href.toString().replace('%%wishlist_url%%', shareUrl);
822            
823            window.open(href, target);
824        },
825        
826        /**
827         * Share: Update URL
828         */
829        shareUrlUpdate: function() {
830            var self = this,
831                itemIds = [];
832            
833            // No need to update if Wishlist is empty
834            if (self.wishlistItems.length == 0) {
835                return;
836            }
837            
838            for (var i = 0; i < self.wishlistItems.length; i++) {
839                itemIds.push(self.wishlistItems[i].id);
840            }
841            itemIds = itemIds.join(','); // Convert IDs array into comma separated string
842            
843            var shareUrl = nmww_data.share_base_url+encodeURIComponent(itemIds);
844            
845            $('#nmww-share').attr('data-share-url', shareUrl);
846        },
847        
848        /**
849         * Share: Copy URL
850         */
851        /*shareUrlCopy: function(copyButton) {
852            var shareInput = document.getElementById('nmww-panel-share-url-input');
853
854            // Wait until "has-copied" class has been removed
855            if (copyButton.classList.contains('has-copied')) {
856                return;
857            }
858
859            shareInput.select(); // Select text field
860            shareInput.setSelectionRange(0, 99999); // For mobile devices
861
862            // Copy to clipboard
863            navigator.clipboard.writeText(shareInput.value);
864
865            // Add/remove "copied" class
866            copyButton.classList.add('has-copied');
867            setTimeout(function() {
868                copyButton.classList.remove('has-copied');
869            }, 2000);
870        },*/
871	}
872	
873	$(function() { // Doc ready
874		// Initialize script
875		$.nmwwPlugin = new NMWW_Plugin();
876	});
877	
878})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.