PageSourceSearch

https://www.petdrugsonline.co.uk/scripts/delayed.js

js petdrugsonline.co.uk collected 2026-10-04 00:05:58 UTC 12,860 bytes, 377 lines download raw bytes

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.