PageSourceSearch

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

js petdrugsonline.co.uk collected 2026-10-04 00:05:28 UTC 30,103 bytes, 915 lines download raw bytes

1/*
2 * Copyright 2024 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13/* eslint-env browser */
14let inlineStylesProcessorPromise;
15function loadInlineStylesProcessor() {
16  if (!inlineStylesProcessorPromise) {
17    inlineStylesProcessorPromise = import('./inline-styles-processor.js');
18  }
19  return inlineStylesProcessorPromise;
20}
21
22function sampleRUM(checkpoint, data) {
23  // eslint-disable-next-line max-len
24  const timeShift = () => (window.performance ? window.performance.now() : Date.now() - window.hlx.rum.firstReadTime);
25  try {
26    window.hlx = window.hlx || {};
27    sampleRUM.enhance = () => { };
28    if (!window.hlx.rum) {
29      const param = new URLSearchParams(window.location.search).get('rum');
30      const weight = (window.SAMPLE_PAGEVIEWS_AT_RATE === 'high' && 10)
31        || (window.SAMPLE_PAGEVIEWS_AT_RATE === 'low' && 1000)
32        || (param === 'on' && 1)
33        || 100;
34      const id = Math.random().toString(36).slice(-4);
35      const isSelected = param !== 'off' && Math.random() * weight < 1;
36      // eslint-disable-next-line object-curly-newline, max-len
37      window.hlx.rum = {
38        weight,
39        id,
40        isSelected,
41        firstReadTime: window.performance ? window.performance.timeOrigin : Date.now(),
42        sampleRUM,
43        queue: [],
44        collector: (...args) => window.hlx.rum.queue.push(args),
45      };
46      if (isSelected) {
47        const dataFromErrorObj = (error) => {
48          const errData = { source: 'undefined error' };
49          try {
50            errData.target = error.toString();
51            errData.source = error.stack
52              .split('\n')
53              .filter((line) => line.match(/https?:\/\//))
54              .shift()
55              .replace(/at ([^ ]+) \((.+)\)/, '$1@$2')
56              .replace(/ at /, '@')
57              .trim();
58          } catch (err) {
59            /* error structure was not as expected */
60          }
61          return errData;
62        };
63
64        window.addEventListener('error', ({ error }) => {
65          const errData = dataFromErrorObj(error);
66          sampleRUM('error', errData);
67        });
68
69        window.addEventListener('unhandledrejection', ({ reason }) => {
70          let errData = {
71            source: 'Unhandled Rejection',
72            target: reason || 'Unknown',
73          };
74          if (reason instanceof Error) {
75            errData = dataFromErrorObj(reason);
76          }
77          sampleRUM('error', errData);
78        });
79
80        sampleRUM.baseURL = sampleRUM.baseURL || new URL(window.RUM_BASE || '/', new URL('https://rum.hlx.page'));
81        sampleRUM.collectBaseURL = sampleRUM.collectBaseURL || sampleRUM.baseURL;
82        sampleRUM.sendPing = (ck, time, pingData = {}) => {
83          // eslint-disable-next-line max-len, object-curly-newline
84          const rumData = JSON.stringify({
85            weight,
86            id,
87            referer: window.location.href,
88            checkpoint: ck,
89            t: time,
90            ...pingData,
91          });
92          const urlParams = window.RUM_PARAMS
93            ? `?${new URLSearchParams(window.RUM_PARAMS).toString()}`
94            : '';
95          const { href: url, origin } = new URL(
96            `.rum/${weight}${urlParams}`,
97            sampleRUM.collectBaseURL,
98          );
99          const body = origin === window.location.origin
100            ? new Blob([rumData], { type: 'application/json' })
101            : rumData;
102          navigator.sendBeacon(url, body);
103          // eslint-disable-next-line no-console
104          console.debug(`ping:${ck}`, pingData);
105        };
106        sampleRUM.sendPing('top', timeShift());
107
108        sampleRUM.enhance = () => {
109          // only enhance once
110          if (document.querySelector('script[src*="rum-enhancer"]')) return;
111          const { enhancerVersion, enhancerHash } = sampleRUM.enhancerContext || {};
112          const script = document.createElement('script');
113          if (enhancerHash) {
114            script.integrity = enhancerHash;
115            script.setAttribute('crossorigin', 'anonymous');
116          }
117          script.src = new URL(
118            `.rum/@adobe/helix-rum-enhancer@${enhancerVersion || '^2'}/src/index.js`,
119            sampleRUM.baseURL,
120          ).href;
121          document.head.appendChild(script);
122        };
123        if (!window.hlx.RUM_MANUAL_ENHANCE) {
124          sampleRUM.enhance();
125        }
126      }
127    }
128    if (window.hlx.rum && window.hlx.rum.isSelected && checkpoint) {
129      window.hlx.rum.collector(checkpoint, data, timeShift());
130    }
131    document.dispatchEvent(new CustomEvent('rum', { detail: { checkpoint, data } }));
132  } catch (error) {
133    // something went awry
134  }
135}
136
137/**
138 * Setup block utils.
139 */
140function setup() {
141  window.hlx = window.hlx || {};
142  window.hlx.RUM_MASK_URL = 'full';
143  window.hlx.RUM_MANUAL_ENHANCE = true;
144  window.hlx.codeBasePath = '';
145  window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') 
145=== 'on';
146
147  const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]');
148  if (scriptEl) {
149    try {
150      const scriptURL = new URL(scriptEl.src, window.location);
151      if (scriptURL.host === window.location.host) {
152        [window.hlx.codeBasePath] = scriptURL.pathname.split('/scripts/scripts.js');
153      } else {
154        [window.hlx.codeBasePath] = scriptURL.href.split('/scripts/scripts.js');
155      }
156    } catch (error) {
157      // eslint-disable-next-line no-console
158      console.log(error);
159    }
160  }
161}
162
163/**
164 * Auto initializiation.
165 */
166
167function init() {
168  setup();
169  sampleRUM();
170}
171
172/**
173 * Sanitizes a string for use as class name.
174 * @param {string} name The unsanitized string
175 * @returns {string} The class name
176 */
177function toClassName(name) {
178  return typeof name === 'string'
179    ? name
180      .toLowerCase()
181      .replace(/[^0-9a-z]/gi, '-')
182      .replace(/-+/g, '-')
183      .replace(/^-|-$/g, '')
184    : '';
185}
186
187/**
188 * Sanitizes a string for use as a js property name.
189 * @param {string} name The unsanitized string
190 * @returns {string} The camelCased name
191 */
192function toCamelCase(name) {
193  return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
194}
195
196/**
197 * Extracts the config from a block.
198 * @param {Element} block The block element
199 * @returns {object} The block config
200 */
201// eslint-disable-next-line import/prefer-default-export
202function readBlockConfig(block) {
203  const config = {};
204  block.querySelectorAll(':scope > div').forEach((row) => {
205    if (row.children) {
206      const cols = [...row.children];
207      if (cols[1]) {
208        const col = cols[1];
209        const name = toClassName(cols[0].textContent);
210        let value = '';
211        if (col.querySelector('a')) {
212          const as = [...col.querySelectorAll('a')];
213          if (as.length === 1) {
214            value = as[0].href;
215          } else {
216            value = as.map((a) => a.href);
217          }
218        } else if (col.querySelector('img')) {
219          const imgs = [...col.querySelectorAll('img')];
220          if (imgs.length === 1) {
221            value = imgs[0].src;
222          } else {
223            value = imgs.map((img) => img.src);
224          }
225        } else if (col.querySelector('p')) {
226          const ps = [...col.querySelectorAll('p')];
227          if (ps.length === 1) {
228            value = ps[0].textContent;
229          } else {
230            value = ps.map((p) => p.textContent);
231          }
232        } else value = row.children[1].textContent;
233        config[name] = value;
234      }
235    }
236  });
237  return config;
238}
239
240/**
241 * Loads a CSS file.
242 * @param {string} href URL to the CSS file
243 */
244async function loadCSS(href) {
245  return new Promise((resolve, reject) => {
246    if (!document.querySelector(`head > link[href="${href}"]`)) {
247      const link = document.createElement('link');
248      link.rel = 'stylesheet';
249      link.href = href;
250      link.onload = resolve;
251      link.onerror = reject;
252      document.head.append(link);
253    } else {
254      resolve();
255    }
256  });
257}
258
259/**
260 * Loads a non module JS file.
261 * @param {string} src URL to the JS file
262 * @param {Object} attrs additional optional attributes
263 */
264async function loadScript(src, attrs) {
265  return new Promise((resolve, reject) => {
266    if (!document.querySelector(`head > script[src="${src}"]`)) {
267      const script = document.createElement('script');
268      script.src = src;
269      if (attrs) {
270        // eslint-disable-next-line no-restricted-syntax, guard-for-in
271        for (const attr in attrs) {
272          script.setAttribute(attr, attrs[attr]);
273        }
274      }
275      script.onload = resolve;
276      script.onerror = reject;
277      document.head.append(script);
278    } else {
279      resolve();
280    }
281  });
282}
283
284/**
285 * Retrieves the content of metadata tags.
286 * @param {string} name The metadata name (or property)
287 * @param {Document} doc Document object to query for metadata. Defaults to the window's document
288 * @returns {string} The metadata value(s)
289 */
290function getMetadata(name, doc = document) {
291  const attr = name && name.includes(':') ? 'property' : 'name';
292  const meta = [...doc.head.querySelectorAll(`meta[${attr}="${name}"]`)]
293    .map((m) => m.content)
294    .join(', ');
295  return meta || '';
296}
297
298/**
299 * Returns a picture element with webp and fallbacks
300 * @param {string} src The image URL
301 * @param {string} [alt] The image alternative text
302 * @param {boolean} [eager] Set loading attribute to eager
303 * @param {Array}
303 [breakpoints] Breakpoints and corresponding params (eg. width)
304 * @param {boolean} useFullUrl Use full url of media image instead of relative path. Usable for AC media integration
305 * @param {string} fileExtension Override file extension
306 *
307 * @returns {Element} The picture element
308 */
309function createOptimizedPicture(
310  src,
311  alt = '',
312  eager = false,
313  breakpoints = [{ media: '(min-width: 600px)', width: '2000' }, { width: '750' }],
314  useFullUrl = false,
315  fileExtension = ''
316) {
317  const url = new URL(src, window.location.href);
318  const picture = document.createElement('picture');
319  const pathname = useFullUrl ? url.href : url.pathname;
320  const ext = fileExtension ?? pathname.substring(pathname.lastIndexOf('.') + 1);
321
322  // webp
323  breakpoints.forEach((br) => {
324    const source = document.createElement('source');
325    if (br.media) source.setAttribute('media', br.media);
326    source.setAttribute('type', 'image/webp');
327    source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`);
328    picture.appendChild(source);
329  });
330
331  // fallback
332  breakpoints.forEach((br, i) => {
333    if (i < breakpoints.length - 1) {
334      const source = document.createElement('source');
335      if (br.media) source.setAttribute('media', br.media);
336      source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
337      picture.appendChild(source);
338    } else {
339      const img = document.createElement('img');
340      img.setAttribute('loading', eager ? 'eager' : 'lazy');
341      img.setAttribute('fetchpriority', eager ? 'high' : 'auto');
342      img.setAttribute('alt', alt);
343      picture.appendChild(img);
344      img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
345    }
346  });
347
348  return picture;
349}
350
351/**
352 * Set template (page structure) and theme (page styles).
353 */
354function decorateTemplateAndTheme() {
355  const addClasses = (element, classes) => {
356    classes.split(',').forEach((c) => {
357      element.classList.add(toClassName(c.trim()));
358    });
359  };
360  const template = getMetadata('template');
361  if (template) addClasses(document.body, template);
362  const theme = getMetadata('theme');
363  if (theme) addClasses(document.body, theme);
364}
365
366/**
367 * Wrap inline text content of block cells within a <p> tag.
368 * @param {Element} block the block element
369 */
370function wrapTextNodes(block) {
371  const validWrappers = [
372    'P',
373    'PRE',
374    'UL',
375    'OL',
376    'PICTURE',
377    'TABLE',
378    'H1',
379    'H2',
380    'H3',
381    'H4',
382    'H5',
383    'H6',
384  ];
385
386  const wrap = (el) => {
387    const wrapper = document.createElement('p');
388    wrapper.append(...el.childNodes);
389    [...el.attributes]
390      // move the instrumentation from the cell to the new paragraph, also keep the class
391      // in case the content is a buttton and the cell the button-container
392      .filter(({ nodeName }) => nodeName === 'class'
393        || nodeName.startsWith('data-aue')
394        || nodeName.startsWith('data-richtext'))
395      .forEach(({ nodeName, nodeValue }) => {
396        wrapper.setAttribute(nodeName, nodeValue);
397        el.removeAttribute(nodeName);
398      });
399    el.append(wrapper);
400  };
401
402  block.querySelectorAll(':scope > div > div').forEach((blockColumn) => {
403    if (blockColumn.hasChildNodes()) {
404      const hasWrapper = !!blockColumn.firstElementChild
405        && validWrappers.some((tagName) => blockColumn.firstElementChild.tagName === tagName);
406      if (!hasWrapper) {
407        wrap(blockColumn);
408      } else if (
409        blockColumn.firstElementChild.tagName === 'PICTURE'
410        && (blockColumn.children.length > 1 || !!blockColumn.textContent.trim())
411      ) {
412        wrap(blockColumn);
413      }
414    }
415  });
416}
417
418/**
419 * Decorates paragraphs containing a single link as buttons.
420 * @param {Element} element container element
421 */
422function decorateButtons(element) {
423  element.querySelectorAll('a').forEach((a) => {
424    a.title = a.title || a.textContent;
425    if (a.href !== a.textContent) {
426      const up = a.parentElement;
427      const twoup = a.parentElement.parentElement;
428      if (!a.querySelector('img')) {
429        if ((up.tagName === 'P' || up.tagName === 'DIV') && up.textContent.includes('{')) {
430          loadInlineStylesProcessor().then(({ applyInlineStyles }) => applyInlineStyles(up, false, a));
431        }
432        if (up.childNodes.length === 1 && (up.tagName === 'P' || up.tagName === 'DIV')) {
433          a.className = 'button'; // default
434          up.classList.add('button-container');
435        }
436        if (
437          up.childNodes.length === 1
438          && up.tagName === 'STRONG'
439          && twoup.childNodes.length === 1
440          && twoup.tagName === 'P'
441        ) {
442          a.className = 'button primary';
443          twoup.classList.add('button-container');
444        }
445        if (
446          up.childNodes.length === 1
447          && up.tagName === 'EM'
448          && twoup.childNodes.length === 1
449          && twoup.tagName === 'P'
450        ) {
451          a.className = 'button secondary';
452          twoup.classList.add('button-container');
453        }
454      }
455    }
456  });
457}
458
459/**
460 * Add <img> for icon, prefixed with codeBasePath and optional prefix.
461 * @param {Element} [span] span element with icon classes
462 * @param {string} [prefix] prefix to be added to icon src
463 * @param {string} [alt] alt text to be added to icon
464 */
465function decorateIcon(span, prefix = '', alt = '') {
466  const iconName = Array.from(span.classList)
467    .find((c) => c.startsWith('icon-'))
468    .substring(5);
469  const img = document.createElement('img');
470  img.dataset.iconName = iconName;
471  img.src = `${window.hlx.codeBasePath}${prefix}/icons/${iconName}.svg`;
472  img.alt = alt;
473  img.loading = 'lazy';
474  span.append(img);
475}
476
477/**
478 * Add <img> for icons, prefixed with codeBasePath and optional prefix.
479 * @param {Element} [element] Element containing icons
480 * @param {string} [prefix] prefix to be added to icon the src
481 */
482function decorateIcons(element, prefix = '') {
483  const icons = [...element.querySelectorAll('span.icon')];
484  icons.forEach((span) => {
485    decorateIcon(span, prefix);
486  });
487}
488
489/**
490 * Decorates all sections in a container element.
491 * @param {Element} main The container element
492 */
493function decorateSections(main) {
494
495  main.querySelectorAll(':scope > div:not([data-section-status])').forEach((section) => {
496    const wrappers = [];
497    let defaultContent = false;
498    [...section.children].forEach((e) => {
499      if ((e.tagName === 'DIV' && e.className) || !defaultContent) {
500        const wrapper = document.createElement('div');
501        wrappers.push(wrapper);
502        defaultContent = e.tagName !== 'DIV' || !e.className;
503        if (defaultContent) wrapper.classList.add('default-content-wrapper');
504      }
505      wrappers[wrappers.length - 1].append(e);
506    });
507    wrappers.forEach((wrapper) => section.append(wrapper));
508    section.classList.add('section');
509    section.dataset.sectionStatus = 'initialized';
510    section.style.display = 'none';
511
512    section.querySelectorAll('p, h1, h2, h3, h4, h5, h6').forEach((el) => {
513      const hasText = el.textContent && el.textContent.trim().length > 0;
514      const hasBrackets = el.textContent.includes('{');
515
516      if (hasText && hasBrackets) {
517        loadInlineStylesProcessor().then(({ applyInlineStyles }) => applyInlineStyles(el, false));
518      }
519    });
520
521    // Process section metadata
522    const sectionMeta = section.querySelector('div.section-metadata');
523    if (sectionMeta) {
524      const meta = readBlockConfig(sectionMeta);
525      Object.keys(meta).forEach((key) => {
526        if (key === 'style') {
527          const styles = meta.style
528            .split(',')
529            .filter((style) => style)
530            .map((style) => toClassName(style.trim()));
531          styles.forEach((style) => section.classList.add(style));
532        } else if (key === 'ID' || key === 'id') {
533          // Set the ID as an actual id attribute for anchor links
534          const sectionId = meta[key].trim();
535          if (sectionId) {
536            section.id = sectionId;
537          }
538        } else {
539          section.dataset[toCamelCase(key)] = meta[key];
540        }
541      });
542
543      // Handle background colour
544      if (meta['backgroundcolour']) {
545        // String in format of "https://example.com/#FF0000",
546        // get the hex color value from the url by splitting the string on slashes
547        // section.classList.add('has-background-colour');
548        const backgroundColourRaw = meta['backgroundcolour'];
549        const backgroundColourString = typeof backgroundColourRaw === 'string'
550          ? backgroundColourRaw
551          : `${backgroundColourRaw}`;
552        const metaBackgroundColour = backgroundColourString.match(/url\(['"]?(.*?)['"]?\)/)?.[1] || backgroundColourString;
553        const hexColor = metaBackgroundColour.split('/').pop();
554
555        // After extracting the hex color value, clean up any remaining url fragments by splitting on the hash symbol and taking the last part
556        const cleanHexColor = hexColor.split('#').pop();
557
558        // Apply background colour directly to section
559        section.style.backgroundColor = `#${cleanHexColor}`;
560      }
561
562      // Handle border image top
563      if (meta['borderimagetop']) {
564        section.classList.add('has-border-image-top');
565        const borderImageTop = document.createElement('span');
566        borderImageTop.className = 'section-border-image-top';
567        const img = document.createElement('img');
568        img.src = meta['borderimagetop'];
569        img.alt = '';
570        borderImageTop.appendChild(img);
571        section.prepend(borderImageTop);
572      }
573
574      // Handle border image bottom
575      if (meta['borderimagebottom']) {
576        section.classList.add('has-border-image-bottom');
577        const borderImageBottom = document.createElement('span');
578        borderImageBottom.className = 'section-border-image-bottom';
579        const img = document.createElement('img');
580        img.src = meta['borderimagebottom'];
581        img.alt = '';
582        borderImageBottom.appendChild(img);
583        section.appendChild(borderImageBottom);
584      }
585
586      sectionMeta.parentNode.remove();
587    }
588
589    // Decorate section background images
590    const desktopSrc = section.dataset.backgroundimage;
591    const mobileSrc = section.dataset.backgroundimagemobile;
592    if (desktopSrc || mobileSrc) {
593      const picture = document.createElement('picture');
594      picture.classList.add('section-background-image');
595
596      // Apply desktop width/height/position settings
597      const bgWidth = section.dataset.backgroundimagewidth;
598      const bgHeightFull = section.dataset.backgroundimageheightfull;
599      const bgPosition = section.dataset.backgroundimageposition;
600      if (bgWidth) picture.classList.add(bgWidth);
601      if (bgHeightFull === 'true') picture.classList.add('bg-img-height-full');
602      if (bgPosition) picture.classList.add(bgPosition);
603
604      // Apply mobile width/height/position settings
605      const bgMobileWidth = section.dataset.backgroundimagemobilewidth;
606      const bgMobileHeightFull = section.dataset.backgroundimagemobileheightfull;
607      const bgMobilePosition = section.dataset.backgroundimagemobileposition;
608      if (bgMobileWidth) picture.classList.add(bgMobileWidth);
609      if (bgMobileHeightFull === 'true') picture.classList.add('bg-img-mobile-height-full');
610      if (bgMobilePosition) picture.classList.add(bgMobilePosition);
611
612      if (desktopSrc && mobileSrc) {
613        const mobileSource = document.createElement('source');
614        mobileSource.setAttribute('media', '(max-width: 640px)');
615        mobileSource.setAttribute('srcset', mobileSrc);
616        picture.appendChild(mobileSource);
617
618        const img = document.createElement('img');
619        img.setAttribute('src', desktopSrc);
620        img.setAttribute('loading', 'lazy');
621        img.setAttribute('alt', '');
622        picture.appendChild(img);
623      } else {
624        const img = document.createElement('img');
625        img.setAttribute('src', desktopSrc || mobileSrc);
626        img.setAttribute('loading', 'lazy');
627        img.setAttribute('alt', '');
628        picture.appendChild(img);
629      }
630
631      section.prepend(picture);
632    }
633
634    if (section.dataset.enableinlinestyling === 'true') {
635      section.querySelectorAll('p, h1, h2, h3, h4, h5, h6').forEach((el) => {
636        if (el.textContent && el.textContent.includes('{')) {
637          loadInlineStylesProcessor().then(({ applyInlineStyles }) => applyInlineStyles(el));
638        }
639      });
640    }
641  });
642}
643
644/**
645 * Gets placeholders object.
646 * @param {string} [prefix] Location of placeholders
647 * @returns {object} Window placeholders object
648 */
649// eslint-disable-next-line import/prefer-default-export
650async function fetchPlaceholders() {
651  window.placeholders = window.placeholders || {};
652
653  if (!window.placeholders.default) {
654    window.placeholders.default = new Promise((resolve) => {
655      const url = getMetadata('placeholders') || '/placeholders.json';
656
657      fetch(url)
658        .then((resp) => resp.ok ? resp.json() : {})
659        .then((json) => {
660          const placeholders = {};
661          json.data?.forEach(({ Key, Text }) => {
662            if (Key) placeholders[toCamelCase(Key)] = Text;
663          });
664          window.placeholders.default = placeholders;
665          resolve(placeholders);
666        })
667        .catch(() => {
668          window.placeholders.default = {};
669          resolve({});
670        });
671    });
672  }
673
674  return window.placeholders.default;
675}
676
677/**
678 * Builds a block DOM Element from a two dimensional array, string, or object
679 * @param {string} blockName name of the block
680 * @param {*} content two dimensional array or string or object of content
681 */
682function buildBlock(blockName, content) {
683  const table = Array.isArray(content) ? content : [[content]];
684  const blockEl = document.createElement('div');
685  // build image block nested div structure
686  blockEl.classList.add(blockName);
687  table.forEach((row) => {
688    const rowEl = document.createElement('div');
689    row.forEach((col) => {
690      const colEl = document.createElement('div');
691      const vals = col.elems ? col.elems : [col];
692      vals.forEach((val) => {
693        if (val) {
694          if (typeof val === 'string') {
695            colEl.innerHTML += val;
696          } else {
697            colEl.appendChild(val);
698          }
699        }
700      });
701      rowEl.appendChild(colEl);
702    });
703    blockEl.appendChild(rowEl);
704  });
705  return blockEl;
706}
707
708/**
709 * Loads JS and CSS for a block.
710 * @param {Element} block The block element
711 */
712async function loadBlock(block) {
713  const status = block.dataset.blockStatus;
714  if (status !== 'loading' && status !== 'loaded') {
715    block.dataset.blockStatus = 'loading';
716    const { blockName } = block.dataset;
717    try {
718      const cssLoaded = loadCSS(`${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.css`);
719      const decorationComplete = new Promise((resolve) => {
720        (async () => {
721          try {
722            const mod = await import(
723              `${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.js`
724            );
725            if (mod.default) {
726              await mod.default(block);
727            }
728          } catch (error) {
729            // eslint-disable-next-line no-console
730            console.log(`failed to load module for ${blockName}`, error);
731          }
732          resolve();
733        })();
734      });
735      await Promise.all([cssLoaded, decorationComplete]);
736    } catch (error) {
737      // eslint-disable-next-line no-console
738      console.log(`failed to load block ${blockName}`, error);
739    }
740    block.dataset.blockStatus = 'loaded';
741  }
742  return block;
743}
744
745/**
746 * Updates all section status in a container element.
747 * @param {Element} main The container element
748 */
749export function updateSectionsStatus(main) {
750  const sections = [...main.querySelectorAll(':scope > div.section')];
751  for (let i = 0; i < sections.length; i += 1) {
752    const section = sections[i];
753    const status = section.dataset.sectionStatus;
754    if (status !== 'loaded') {
755      const loadingBlock = section.querySelector('.block[data-block-status="initialized"], .block[data-block-status="loading"]');
756      if (loadingBlock) {
757        section.dataset.sectionStatus = 'loading';
758        break;
759      } else {
760        section.dataset.sectionStatus = 'loaded';
761        section.style.display = null;
762      }
763    }
764  }
765}
766
767export async function loadBlocks(main) {
768  updateSectionsStatus(main);
769  const blocks = [...main.querySelectorAll('div.block')];
770  for (let i = 0; i < blocks.length; i += 1) {
771    // eslint-disable-next-line no-await-in-loop
772    await loadBlock(blocks[i]);
773    updateSectionsStatus(main);
774  }
775}
776
777/**
778 * Decorates a block.
779 * @param {Element} block The block element
780 */
781function decorateBlock(block) {
782  const shortBlockName = block.classList[0];
783  if (shortBlockName && !block.dataset.blockStatus) {
784    block.classList.add('block');
785
786    // [...block.children].forEach((row) => {
787    //   const label = row.children[0];
788    //   const body = row.children[1];
789
790    //   if (!body) {
791    //     if (row) {
792    //       if (label.innerText && row.closest('.block')) {
793    //         try {
794    //           row.closest('.block').classList.add(label.innerText);
795    //         } catch (err) {
796    //           return;
797    //         }
798    //       }
799    //     }
800    //     row.remove();
801    //   }
802    // });
803
804    block.dataset.blockName = shortBlockName;
805    block.dataset.blockStatus = 'initialized';
806    wrapTextNodes(block);
807    const blockWrapper = block.parentElement;
808    blockWrapper.classList.add(`${shortBlockName}-wrapper`);
809    const section = block.closest('.section');
810    if (section) section.classList.add(`${shortBlockName}-container`);
811    // eslint-disable-next-line no-use-before-define
812    decorateButtons(block);
813  }
814}
815
816/**
817 * Decorates all blocks in a container element.
818 * @param {Element} main The container element
819 */
820function decorateBlocks(main) {
821  main.querySelectorAll('div.section:not(.no-load) > div > div').forEach(decorateBlock);
822}
823
824/**
825 * Loads a block named 'header' into header
826 * @param {Element} header header element
827 * @returns {Promise}
828 */
829async function loadHeader(header) {
830  const headerBlock = buildBlock('header', '');
831  header.append(headerBlock);
832  decorateBlock(headerBlock);
833  return loadBlock(headerBlock);
834}
835
836/**
837 * Loads a block named 'footer' into footer
838 * @param footer footer element
839 * @returns {Promise}
840 */
841async function loadFooter(footer) {
842  const footerBlock = buildBlock('footer', '');
843  footer.append(footerBlock);
844  decorateBlock(footerBlock);
845  return loadBlock(footerBlock);
846}
847
848/**
849 * Loads all blocks in a section.
850 * @param {Element} section The section element
851 */
852
853async function loadSection(section, loadCallback) {
854  const status = section.dataset.sectionStatus;
855  if (!status || status === 'initialized') {
856    section.dataset.sectionStatus = 'loading';
857    const blocks = [...section.querySelectorAll('div.block')];
858    for (let i = 0; i < blocks.length; i += 1) {
859      // eslint-disable-next-line no-await-in-loop
860      await loadBlock(blocks[i]);
861    }
862    if (loadCallback) await loadCallback(section);
863    section.dataset.sectionStatus = 'loaded';
864    section.style.display = null;
865  }
866}
867
868/**
869 * Loads all sections.
870 * @param {Element} element The parent element of sections to load
871 */
872
873async function loadSections(element) {
874  /**
875   * .no-load is a PDOL customisation. As part of PDP SSR initilisation
876   * we manually build up the section that houses the PDP dropin. This
877   * Change prevents it from loading again.
878   */
879  const sections = [...element.querySelectorAll('div.section:not(.no-load)')];
880  for (let i = 0; i < sections.length; i += 1) {
881    // eslint-disable-next-line no-await-in-loop
882    await loadSection(sections[i]);
883    if (i === 0 && sampleRUM.enhance) {
884      sampleRUM.enhance();
885    }
886  }
887}
888
889init();
890
891export {
892  buildBlock,
893  createOptimizedPicture,
894  decorateBlock,
895  decorateBlocks,
896  decorateButtons,
897  decorateIcons,
898  decorateSections,
899  decorateTemplateAndTheme,
900  fetchPlaceholders,
901  getMetadata,
902  loadBlock,
903  loadCSS,
904  loadFooter,
905  loadHeader,
906  loadScript,
907  loadSection,
908  loadSections,
909  readBlockConfig,
910  sampleRUM,
911  setup,
912  toCamelCase,
913  toClassName,
914  wrapTextNodes,
915};

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.