1jQuery(function ($) { 2 3 var ajaxUrl = wc_cart_params.ajax_url; 4 var nonce = typeof anchor_cart !== 'undefined' ? anchor_cart.nonce : ''; 5 6 // --------------------------------------------------------------------------- 7 // Helpers 8 // --------------------------------------------------------------------------- 9 10 function replaceCart(html) { 11 var $current = $('.cart_wrapper'); 12 if ($current.length) { 13 $current.replaceWith(html); 14 } else { 15 // Fallback 16 $('.cart_wrapper').first().html(html); 17 } 18 } 19 20 function updateNotices(html) { 21 $('.cart-notices').html(html); 22 } 23 24 // Always query fresh â overlay is inside .sample-cart-wrapper which gets 25 // replaced when the cart empties, so a cached reference would go stale. 26 function showOverlay() { 27 $('.cart-loading-overlay').css('display', 'flex'); 28 } 29 30 function hideOverlay() { 31 $('.cart-loading-overlay').css('display', 'none'); 32 } 33 34 /** 35 * Tell WooCommerce to re-fetch all cart fragments (mini-cart count bubble etc.). 36 * WC's cart-fragments.js listens for this event on document.body. 37 */ 38 function refreshCartFragments() { 39 $(document.body).trigger('wc_fragment_refresh'); 40 } 41 42 43 // --------------------------------------------------------------------------- 44 // REMOVE SINGLE CART ITEM 45 // --------------------------------------------------------------------------- 46 47 var removingKeys = {}; 48 49 $('body').on('click', '.cart-remove-item', function (e) { 50 e.preventDefault(); 51 52 var $btn = $(this); 53 var cartItemKey = $btn.data('cart-item-key'); 54 var $row = $btn.closest('.cart-product-row'); 55 var $categoryBlock = $row.closest('.cart-category-block'); 56 57 if ($btn.hasClass('removing')) return; 58 59 removingKeys[cartItemKey] = true; 60 $btn.addClass('removing').html('<span class="cart-loader"></span>'); 61 $row.addClass('removing'); 62 63 $.ajax({
64 type: 'POST', 65 url: ajaxUrl, 66 data: { 67 action: 'woocommerce_remove_cart_item', 68 cart_item_key: cartItemKey, 69 nonce: nonce, 70 }, 71 72 success: function (response) { 73 if (!response.success) { 74 delete removingKeys[cartItemKey]; 75 $btn.removeClass('removing').html(trashIconSVG()); 76 $row.removeClass('removing'); 77 return; 78 } 79 80 if (response.data.notices) { 81 updateNotices(response.data.notices); 82 } 83 84 if (response.data.cart) { 85 replaceCart(response.data.cart); 86 87 // WC's cart-fragments.js triggers window.location.reload() when the 88 // cart hash from get_refreshed_fragments differs from the stored hash. 89 // It reads the hash from BOTH sessionStorage AND the woocommerce_cart_hash 90 // cookie. Clearing only sessionStorage (previous attempt) left the cookie 91 // intact â still caused a mismatch â still reloaded. 92 // 93 // Fix: clear both the sessionStorage keys AND the cookie so WC has no 94 // stored baseline. With stored_hash === '', WC's condition 95 // (stored_hash !== '' && new_hash !== stored_hash) evaluates to false 96 // â fragments update silently, no reload. 97 98 if (typeof wc_cart_fragments_params !== 'undefined') { 99 try { 100 sessionStorage.removeItem(wc_cart_fragments_params.cart_hash_key); 101 sessionStorage.removeItem(wc_cart_fragments_params.fragment_name); 102 } catch (e) { } 103 } 104 105 // Clear the cart hash cookie on all possible paths 106 var cookiePath = (typeof wc_cart_fragments_params !== 'undefined' && wc_cart_fragments_params.cookie_hash) 107 ? wc_cart_fragments_params.cookie_hash 108 : '/'; 109 document.cookie = 'woocommerce_cart_hash=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=' + cookiePath + ';'; 110 document.cookie = 'woocommerce_items_in_cart=0; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=' + cookiePath + ';'; 111 112 // NOW it is safe to refresh fragments â WC will write the new empty 113 // hash as a fresh baseline instead of comparing against a stale one. 114 refreshCartFragments(); 115 116 delete removingKeys[cartItemKey]; 117 return; 118 } 119 120 // Normal case â optimistic row removal 121 refreshCartFragments(); 122 123 $row.fadeOut(200, function () { 124 $row.remove(); 125 delete removingKeys[cartItemKey]; 126 127 var remaining = $categoryBlock.find('.cart-product-row').length; 128 $categoryBlock.find('.item-count-badge').text(remaining + ' Items'); 129 130 var totalRemaining = $('.cart-product-row').length; 131 var label = totalRemaining === 1 ? 'Product Added' : 'Products Added'; 132 $('.cart-total-count').text(totalRemaining + ' ' + label); 133 134 if (remaining === 0) { 135 $categoryBlock.remove(); 136 // No more empty-cart AJAX here â server now handles the last-item case 137 } 138 }); 139 }, 140 141 error: function () { 142 delete removingKeys[cartItemKey]; 143 $btn.removeClass('removing').html(trashIconSVG()); 144 $row.removeClass('removing'); 145 }, 146 }); 147 }); 148 149 150 // --------------------------------------------------------------------------- 151 // RESTORE SINGLE ITEM (Undo remove) 152 // --------------------------------------------------------------------------- 153 154 $('body').on('click', '.anchor-restore-item', function (e) { 155 e.preventDefault(); 156 157 var cartItemKey = $(this).data('cart-item-key'); 158 var $link = $(this); 159 160 if ($link.hasClass('processing') || removingKeys[cartItemKey]) return; 161 162 $link.addClass('processing').html('Restoring⦠<span class="cart-loader"></span>'); 163 showOverlay(); 164 165 $.ajax({
166 type: 'POST', 167 url: ajaxUrl, 168 data: { 169 action: 'anchor_restore_cart_item', 170 cart_item_key: cartItemKey, 171 nonce: nonce, 172 }, 173 174 success: function (response) { 175 if (response.success && response.data.cart) { 176 replaceCart(response.data.cart); 177 } 178 if (response.success && response.data.notices) { 179 updateNotices(response.data.notices); 180 } 181 182 // Give the DOM a tiny moment to settle before letting fragments run 183 setTimeout(function () { 184 refreshCartFragments(); 185 }, 100); 186 }, 187 188 error: function () { 189 $link.removeClass('processing').html('Undo?'); 190 }, 191 192 complete: function () { 193 hideOverlay(); 194 $link.removeClass('processing'); 195 }, 196 }); 197 }); 198 199 200 // --------------------------------------------------------------------------- 201 // OPEN EMPTY CART MODAL 202 // --------------------------------------------------------------------------- 203 204 $('body').on('click', '.empty-cart-btn', function (e) { 205 e.preventDefault(); 206 var modal = new bootstrap.Modal(document.getElementById('emptyCartModal')); 207 modal.show(); 208 }); 209 210 211 // --------------------------------------------------------------------------- 212 // CONFIRM EMPTY CART 213 // --------------------------------------------------------------------------- 214 215 $('body').on('click', '.confirm-empty-cart', function () { 216 217 var $btn = $(this); 218 var modalEl = document.getElementById('emptyCartModal'); 219 var modal = bootstrap.Modal.getInstance(modalEl); 220 221 if ($btn.hasClass('processing')) return; 222 223 $btn.addClass('processing').text('Emptyingâ¦').prop('disabled', true); 224 showOverlay(); 225 226 $.ajax({
227 type: 'POST', 228 url: ajaxUrl, 229 data: { action: 'anchor_empty_cart', nonce: nonce }, 230 231 success: function (response) { 232 modal.hide(); 233 234 if (response.success && response.data.cart) { 235 replaceCart(response.data.cart); 236 } 237 if (response.success && response.data.notices) { 238 updateNotices(response.data.notices); 239 } 240 refreshCartFragments(); 241 }, 242 243 complete: function () { 244 $btn.removeClass('processing').text('Empty Cart').prop('disabled', false); 245 hideOverlay(); 246 }, 247 }); 248 }); 249 250 251 // --------------------------------------------------------------------------- 252 // UNDO EMPTY CART 253 // 254 // The .cart-loading-overlay element does not exist in cart-empty.php, so we 255 // cannot show the full-page overlay here. Instead we give inline feedback by 256 // turning the "Undo?" link itself into a micro-spinner. 257 // --------------------------------------------------------------------------- 258 259 $('body').on('click', '.undo-empty-cart', function (e) { 260 e.preventDefault(); 261 262 var $link = $(this); 263 264 if ($link.hasClass('processing')) return; 265 $link.addClass('processing').html('Restoring⦠<span class="cart-loader"></span>'); 266 267 $.ajax({
268 type: 'POST', 269 url: ajaxUrl, 270 data: { action: 'anchor_restore_cleared_cart', nonce: nonce }, 271 272 success: function (response) { 273 if (response.success && response.data.cart) { 274 replaceCart(response.data.cart); 275 } 276 if (response.success && response.data.notices) { 277 updateNotices(response.data.notices); 278 } 279 refreshCartFragments(); 280 }, 281 282 error: function () { 283 // Restore the link text so the user can try again. 284 $link.removeClass('processing').html('Undo?'); 285 }, 286 }); 287 }); 288 289 290 // --------------------------------------------------------------------------- 291 // Utility: trash icon SVG (restores remove button after AJAX error) 292 // --------------------------------------------------------------------------- 293 294 function trashIconSVG() { 295 return '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="24" viewBox="0 0 20 24" fill="none" aria-hidden="true">' 296 + '<path d="M15.7261 10.0234C15.5152 10.0234 15.3129 10.1072 15.1637 10.2564C15.0145 10.4056 14.9307 10.6079 14.9307 10.8189V19.7203C14.9079 20.1226 14.7271 20.4995 14.4276 20.769C14.1282 21.0385 13.7343 21.1788 13.3319 21.1592H6.98453C6.58213 21.1788 6.18831 21.0385 5.88885 20.769C5.5894 20.4995 5.40857 20.1226 5.38575 19.7203V10.8189C5.38575 10.6079 5.30195 10.4056 5.15278 10.2564C5.00361 10.1072 4.80129 10.0234 4.59034 10.0234C4.37938 10.0234 4.17706 10.1072 4.02789 10.2564C3.87872 10.4056 3.79492 10.6079 3.79492 10.8189V19.7203C3.81763 20.5446 4.16605 21.3262 4.76388 21.8941C5.36171 22.4619 6.16022 22.7697 6.98453 22.7501H13.3319C14.1562 22.7697 14.9548 22.4619 15.5526 21.8941C16.1504 21.3262 16.4988 20.5446 16.5215 19.7203V10.8189C16.5215 10.6079 16.4377 10.4056 16.2886 10.2564C16.1394 10.1072 15.9371 10.0234 15.7261 10.0234Z" fill="#014A8D"/>' 297 + '<path d="M16.522 7.6382H13.3404V6.04737C13.3404 5.83641 13.2566 5.63409 13.1074 5.48492C12.9582 5.33576 12.7559 5.25195 12.545 5.25195H7.77248C7.56153 5.25195 7.35921 5.33576 7.21004 5.48492C7.06087 5.63409 6.97707 5.83641 6.97707 6.04737V7.6382H3.79541C3.58446 7.6382 3.38214 7.722 3.23297 7.87117C3.0838 8.02034 3 8.22265 3 8.43361C3 8.64457 3.0838 8.84688 3.23297 8.99605C3.38214 9.14522 3.58446 9.22902 3.79541 9.22902H16.522C16.733 9.22902 16.9353 9.14522 17.0845 8.99605C17.2337 8.84688 17.3175 8.64457 17.3175 8.43361C17.3175 8.22265 17.2337 8.02034 17.0845 7.87117C16.9353 7.722 16.733 7.6382 16.522 7.6382ZM8.5679 7.6382V6.84278H11.7496V7.6382H8.5679Z" fill="#014A8D"/>' 298 + '<path d="M9.36231 17.9766V12.4087C9.36231 12.1977 9.27851 11.9954 9.12934 11.8463C8.98017 11.6971 8.77786 11.6133 8.5669 11.6133C8.35594 11.6133 8.15363 11.6971 8.00446 11.8463C7.85529 11.9954 7.77148 12.1977 7.77148 12.4087V17.9766C7.77148 18.1876 7.85529 18.3899 8.00446 18.539C8.15363 18.6882 8.35594 18.772 8.5669 18.772C8.77786 18.772 8.98017 18.6882 9.12934 18.539C9.27851 18.3899 9.36231 18.1876 9.36231 17.9766Z" fill="#014A8D"/>' 299 + '<path d="M12.5459 17.9766V12.4087C12.5459 12.1977 12.4621 11.9954 12.3129 11.8463C12.1638 11.6971 11.9614 11.6133 11.7505 11.6133C11.5395 11.6133 11.3372 11.6971 11.188 11.8463C11.0389 11.9954 10.9551 12.1977 10.9551 12.4087V17.9766C10.9551 18.1876 11.0389 18.3899 11.188 18.539C11.3372 18.6882 11.5395 18.772 11.7505 18.772C11.9614 18.772 12.1638 18.6882 12.3129 18.539C12.4621 18.3899 12.5459 18.1876 12.5459 17.9766Z" fill="#014A8D"/>' 300 + '</svg>'; 301 } 302 303});
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.