1// Favorites module â localStorage for all, DB sync for logged-in users 2var Favorites = (function() { 3 var STORAGE_KEY = 'nmw_favorites'; 4 var _list = []; // array of release IDs (integers) 5 var _synced = false; 6 var _iconClass = (typeof shop !== 'undefined' && shop.b2b) ? 'fa-star' : 'fa-heart'; 7 8 function _save() { 9 try { 10 localStorage.setItem(STORAGE_KEY, JSON.stringify(_list)); 11 } catch(e) {} 12 } 13 14 function _load() { 15 try { 16 var stored = localStorage.getItem(STORAGE_KEY); 17 _list = stored ? JSON.parse(stored) : []; 18 if (!Array.isArray(_list)) _list = []; 19 } catch(e) { 20 _list = []; 21 } 22 } 23 24 function _isLoggedIn() { 25 try { 26 var c = getCookie('login'); 27 if (c) { 28 var d = JSON.parse(c); 29 return d && d.idxFrontUser; 30 } 31 } catch(e) {} 32 return false; 33 } 34 35 function _getReleaseId($el) { 36 var releaseId = null; 37 // Try from shopitem ID 38 var id = $el.attr('id'); 39 if (id && id.indexOf('shopitem') === 0) { 40 releaseId = id.replace('shopitem', ''); 41 } 42 if (!releaseId) { 43 // Try from release_id links 44 var $link = $el.find('a[href*="release_id="]'); 45 if ($link.length) { 46 var match = $link.attr('href').match(/release_id=(\d+)/); 47 if (match) releaseId = match[1]; 48 } 49 } 50 if (!releaseId) { 51 // Try from addCart onclick 52 var $cart = $el.find('[onclick*="addCart"]'); 53 if ($cart.length) { 54 var cartMatch = $cart.attr('onclick').match(/addCart\('[^']*','(\d+)'\)/); 55 if (cartMatch) releaseId = cartMatch[1]; 56 } 57 } 58 return releaseId; 59 } 60 61 function _injectButtons() { 62 var _btnHtml = function(releaseId) { 63 return '<span class="favorite-btn" data-release="' + releaseId + '" data-ajax="no-go" title="Favorite"><i class="far ' + _iconClass + '"></i></span>'; 64 }; 65 66 // List view: .wishlist-item placeholders with data-release attribute 67 $('.wishlist-item[data-release]').each(function() { 68 if ($(this).find('.favorite-btn').length > 0) return; 69 var releaseId = $(this).data('release'); 70 if (!releaseId) return; 71 $(this).html(_btnHtml(releaseId)); 72 }); 73 74 // Grid view: .shopcol items with shopitem ID (top-level grid cards) 75 $('.shopcol[id^="shopitem"]').each(function() { 76 if ($(this).find('.favorite-btn').length > 0) return; 77 var releaseId = _getReleaseId($(this)); 78 if (!releaseId) return; 79 var $firstLink = $(this).find('a').first(); 80 if ($firstLink.length) { 81 $('<div class="favorite-row">' + _btnHtml(releaseId) + '</div>').insertAfter($firstLink); 82 } else { 83 $(this).prepend('<div class="favorite-row">' + _btnHtml(releaseId) + '</div>'); 84 } 85 }); 86 } 87 88 function _updateUI() { 89 _injectButtons(); 90 $('.favorite-btn').each(function() { 91 var releaseId = parseInt($(this).data('release')); 92 if (_list.indexOf(releaseId) > -1) { 93 $(this).addClass('is-favorite'); 94 $(this).find('i').removeClass('far').addClass('fas'); 95 } else { 96 $(this).removeClass('is-favorite'); 97 $(this).find('i').removeClass('fas').addClass('far'); 98 } 99 }); 100 // Update favorites count in menu 101 var count = _list.length; 102 $('.favorites-count').text(count > 0 ? '(' + count + ')' : ''); 103 if (count > 0) { 104 $('.favorites-count').show(); 105 $('.favorites-count').closest('a').find('i.fa-heart').removeClass('far').addClass('fas'); 106 } else { 107 $('.favorites-count').hide(); 108 $('.favorites-count').closest('a').find('i.fa-heart').removeClass('fas').addClass('far'); 109 } 110 } 111 112 function toggle(releaseId) { 113 releaseId = parseInt(releaseId); 114 var idx = _list.indexOf(releaseId); 115 if (idx > -1) { 116 _list.splice(idx, 1); 117 } else { 118 _list.push(releaseId); 119 } 120 _save(); 121 _updateUI(); 122 123 // If logged in, also sync to DB 124 if (_isLoggedIn()) { 125 $.post('/ajax_favorites.php', { 126 action: 'toggle', 127 idxRelease: releaseId 128 }); 129 } 130 } 131 132 function isFavorite(releaseId) { 133 return _list.indexOf(parseInt(releaseId)) > -1; 134 } 135 136 function getList() { 137 return _list.slice(); 138 } 139 140 function syncOnLogin() { 141 if (!_isLoggedIn()) return; 142 if (_synced) return; 143 _synced = true; 144 145 // Send local favorites to DB, get merged list back 146 $.post('/ajax_favorites.php', { 147 action: 'sync', 148 releases: JSON.stringify(_list) 149 }, function(data) { 150 if (data && data.success && data.favorites) { 151 _list = data.favorites; 152 _save(); 153 _updateUI(); 154 } 155 }, 'JSON'); 156 } 157
158 function init() { 159 _load(); 160 161 // Delegate click handler for favorite buttons 162 $(document).on('click', '.favorite-btn', function(e) { 163 e.preventDefault(); 164 e.stopPropagation(); 165 var releaseId = $(this).data('release'); 166 toggle(releaseId); 167 }); 168 169 // If logged in, sync with DB 170 if (_isLoggedIn()) { 171 syncOnLogin(); 172 } else { 173 _updateUI(); 174 } 175 176 } 177 178 function clearAll(callback) { 179 var toRemove = _list.slice(); 180 _list = []; 181 _save(); 182 _updateUI(); 183 184 if (_isLoggedIn() && toRemove.length > 0) { 185 // Toggle each favorite off in the DB 186 var pending = toRemove.length; 187 function done() { 188 pending--; 189 if (pending <= 0 && callback) callback(); 190 } 191 toRemove.forEach(function(releaseId) { 192 $.post('/ajax_favorites.php', { 193 action: 'toggle', 194 idxRelease: releaseId 195 }).always(done); 196 }); 197 } else { 198 if (callback) callback(); 199 } 200 } 201 202 return { 203 init: init, 204 toggle: toggle, 205 isFavorite: isFavorite, 206 getList: getList, 207 syncOnLogin: syncOnLogin, 208 updateUI: _updateUI, 209 clearAll: clearAll 210 }; 211})(); 212 213$(document).ready(function() { 214 Favorites.init(); 215});
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.