1/* eslint-disable import/no-cycle */ 2import { getConfigValue } from './configs.js'; 3import { getUserTokenCookie } from './initializers/index.js'; 4import { getConsent } from './scripts.js'; 5import EmarsysTracker, { whenEmarsysReady } from './helpers/emarsysTracker.js'; 6import { getSignInToken, performMonolithGraphQLQuery } from "./commerce.js"; 7import initPuzzelChat from './widgets/puzzlechat.js'; 8import decorate from "../../blocks/product-recommendations/product-recommendations.js"; /* Delayed loading of the product-recommendations.js script for the Quick View functionality on the PLP/SRP */ 9 10function isIOS() { 11 return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 12} 13 14function isTouchDevice() { 15 return (('ontouchstart' in window) || 16 (navigator.maxTouchPoints > 0) || 17 (navigator.msMaxTouchPoints > 0)); 18} 19 20function preventIOSZoom() { 21 if (isIOS()) { 22 const inputFields = document.querySelectorAll('input'); 23 24 // Set the font size to 16px for inputs on IOS. 25 for (var i = 0; i < inputFields.length; i++) { 26 inputFields[i].style.fontSize = '16px'; 27 } 28 } 29} 30 31async function initAnalytics() { 32 // Load Commerce events SDK and collector 33 if (getConsent('commerce-collection')) { 34 const config = { 35 environmentId: await getConfigValue('commerce.headers.cs.Magento-Environment-Id'), 36 environment: await getConfigValue('commerce-environment'), 37 storeUrl: await getConfigValue('commerce-store-url'), 38 websiteId: parseInt(await getConfigValue('commerce-website-id'), 10), 39 websiteCode: await getConfigValue('commerce.headers.cs.Magento-Website-Code'), 40 storeId: parseInt(await getConfigValue('commerce-store-id'), 10), 41 storeCode: await getConfigValue('commerce.headers.cs.Magento-Store-Code'), 42 storeViewId: parseInt(await getConfigValue('commerce-store-view-id'), 10), 43 storeViewCode: await getConfigValue('commerce.headers.cs.Magento-Store-View-Code'), 44 websiteName: await getConfigValue('commerce-website-name'), 45 storeName: await getConfigValue('commerce-store-name'), 46 storeViewName: await getConfigValue('commerce-store-view-name'), 47 baseCurrencyCode: await getConfigValue('commerce-base-currency-code'), 48 storeViewCurrencyCode: await getConfigValue('commerce-base-currency-code'), 49 storefrontTemplate: 'EDS' 50 }; 51 52 window.adobeDataLayer.push( 53 { storefrontInstanceContext: config }, 54 { eventForwardingContext: { commerce: true, aep: false } }, 55 { 56 shopperContext: { 57 shopperId: getUserTokenCookie() ? 'logged-in' : 'guest', 58 }, 59 }, 60 ); 61 62 // Load events SDK and collector 63 await import('./commerce-events-sdk.js'); 64 await import('./commerce-events-collector.js'); 65 66 const { initSearchAndProductClickEvents } = await import('./event-triggers.js'); 67 initSearchAndProductClickEvents(); 68 } 69} 70 71if (document.prerendering) { 72 document.addEventListener('prerenderingchange', initAnalytics, { 73 once: true, 74 }); 75} else { 76 initAnalytics(); 77 // bindPLPEvents(); 78} 79 80preventIOSZoom(); 81 82async function initEmarsysTracker() { 83 try { 84 let trackerInstance = new EmarsysTracker(); 85 window.emarsysTracker = trackerInstance; 86 console.log("EmarsysTracker initialized and attached to window.emarsysTracker."); 87 } catch (error) { 88 console.error("Failed to initialize EmarsysTracker:", error); 89 } 90 91 whenEmarsysReady(async tracker => { 92 // Push category immediately â does not depend on auth state and needs to 93 // land in the same debounced go batch as cart and searchTerm. 94 if (Array.from(document.body.classList).some(className => className.includes('plp-widget'))) { 95 const breadcrumbOl = document.querySelector('nav.breadcrumb ol'); 96 let categoryPath = ''; 97 98 if (breadcrumbOl) { 99 const items = breadcrumbOl.querySelectorAll('li'); 100 const pathSegments = []; 101 102 // Start from the second item (index 1) to skip "Home" 103 for (let i = 1; i < items.length; i++) { 104 const item = items[i]; 105 // Find the main text element, which is either an <a> tag or a <span> with aria-current="page" 106 const linkElement = item.querySelector('a'); 107 const spanElement = item.querySelector('span[aria-current="page"]'); 108 109 if (linkElement) { 110 pathSegments.push(linkElement.textContent.trim()); 111 }
111 else if (spanElement) { 112 pathSegments.push(spanElement.textContent.trim()); 113 } 114 } 115 116 categoryPath = pathSegments.join(' > '); 117 console.log("Constructed Category Path:", categoryPath); 118 119 if (categoryPath) { 120 tracker.pushCommand('category', categoryPath); 121 } else { 122 console.log('No breadcrumb segments found after "Home".'); 123 } 124 } else { 125 console.log('Breadcrumb element (nav.breadcrumb ol) not found.'); 126 } 127 } 128 129 // Check if the user is logged in and only then set the customer ID 130 const userToken = await getSignInToken(); 131 132 if (userToken) { 133 let customerId = await getKaosCustomerUrn(); 134 135 if (customerId) { 136 tracker.setCustomerId(customerId); 137 console.log(`Emarsys Customer ID set via whenEmarsysReady for user ${customerId}`); 138 } else { 139 console.log('EmarsysTracker: setCustomerId called with empty customerId.'); 140 } 141 } 142 }); 143 144} 145initEmarsysTracker(); 146 147async function getKaosCustomerUrn() { 148 const query = ` 149 query GetCustomerUrn { 150 customer { 151 khaos_customer_urn 152 } 153 } 154 `; 155 156 try { 157 const data = await performMonolithGraphQLQuery( 158 query, 159 [], 160 true, 161 true 162 ); 163 164 if (data.errors) { 165 console.error('GraphQL Errors:', data.errors); 166 return null; 167 } 168 console.log(data); 169 const customerUrn = data.data.customer.khaos_customer_urn; 170 return customerUrn; 171 } catch (error) { 172 console.error('Error fetching GraphQL data:', error); 173 return null; 174 } 175} 176 177function closeAllTooltips() { 178 const existingTooltips = document.querySelectorAll('.mrrp-global-tooltip'); 179 existingTooltips.forEach(tooltip => { 180 if (tooltip) { 181 setTimeout(() => { 182 tooltip.classList.remove('active'); 183 tooltip.remove(); 184 }, 100); 185 } 186 }); 187} 188 189function showTooltip(triggerIcon) { 190 closeAllTooltips(); 191 192 const ARROW_HEIGHT = 8; 193 const GAP_BETWEEN_ICON_AND_ARROW = 13; 194 195 const originalTooltipContent = triggerIcon.nextElementSibling || triggerIcon.querySelector('.pdp-vet-info-content') || triggerIcon.querySelector('.price-vet-info');; 196 if (!originalTooltipContent || (!originalTooltipContent.classList.contains('tooltip') && !originalTooltipContent.classList.contains('pdp-vet-info-content') && !originalTooltipContent.classList.contains('price-vet-info'))) { 197 console.warn('Tooltip content not found for:', triggerIcon); 198 return; 199 } 200 201 const globalTooltip = document.createElement('div'); 202 globalTooltip.className = 'mrrp-global-tooltip'; 203 204 if (originalTooltipContent.querySelector('.tooltip-text')) { 205 globalTooltip.innerHTML = '<span class="tooltip-text">' + originalTooltipContent.innerHTML + '</span>'; 206 } else { 207 globalTooltip.innerHTML = '<span class="tooltip-text">' + originalTooltipContent.innerHTML + '</span>'; 208 } 209 210 if (!triggerIcon.id) { 211 triggerIcon.id = `mrrp-icon-${Math.random().toString(36).substr(2, 9)}`; 212 } 213 globalTooltip.dataset.triggerId = triggerIcon.id; 214 215 document.body.appendChild(globalTooltip); 216 217 const triggerRect = triggerIcon.getBoundingClientRect(); 218 const tooltipRect = globalTooltip.getBoundingClientRect(); 219 220 const totalOffset = tooltipRect.height + ARROW_HEIGHT + GAP_BETWEEN_ICON_AND_ARROW; 221 let top = triggerRect.top + window.scrollY - totalOffset + triggerRect.height; 222 let left = triggerRect.left + window.scrollX + (triggerRect.width / 2) - (tooltipRect.width / 2); 223 224 if (top < window.scrollY + 10) { 225 top = triggerRect.bottom + window.scrollY + ARROW_HEIGHT + GAP_BETWEEN_ICON_AND_ARROW; 226 globalTooltip.classList.add('is-below'); 227 } 228 229 if (left < 10) { left = 10; } 230 const maxLeft = document.documentElement.clientWidth - tooltipRect.width - 10; 231 if (left > maxLeft) { left = maxLeft; } 232 233 const iconCenter = triggerRect.left + window.scrollX + (triggerRect.width / 2); 234 const arrowLeftPosition = iconCenter - left; 235 globalTooltip.style.setProperty('--arrow-left-position', `${arrowLeftPosition}px`); 236 237 globalTooltip.style.top = `${top}px`; 238 globalTooltip.style.left = `${left}px`; 239 240 setTimeout(() => { 241 globalTooltip.classList.add('active'); 242 }, 10); 243} 244 245function initTooltipListeners() { 246 let hoverTimeout; 247 const canHover = window.matchMedia('(hover: hover)'); 248 249 document.body.addEventListener('mouseover', (e) => { 250 if (!canHover.matches) return; 251 const triggerIcon = e.target.closest('.mrrp-icon') || e.target.closest('.pdp-vet-info-container') || e.target.closest('.price-vet-info-container'); 252 if (!triggerIcon) return; 253 254 hoverTimeout = setTimeout(() => { 255 showTooltip(triggerIcon); 256 }, 300); 257 }); 258 259 document.body.addEventListener('mouseout', (e) => { 260 if (!canHover.matches) return; 261 const triggerIcon = e.target.closest('.mrrp-icon') || e.target.closest('.pdp-vet-info-container') || e.target.closest('.price-vet-info-container'); 262 if (triggerIcon) { 263 e.preventDefault(); 264 clearTimeout(hoverTimeout); 265 } 266 if (!e.target.closest('.mrrp-icon') && !e.target.closest('.mrrp-global-tooltip') && !e.target.classList.contains('pdp-vet-info-tooltip') && !e.target.classList.contains('tooltip-container') && !e.target.classList.contains('price-tooltip')) { 267 closeAllTooltips(); 268 } 269 }); 270 271 document.body.addEventListener('touchstart', (e) => { 272 const triggerIcon = e.target.closest('.mrrp-icon'); 273 if (triggerIcon) { 274 e.preventDefault(); 275 clearTimeout(hoverTimeout); 276 } 277 if (!e.target.closest('.mrrp-icon') && !e.target.closest('.mrrp-global-tooltip') && !e.target.classList.contains('pdp-vet-info-tooltip') && !e.target.classList.contains('tooltip-container') && !e.target.classList.contains('price-tooltip')) { 278 closeAllTooltips(); 279 } 280 }); 281 282 document.body.addEventListener('click', (e) => { 283 const triggerIcon = e.target.closest('.mrrp-icon') || e.target.closest('.pdp-vet-info-container') || e.target.closest('.price-vet-info-container'); 284 const openTooltip = document.querySelector('.mrrp-global-tooltip'); 285 286 if (triggerIcon) { 287 e.preventDefault(); 288 e.stopPropagation(); 289 clearTimeout(hoverTimeout); 290 291 if (openTooltip) { 292 if (openTooltip.dataset.triggerId === triggerIcon.id) { 293 closeAllTooltips(); 294 } else { 295 closeAllTooltips(); 296 showTooltip(triggerIcon); 297 } 298 } else { 299 showTooltip(triggerIcon); 300 } 301 return; 302 } 303 304 if (!e.target.closest('.mrrp-global-tooltip')) { 305 closeAllTooltips(); 306 } 307 }); 308 309 document.addEventListener('scroll', closeAllTooltips, true); 310 window.addEventListener('resize', closeAllTooltips); 311} 312 313 314initTooltipListeners(); 315 316// Initialize Puzzel Chat - with priority for active sessions 317let puzzelInitialized = false;
318 319async function initPuzzelChatIfNeeded() { 320 if (puzzelInitialized) return; 321 322 puzzelInitialized = true; 323 324 try { 325 const configId = await getConfigValue('puzzelchat-config-id'); 326 if (configId) { 327 initPuzzelChat(configId); 328 } else { 329 console.warn('Puzzel Chat config ID not found.'); 330 puzzelInitialized = false; 331 } 332 } catch (error) { 333 console.error('Error initializing Puzzel Chat:', error); 334 puzzelInitialized = false; 335 } 336} 337 338// Check if there are any chat links on the page 339const chatLinks = document.querySelectorAll('a[href$="#puzzelchat"]'); 340const hasChatLinks = chatLinks.length > 0; 341 342// Initialize Puzzel Chat immediately on page load if: 343// 1. There are chat links on the page (ready to use) 344// 2. This ensures active chats are restored quickly 345if (hasChatLinks && !document.prerendering) { 346 initPuzzelChatIfNeeded(); 347} 348 349// Listen for chat link clicks to ensure initialization 350document.addEventListener('click', async (event) => { 351 const link = event.target.closest('a[href$="#puzzelchat"]'); 352 if (!link) return; 353 354 event.preventDefault(); 355 356 // Initialize Puzzel if not already initialized 357 if (!puzzelInitialized) { 358 await initPuzzelChatIfNeeded(); 359 } 360}); 361 362// Fallback: Initialize Puzzel when any chat link enters the viewport 363// (in case chat links are not present on initial load, added dynamically later) 364if (hasChatLinks) { 365 const chatLinkObserver = new IntersectionObserver((entries) => { 366 entries.forEach((entry) => { 367 if (entry.isIntersecting && !puzzelInitialized) { 368 initPuzzelChatIfNeeded(); 369 } 370 }); 371 }, { 372 root: null, 373 threshold: 0.1, 374 }); 375 376 chatLinks.forEach(link => chatLinkObserver.observe(link)); 377}
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.