1(function () { 2 'use strict'; 3 4 window.dataLayer = window.dataLayer || []; 5 6 const config = window.SAMURAI_GA4_CONFIG || {}; 7 const ecommerceEvents = new Set([ 8 'view_item', 9 'view_item_list', 10 'select_item', 11 'add_to_cart', 12 'remove_from_cart', 13 'view_cart', 14 'begin_checkout', 15 'checkout_button_click', 16 'add_shipping_info', 17 'add_payment_info', 18 'payment_button_click', 19 'checkout_error', 20 'purchase' 21 ]); 22 23 const qoCartReported = new Map(); 24 const qoCartPending = new Map(); 25 const qoCartTimers = new Map(); 26 const variationPrices = new Map(); 27 let lastStandardAddIntent = null; 28 let lastRemoveIntent = null; 29 let checkoutErrorSentAt = 0; 30 31 function storageKey(key) { 32 return 'samurai_ga4_once:' + String(key || ''); 33 } 34 35 function canUseSessionStorage() { 36 try { 37 window.sessionStorage.setItem('__samurai_ga4_test', '1'); 38 window.sessionStorage.removeItem('__samurai_ga4_test'); 39 return true; 40 } catch (_) { 41 return false; 42 } 43 } 44 45 const hasSessionStorage = canUseSessionStorage(); 46 47 // --------------------------------------------------------------------- 48 // Durable one-shot store (purchase dedup). 49 // 50 // sessionStorage is per-tab and dies with the tab, so the same 51 // transaction_id was re-sent to GA4 whenever the order-received page was 52 // opened in a second tab or revisited later from My Account. localStorage 53 // is shared by every tab of the origin and survives the browser closing, 54 // so a transaction_id recorded here is never sent twice from this browser. 55 // Only 'purchase:<transaction_id>' keys use it; every other event keeps 56 // the existing per-session behaviour. 57 // --------------------------------------------------------------------- 58 const DURABLE_STORE_KEY = 'samurai_ga4_sent_once'; 59 const DURABLE_TTL_MS = 400 * 24 * 60 * 60 * 1000; 60 const DURABLE_MAX_ENTRIES = 60; 61 62 function canUseLocalStorage() { 63 try { 64 window.localStorage.setItem('__samurai_ga4_ls_test', '1'); 65 window.localStorage.removeItem('__samurai_ga4_ls_test'); 66 return true; 67 } catch (_) { 68 return false; 69 } 70 } 71 72 const hasLocalStorage = canUseLocalStorage(); 73 74 function isDurableKey(key) { 75 return String(key || '').indexOf('purchase:') === 0; 76 } 77 78 function readDurableEntries() { 79 try { 80 const parsed = JSON.parse(window.localStorage.getItem(DURABLE_STORE_KEY) || '[]'); 81 return Array.isArray(parsed) ? parsed : []; 82 } catch (_) { 83 return []; 84 } 85 } 86 87 function durableHas(key) { 88 const now = Date.now(); 89 return readDurableEntries().some(function (entry) { 90 return entry && entry.k === key && (now - Number(entry.t || 0)) < DURABLE_TTL_MS; 91 }); 92 } 93 94 function durableRemember(key) { 95 const now = Date.now(); 96 const kept = readDurableEntries().filter(function (entry) { 97 return entry && entry.k && entry.k !== key && (now - Number(entry.t || 0)) < DURABLE_TTL_MS; 98 }); 99 kept.push({ k: key, t: now }); 100 101 try { 102 window.localStorage.setItem(DURABLE_STORE_KEY, JSON.stringify(kept.slice(-DURABLE_MAX_ENTRIES))); 103 } catch (_) {} 104 } 105 106 function push(payload) { 107 if (!payload || typeof payload !== 'object' || !payload.event) return; 108 if (config.enabled === false && !config.debug) return; 109 110 if (payload.ecommerce && ecommerceEvents.has(payload.event)) { 111 window.dataLayer.push({ ecommerce: null }); 112 } 113 114 window.dataLayer.push(payload); 115 116 if (config.debug && window.console && typeof window.console.info === 'function') { 117 window.console.info('[Samurai GA4]', payload.event, payload); 118 } 119 } 120 121 function pushOnce(key, payload) { 122 if (!key) { 123 push(payload); 124 return; 125 } 126 127 if (hasLocalStorage && isDurableKey(key)) { 128 if (durableHas(key)) return; 129 push(payload); 130 durableRemember(key); 131 return; 132 } 133 134 const keyName = storageKey(key); 135 if (hasSessionStorage && window.sessionStorage.getItem(keyName) === '1') return; 136 137 push(payload); 138 139 if (hasSessionStorage) { 140 window.sessionStorage.setItem(keyName, '1'); 141 } 142 } 143 144 function cleanText(value, maxLength) { 145 const text = String(value || '').replace(/\s+/g, ' ').trim(); 146 return maxLength ? text.slice(0, maxLength) : text; 147 } 148 149 function cleanDimension(value) { 150 return cleanText(value, 100); 151 } 152 153 function parseMoney(value) { 154 if (typeof value === 'number') return Math.round(value * 100) / 100; 155 const normalized = String(value || '').replace(/[^\d.-]/g, ''); 156 const parsed = parseFloat(normalized); 157 return Number.isFinite(parsed) ? Math.round(parsed * 100) / 100 : 0; 158 } 159 160 function parseQty(value) { 161 const parsed = parseInt(String(value || ''), 10); 162 return Number.isFinite(parsed) && parsed > 0 ? parsed : 0; 163 } 164 165 function clonePlain(value) { 166 if (!value || typeof value !== 'object') return {}; 167 try { 168 return JSON.parse(JSON.stringify(value)); 169 } catch (_) { 170 return {}; 171 } 172 } 173 174 function ecommerceFromConfig(extra) { 175 const ecommerce = clonePlain(config.ecommerce); 176 if (!ecommerce.currency) ecommerce.currency = config.currency || 'JPY'; 177 if (!Array.isArray(ecommerce.items)) ecommerce.items = []; 178 return Object.assign(ecommerce, extra || {}); 179 } 180 181 function ecommerceForItem(item, quantity) {
182 const qty = Math.max(1, parseQty(quantity || item.quantity || 1)); 183 const price = parseMoney(item.price); 184 return { 185 currency: config.currency || 'JPY', 186 value: price > 0 ? Math.round(price * qty * 100) / 100 : undefined, 187 items: [Object.assign({}, item, { quantity: qty })] 188 }; 189 } 190 191 function removeEmptyValues(value) { 192 Object.keys(value).forEach((key) => { 193 if (value[key] === '' || value[key] === null || typeof value[key] === 'undefined') { 194 delete value[key]; 195 } 196 }); 197 return value; 198 } 199 200 function cssEscape(value) { 201 if (window.CSS && typeof window.CSS.escape === 'function') { 202 return window.CSS.escape(String(value)); 203 } 204 205 return String(value).replace(/["\\]/g, '\\$&'); 206 } 207 208 function qoNumericId(input, row) { 209 const variation = parseInt(input?.dataset.variationId || '', 10); 210 if (Number.isFinite(variation) && variation > 0) return String(variation); 211 const product = parseInt(input?.dataset.productId || '', 10); 212 if (Number.isFinite(product) && product > 0) return String(product); 213 return String(input?.dataset.variantId || row?.dataset.productId || ''); 214 } 215 216 function productFromQuickOrderRow(target, quantity) { 217 const row = target && target.closest ? target.closest('tr[data-product-id], tr') : null; 218 if (!row) return {}; 219 220 const title = row.querySelector('.sst-qo__pname, [data-product-title], .sst-qo__title, .sst-qo__prod'); 221 const input = target.matches && target.matches('input[data-variant-id]') 222 ? target 223 : (target.closest && target.closest('.sst-qo__offer-cell')?.querySelector('input[data-variant-id]')) 224 || row.querySelector('input[data-variant-id]'); 225 const activeOffer = target.closest ? target.closest('.sst-qo__offer-cell') : null; 226 const offerLabel = activeOffer ? cleanText(activeOffer.querySelector('.sst-qo__offer-label')?.textContent) : cleanText(input?.dataset.offer || ''); 227 const group = row.closest('.sst-qo__group'); 228 const listName = group?.dataset.group ? 'Quick Order - ' + cleanDimension(group.dataset.group) : 'Quick Order'; 229 const item = { 230 item_id: qoNumericId(input, row), 231 item_sku: cleanDimension(input?.dataset.sku || ''), 232 item_name: cleanText(input?.dataset.name || row.dataset.pname || title?.textContent || '', 100), 233 item_variant: cleanDimension(offerLabel || input?.dataset.offer || ''), 234 item_category: cleanDimension(row.dataset.group || group?.dataset.group || ''), 235 item_list_name: listName, 236 price: parseMoney(input?.dataset.price || row.dataset.price || 0) 237 }; 238 239 if (quantity) item.quantity = Math.max(1, parseQty(quantity)); 240 return removeEmptyValues(item); 241 } 242 243 function productFromElement(target, quantity) { 244 if (!target || !target.closest) return {}; 245 if (target.closest('.sst-qo')) return productFromQuickOrderRow(target, quantity); 246 247 const root = target.closest('form.cart, li.product, .product, tr.cart_item, .cart_item') || target; 248 249 const baked = root.querySelector ? root.querySelector('.sst-ga4-item-data[data-ga4-item]') : null; 250 if (baked) { 251 let item = {}; 252 try { 253 item = JSON.parse(baked.dataset.ga4Item || '{}') || {}; 254 } catch (_) { 255 item = {}; 256 } 257 258 const variationInput = root.querySelector('input[name="variation_id"]'); 259 const variationId = parseInt(variationInput?.value || '', 10); 260 if (Number.isFinite(variationId) && variationId > 0) { 261 item.item_id = String(variationId); 262 const variationPrice = variationPrices.get(String(variationId)); 263 if (typeof variationPrice === 'number') item.price = variationPrice; 264 item.item_variant = cleanDimension(root.querySelector('[name^="attribute_"] option:checked')?.textContent || item.item_variant || ''); 265 } 266 267 const bakedQty = root.querySelector('input.qty, input[name="quantity"]'); 268 item.item_name = cleanText(item.item_name || '', 100); 269 item.price = parseMoney(item.price); 270 item.quantity = Math.max(1, parseQty(quantity || bakedQty?.value || 1)); 271 return removeEmptyValues(item); 272 } 273
274 const qtyInput = root.querySelector('input.qty, input[name="quantity"]'); 275 const name = root.querySelector('.product_title, .woocommerce-loop-product__title, .product-name a, .product-name, .cart_item .product-name'); 276 const price = root.querySelector('.price .amount, .woocommerce-Price-amount, .product-price .amount'); 277 const variation = root.querySelector('input[name="variation_id"]'); 278 const product = root.querySelector('input[name="product_id"], button[name="add-to-cart"], input[name="add-to-cart"]'); 279 const item = { 280 item_id: target.dataset.product_id || target.dataset.productId || variation?.value || product?.value || target.value || '', 281 item_name: cleanText(target.dataset.product_name || target.dataset.productName || name?.textContent || target.getAttribute('aria-label') || '', 100), 282 item_variant: cleanDimension(root.querySelector('[name^="attribute_"] option:checked')?.textContent || ''), 283 price: parseMoney(target.dataset.price || price?.textContent || 0), 284 quantity: Math.max(1, parseQty(quantity || qtyInput?.value || target.dataset.quantity || 1)) 285 }; 286 287 return removeEmptyValues(item); 288 } 289 290 function pushItemEvent(eventName, source, item, quantity) { 291 if (!item || (!item.item_id && !item.item_name)) return; 292 293 const payload = { 294 event: eventName, 295 source: source, 296 ecommerce: ecommerceForItem(item, quantity) 297 }; 298 299 if (!payload.ecommerce.value) delete payload.ecommerce.value; 300 push(payload); 301 } 302 303 function hasItemIdentity(item) { 304 return Boolean(item && (item.item_id || item.item_name)); 305 } 306 307 // ----- Quick Order cart tracking ----- 308 // quick-order.js dispatches CustomEvents from its single staging mutation 309 // point (setQuickOrderCartQuantity) and its server-snapshot restore path 310 // (applyQoCartSnapshot). Deltas are computed here against the last reported 311 // quantity per variant, so programmatic restores never emit cart events. 312 313 function qoItemFromCartDetail(detail) { 314 const variation = parseInt(detail.variationId, 10); 315 const product = parseInt(detail.productId, 10); 316 const item = { 317 item_id: Number.isFinite(variation) && variation > 0 318 ? String(variation) 319 : (Number.isFinite(product) && product > 0 ? String(product) : String(detail.vid || '')), 320 item_sku: cleanDimension(detail.sku || ''), 321 item_name: cleanText(detail.name || '', 100), 322 item_list_name: 'Quick Order', 323 price: parseMoney(detail.price) 324 }; 325 326 return removeEmptyValues(item); 327 } 328 329 function flushQoCartDelta(vid) { 330 qoCartTimers.delete(vid); 331 const pending = qoCartPending.get(vid); 332 qoCartPending.delete(vid); 333 if (!pending) return; 334 335 const previous = qoCartReported.has(vid) ? qoCartReported.get(vid) : 0; 336 const delta = pending.quantity - previous; 337 qoCartReported.set(vid, pending.quantity); 338 if (delta === 0) return; 339 340 const eventName = delta > 0 ? 'add_to_cart' : 'remove_from_cart'; 341 pushItemEvent(eventName, 'quick_order', qoItemFromCartDetail(pending), Math.abs(delta)); 342 } 343 344 function onQoCartQty(event) { 345 const detail = event.detail || {}; 346 const vid = String(detail.vid || ''); 347 if (!vid) return; 348 349 const quantity = Math.max(0, parseInt(detail.quantity, 10) || 0); 350 351 if (detail.silent) { 352 window.clearTimeout(qoCartTimers.get(vid)); 353 qoCartTimers.delete(vid); 354 qoCartPending.delete(vid); 355 qoCartReported.set(vid, quantity); 356 return; 357 } 358 359 qoCartPending.set(vid, Object.assign({}, detail, { quantity: quantity })); 360 window.clearTimeout(qoCartTimers.get(vid)); 361 qoCartTimers.set(vid, window.setTimeout(function () { 362 flushQoCartDelta(vid); 363 }, 900)); 364 } 365 366 function onQoCartBaseline(event) { 367 const items = (event.detail && event.detail.items) || []; 368 const seen = new Set(); 369 370 items.forEach(function (entry) { 371 const vid = String(entry.vid || ''); 372 if (!vid) return; 373 seen.add(vid); 374 if (qoCartTimers.has(vid)) return; 375 qoCartReported.set(vid, Math.max(0, parseInt(entry.quantity, 10) || 0)); 376 }); 377 378 qoCartReported.forEach(function (_qty, vid) { 379 if (!seen.has(vid) && !qoCartTimers.has(vid)) qoCartReported.set(vid, 0); 380 }); 381 } 382 383 function onQoViewItem(event) {
384 const row = event.detail && event.detail.row; 385 if (!row || !row.querySelector) return; 386 387 const item = productFromQuickOrderRow(row, 1); 388 if (!hasItemIdentity(item)) return; 389 390 pushOnce('view_item:qo:' + (item.item_id || item.item_name), { 391 event: 'view_item', 392 source: 'quick_order', 393 ecommerce: ecommerceForItem(item, 1) 394 }); 395 } 396 397 function qoListHash(ids) { 398 const text = ids.join('|'); 399 let hash = 5381; 400 for (let i = 0; i < text.length; i += 1) { 401 hash = ((hash << 5) + hash + text.charCodeAt(i)) >>> 0; 402 } 403 return hash.toString(36); 404 } 405 406 function pushQuickOrderGroupList(group) { 407 if (!group || !group.querySelectorAll) return; 408 409 const items = []; 410 Array.from(group.querySelectorAll('tbody tr')).slice(0, 25).forEach(function (row, index) { 411 const item = productFromQuickOrderRow(row, 1); 412 if (!hasItemIdentity(item)) return; 413 item.index = index; 414 items.push(item); 415 }); 416 if (!items.length) return; 417 418 const groupKey = cleanDimension(group.dataset.group || 'quick_order'); 419 pushOnce('view_item_list:qo:' + groupKey + ':' + qoListHash(items.map(function (item) { return item.item_id || ''; })), { 420 event: 'view_item_list', 421 source: 'quick_order', 422 ecommerce: { 423 item_list_id: 'quick_order:' + groupKey, 424 item_list_name: 'Quick Order - ' + groupKey, 425 items: items 426 } 427 }); 428 } 429 430 function pushQuickOrderLists() { 431 document.querySelectorAll('.sst-qo__group').forEach(pushQuickOrderGroupList); 432 } 433 434 function onQoRowsRendered(event) { 435 const group = event.detail && event.detail.group; 436 if (group) pushQuickOrderGroupList(group); 437 } 438 439 function pushQuickOrderView() { 440 const root = document.querySelector('.sst-qo'); 441 if (!root) return; 442 443 push({ 444 event: 'quick_order_view', 445 source: 'quick_order' 446 }); 447 } 448 449 function selectedShippingTier() { 450 const checked = document.querySelector('input[name^="shipping_method"]:checked'); 451 const hidden = document.querySelector('input[name^="shipping_method"][type="hidden"]'); 452 const input = checked || hidden; 453 if (!input) return ''; 454 455 const label = input.id ? document.querySelector('label[for="' + cssEscape(input.id) + '"]') : null; 456 return cleanDimension(label?.textContent || input.value || ''); 457 } 458 459 function selectedPaymentType() { 460 const checked = document.querySelector('input[name="payment_method"]:checked'); 461 if (!checked) return ''; 462 463 const label = checked.id ? document.querySelector('label[for="' + cssEscape(checked.id) + '"]') : null; 464 return cleanDimension(label?.textContent || checked.value || ''); 465 } 466 467 function fieldValue(id) { 468 const field = document.getElementById(id); 469 if (!field || field.disabled) return ''; 470 return cleanText(field.value); 471 } 472 473 function requiredFieldComplete(id) { 474 const field = document.getElementById(id); 475 if (!field || field.disabled) return true; 476 477 const wrapper = field.closest('.form-row, p, label, div'); 478 if (wrapper && wrapper.classList.contains('woocommerce-invalid')) return false; 479 if (wrapper && wrapper.classList.contains('validate-required')) return fieldValue(id) !== ''; 480 if (field.hasAttribute('required') || field.getAttribute('aria-required') === 'true') return fieldValue(id) !== ''; 481 482 return true; 483 } 484 485 function checkoutAddressComplete(prefix) { 486 const fields = [prefix + '_first_name', prefix + '_last_name', prefix + '_country', prefix + '_address_1', prefix + '_city']; 487 return fields.every(requiredFieldComplete); 488 } 489 490 function checkoutContactComplete() { 491 return requiredFieldComplete('billing_email') && requiredFieldComplete('billing_phone'); 492 } 493 494 function checkoutShippingInfoComplete() { 495 const hasCheckout = document.querySelector('form.checkout, form.woocommerce-checkout'); 496 if (!hasCheckout) return false; 497 498 const billingComplete = checkoutAddressComplete('billing') && checkoutContactComplete(); 499 const shipDifferent = document.getElementById('ship-to-different-address-checkbox'); 500 const shippingComplete = shipDifferent && shipDifferent.checked ? checkoutAddressComplete('shipping') : true; 501 502 return billingComplete && shippingComplete; 503 } 504 505 function maybePushShippingInfo() { 506 if (!checkoutShippingInfoComplete()) return; 507 508 const shippingTier = selectedShippingTier(); 509 const ecommerce = ecommerceFromConfig(shippingTier ? { shipping_tier: shippingTier } : {});
510 const key = 'add_shipping_info:' + (config.cart_hash || window.location.pathname); 511 512 pushOnce(key, { 513 event: 'add_shipping_info', 514 source: 'checkout', 515 checkout_step: 'shipping_info', 516 shipping_tier: shippingTier, 517 ecommerce: ecommerce 518 }); 519 } 520 521 function maybePushPaymentInfo() { 522 const paymentType = selectedPaymentType(); 523 if (!paymentType) return; 524 525 const ecommerce = ecommerceFromConfig({ payment_type: paymentType }); 526 const key = 'add_payment_info:' + (config.cart_hash || window.location.pathname); 527 528 pushOnce(key, { 529 event: 'add_payment_info', 530 source: 'checkout', 531 checkout_step: 'payment_info', 532 payment_type: paymentType, 533 ecommerce: ecommerce 534 }); 535 } 536 537 function pushPaymentButtonClick(target) { 538 const paymentType = selectedPaymentType(); 539 const buttonId = target?.id || target?.name || ''; 540 maybePushShippingInfo(); 541 maybePushPaymentInfo(); 542 543 window.setTimeout(function () { 544 push({ 545 event: 'payment_button_click', 546 source: 'checkout', 547 checkout_step: 'place_order', 548 payment_type: paymentType, 549 button_id: buttonId, 550 ecommerce: ecommerceFromConfig(paymentType ? { payment_type: paymentType } : {}) 551 }); 552 }, 250); 553 } 554 555 function checkoutErrorType() { 556 const errorText = cleanText(Array.from(document.querySelectorAll('.woocommerce-error, .wc-block-components-notice-banner.is-error')) 557 .map((node) => node.textContent) 558 .join(' ')) 559 .toLowerCase(); 560 561 if (/payment|card|wise|paypal|stripe|æ¯æ|決æ¸/.test(errorText)) return 'payment_failed'; 562 if (/required|invalid|valid|missing|å¿ é |å ¥å|ç¡å¹/.test(errorText)) return 'validation_error'; 563 if (/stock|å¨åº«/.test(errorText)) return 'stock_error'; 564 565 return 'checkout_error'; 566 } 567 568 function pushCheckoutError() { 569 const now = Date.now(); 570 if (now - checkoutErrorSentAt < 1500) return; 571 checkoutErrorSentAt = now; 572 573 const errors = document.querySelectorAll('.woocommerce-error li, .woocommerce-error, .wc-block-components-notice-banner.is-error'); 574 push({ 575 event: 'checkout_error', 576 source: 'checkout', 577 checkout_step: 'place_order', 578 error_type: checkoutErrorType(), 579 error_count: Math.max(1, errors.length), 580 ecommerce: ecommerceFromConfig() 581 }); 582 } 583 584 function onDocumentClick(event) { 585 const target = event.target; 586 if (!target || !target.closest) return; 587 588 const qoProductLink = target.closest('.sst-qo__group tbody tr a.sst-qo__prod'); 589 if (qoProductLink) { 590 const item = productFromQuickOrderRow(qoProductLink, 1); 591 if (hasItemIdentity(item)) { 592 push({ 593 event: 'select_item', 594 source: 'quick_order', 595 ecommerce: { 596 currency: config.currency || 'JPY', 597 item_list_id: 'quick_order', 598 item_list_name: item.item_list_name || 'Quick Order', 599 items: [item] 600 } 601 }); 602 } 603 return; 604 } 605 606 const qoCheckout = target.closest('.sst-qo [data-action="checkout"]'); 607 if (qoCheckout) { 608 push({ 609 event: 'checkout_button_click', 610 source: 'quick_order', 611 button_id: qoCheckout.dataset.action || 'checkout', 612 ecommerce: ecommerceFromConfig() 613 }); 614 return; 615 } 616 617 const paymentButton = target.closest('#place_order, button[name="woocommerce_checkout_place_order"], .wc-block-components-checkout-place-order-button, .wc-block-components-express-payment__button'); 618 if (paymentButton) { 619 pushPaymentButtonClick(paymentButton); 620 return; 621 } 622 623 const addButton = target.closest('.single_add_to_cart_button, .add_to_cart_button, button[name="add-to-cart"]'); 624 if (addButton && !addButton.disabled && !addButton.closest('.sst-qo')) { 625 const item = productFromElement(addButton); 626 lastStandardAddIntent = item; 627 if (!addButton.classList.contains('ajax_add_to_cart')) { 628 pushItemEvent('add_to_cart', 'woocommerce', item, item.quantity || 1); 629 } 630 return; 631 } 632 633 const removeButton = target.closest('.remove_from_cart_button, a.remove'); 634 if (removeButton && !removeButton.closest('.sst-qo')) { 635 const item = productFromElement(removeButton); 636 lastRemoveIntent = item; 637 if (!removeButton.classList.contains('remove_from_cart_button')) { 638 pushItemEvent('remove_from_cart', 'woocommerce', item, item.quantity || 1); 639 } 640 } 641 } 642 643 function bindWooCommerceEvents() { 644 if (!window.jQuery) return; 645 646 window.jQuery(document.body).on('added_to_cart', function (_event, _fragments, _cartHash, button) { 647 const el = button && button.length ? button[0] : null; 648 const item = productFromElement(el); 649 const trackedItem = hasItemIdentity(item) ? item : lastStandardAddIntent; 650 pushItemEvent('add_to_cart', 'woocommerce', trackedItem, trackedItem?.quantity || 1); 651 }); 652 653 window.jQuery(document.body).on('removed_from_cart', function (_event, _fragments, _cartHash, button) { 654 const el = button && button.length ? button[0] : null; 655 const item = productFromElement(el); 656 const trackedItem = hasItemIdentity(item) ? item : lastRemoveIntent; 657 pushItemEvent('remove_from_cart', 'woocommerce', trackedItem, trackedItem?.quantity || 1); 658 }); 659 660 window.jQuery(document.body).on('updated_checkout', function () { 661 window.setTimeout(maybePushShippingInfo, 250); 662 }); 663 664 window.jQuery(document.body).on('found_variation', 'form.variations_form', function (_event, variation) { 665 if (!variation || !variation.variation_id) return; 666 if (typeof variation.display_price === 'number') { 667 variationPrices.set(String(variation.variation_id), Math.round(variation.display_price * 100) / 100); 668 } 669 }); 670 671 window.jQuery(document.body).on('checkout_error', function () { 672 window.setTimeout(pushCheckoutError, 100); 673 }); 674 } 675 676 function bindCheckoutEvents() { 677 const checkoutForm = document.querySelector('form.checkout, form.woocommerce-checkout'); 678 if (!checkoutForm) return; 679
680 let checkoutTimer = 0; 681 const schedule = function () { 682 window.clearTimeout(checkoutTimer); 683 checkoutTimer = window.setTimeout(maybePushShippingInfo, 350); 684 }; 685 686 checkoutForm.addEventListener('change', schedule, true); 687 checkoutForm.addEventListener('blur', schedule, true); 688 schedule(); 689 690 const observer = new MutationObserver(function () { 691 if (document.querySelector('.woocommerce-error, .wc-block-components-notice-banner.is-error')) { 692 pushCheckoutError(); 693 } 694 }); 695 696 observer.observe(document.body, { childList: true, subtree: true }); 697 } 698 699 function onReady(callback) { 700 if (document.readyState === 'loading') { 701 document.addEventListener('DOMContentLoaded', callback, { once: true }); 702 return; 703 } 704 705 callback(); 706 } 707 708 window.SamuraiGA4 = { 709 push: push, 710 pushOnce: pushOnce 711 }; 712 713 document.addEventListener('click', onDocumentClick, true); 714 document.addEventListener('sst:qo:cart-qty', onQoCartQty); 715 document.addEventListener('sst:qo:cart-baseline', onQoCartBaseline); 716 document.addEventListener('sst:qo:view-item', onQoViewItem); 717 document.addEventListener('sst:qo:rows-rendered', onQoRowsRendered); 718 719 onReady(function () { 720 bindWooCommerceEvents(); 721 bindCheckoutEvents(); 722 pushQuickOrderLists(); 723 window.setTimeout(pushQuickOrderView, 1200); 724 725 try { 726 const position = new URLSearchParams(window.location.search).get('sst_pos'); 727 if (position) { 728 push({ event: 'internal_nav', source: 'internal', position: cleanDimension(position) }); 729 } 730 } catch (_) {} 731 }); 732})();
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.