diff --git a/blog/blocks/animation/animation.js b/blog/blocks/animation/animation.js
index efa6a95..4851642 100644
--- a/blog/blocks/animation/animation.js
+++ b/blog/blocks/animation/animation.js
@@ -1,4 +1,4 @@
-import { buildFigure } from '../../scripts/scripts.js';
+import { buildFigure } from '../../../scripts/scripts.js';
export default function decorateAnimation(blockEl) {
const a = blockEl.querySelector('a');
diff --git a/blog/blocks/article-feed/article-feed.js b/blog/blocks/article-feed/article-feed.js
index f7fb6f8..cbc8d65 100644
--- a/blog/blocks/article-feed/article-feed.js
+++ b/blog/blocks/article-feed/article-feed.js
@@ -1,10 +1,12 @@
import {
readBlockConfig,
+} from '../../../scripts/lib-franklin.js';
+import {
buildArticleCard,
fetchPlaceholders,
fetchBlogArticleIndex,
stamp,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
function isCardOnPage(article) {
const path = article.path.split('.')[0];
diff --git a/blog/blocks/article-header/article-header.js b/blog/blocks/article-header/article-header.js
index ed06aee..dc21fe8 100644
--- a/blog/blocks/article-header/article-header.js
+++ b/blog/blocks/article-header/article-header.js
@@ -1,11 +1,13 @@
import {
- buildFigure,
+ toClassName,
createOptimizedPicture,
+} from '../../../scripts/lib-franklin.js';
+import {
+ buildFigure,
getRootPath,
- toClassName,
loadAuthorTaxonomy,
authorTaxonomy,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
async function populateAuthorImg(imgContainer, url, name) {
await loadAuthorTaxonomy();
diff --git a/blog/blocks/banner/banner.js b/blog/blocks/banner/banner.js
index fc912be..b2aa7d4 100644
--- a/blog/blocks/banner/banner.js
+++ b/blog/blocks/banner/banner.js
@@ -1,7 +1,9 @@
import {
normalizeHeadings,
+} from '../../../scripts/lib-franklin.js';
+import {
makeLinksRelative,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
export default function decorate(block) {
const bannerContents = document.createElement('div');
diff --git a/blog/blocks/embed/embed.js b/blog/blocks/embed/embed.js
index ebdb6a4..4c9805e 100644
--- a/blog/blocks/embed/embed.js
+++ b/blog/blocks/embed/embed.js
@@ -1,4 +1,4 @@
-import { buildFigure } from '../../scripts/scripts.js';
+import { buildFigure } from '../../../scripts/scripts.js';
const loadScript = (url, callback, type) => {
const head = document.querySelector('head');
diff --git a/blog/blocks/featured-article/featured-article.js b/blog/blocks/featured-article/featured-article.js
index 2fcd934..70a9d87 100644
--- a/blog/blocks/featured-article/featured-article.js
+++ b/blog/blocks/featured-article/featured-article.js
@@ -4,7 +4,7 @@ import {
buildArticleCard,
getBlogArticle,
rewritePath,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
async function decorateFeaturedArticle(featuredArticleEl, articlePath) {
const article = await getBlogArticle(articlePath);
diff --git a/blog/blocks/footer/footer.js b/blog/blocks/footer/footer.js
index 809466a..8160afa 100644
--- a/blog/blocks/footer/footer.js
+++ b/blog/blocks/footer/footer.js
@@ -1,7 +1,7 @@
import {
fetchPlaceholders,
debug,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
import createTag from '../gnav/gnav-utils.js';
const GLOBE_IMG = '';
diff --git a/blog/blocks/gnav/gnav-profile.js b/blog/blocks/gnav/gnav-profile.js
index d2160b1..1cf6df8 100644
--- a/blog/blocks/gnav/gnav-profile.js
+++ b/blog/blocks/gnav/gnav-profile.js
@@ -1,4 +1,4 @@
-import { getHelixEnv } from '../../scripts/scripts.js';
+import { getHelixEnv } from '../../../scripts/scripts.js';
import createTag from './gnav-utils.js';
function decorateEmail(email) {
diff --git a/blog/blocks/gnav/gnav-search.js b/blog/blocks/gnav/gnav-search.js
index dced0cc..5e2d972 100644
--- a/blog/blocks/gnav/gnav-search.js
+++ b/blog/blocks/gnav/gnav-search.js
@@ -1,4 +1,9 @@
-import { fetchBlogArticleIndex, createOptimizedPicture } from '../../scripts/scripts.js';
+import {
+ createOptimizedPicture,
+} from '../../../scripts/lib-franklin.js';
+import {
+ fetchBlogArticleIndex,
+} from '../../../scripts/scripts.js';
import createTag from './gnav-utils.js';
function decorateCard(hit) {
diff --git a/blog/blocks/gnav/gnav.js b/blog/blocks/gnav/gnav.js
index 36dc19d..fcdbbe7 100644
--- a/blog/blocks/gnav/gnav.js
+++ b/blog/blocks/gnav/gnav.js
@@ -1,12 +1,14 @@
+import {
+ loadCSS,
+} from '../../../scripts/lib-franklin.js';
import {
loadScript,
getHelixEnv,
getBlockClasses,
debug,
makeLinkRelative,
- loadCSS,
getRootPath,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
import createTag from './gnav-utils.js';
const COMPANY_IMG = '
';
diff --git a/blog/blocks/images/images.js b/blog/blocks/images/images.js
index 3de1361..64ea38b 100644
--- a/blog/blocks/images/images.js
+++ b/blog/blocks/images/images.js
@@ -1,4 +1,4 @@
-import { buildFigure } from '../../scripts/scripts.js';
+import { buildFigure } from '../../../scripts/scripts.js';
function buildColumns(rowEl, count) {
const columnEls = Array.from(rowEl.children);
diff --git a/blog/blocks/infographic/infographic.js b/blog/blocks/infographic/infographic.js
index 0375c0e..6cb02e4 100644
--- a/blog/blocks/infographic/infographic.js
+++ b/blog/blocks/infographic/infographic.js
@@ -1,4 +1,4 @@
-import { buildFigure } from '../../scripts/scripts.js';
+import { buildFigure } from '../../../scripts/scripts.js';
export default function decorateInfographic(blockEl) {
const figEl = buildFigure(blockEl.firstChild.firstChild);
diff --git a/blog/blocks/interlinks/interlinks.js b/blog/blocks/interlinks/interlinks.js
index 6b928ef..c2ffcf2 100644
--- a/blog/blocks/interlinks/interlinks.js
+++ b/blog/blocks/interlinks/interlinks.js
@@ -1,6 +1,6 @@
import {
getRootPath,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
/**
* Checks if a given match intersects with an existing match
diff --git a/blog/blocks/recommended-articles/recommended-articles.js b/blog/blocks/recommended-articles/recommended-articles.js
index 0c2257c..2221076 100644
--- a/blog/blocks/recommended-articles/recommended-articles.js
+++ b/blog/blocks/recommended-articles/recommended-articles.js
@@ -3,7 +3,7 @@ import {
getBlogArticle,
fetchPlaceholders,
rewritePath,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
async function decorateRecommendedArticles(recommendedArticlesEl, paths) {
if (recommendedArticlesEl.classList.contains('small')) {
diff --git a/blog/blocks/tags/tags.js b/blog/blocks/tags/tags.js
index 94a3cf5..3a6fe3a 100644
--- a/blog/blocks/tags/tags.js
+++ b/blog/blocks/tags/tags.js
@@ -1,7 +1,9 @@
import {
toClassName,
+} from '../../../scripts/lib-franklin.js';
+import {
loadTaxonomy,
-} from '../../scripts/scripts.js';
+} from '../../../scripts/scripts.js';
export default function decorateTags(blockEl) {
const tags = blockEl.textContent.split(', ');
diff --git a/blog/blocks/video/video.js b/blog/blocks/video/video.js
index 82ffdaf..b42f377 100644
--- a/blog/blocks/video/video.js
+++ b/blog/blocks/video/video.js
@@ -1,4 +1,4 @@
-import { buildFigure } from '../../scripts/scripts.js';
+import { buildFigure } from '../../../scripts/scripts.js';
export default function decorate(block) {
if (block.classList.contains('is-loaded')) {
diff --git a/blog/scripts/scripts.js b/blog/scripts/scripts.js
deleted file mode 100644
index 38146ce..0000000
--- a/blog/scripts/scripts.js
+++ /dev/null
@@ -1,1707 +0,0 @@
-/*
- * Copyright 2021 Adobe. All rights reserved.
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License. You may obtain a copy
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
- * OF ANY KIND, either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-
-const PRODUCTION_DOMAINS = ['business.adobe.com'];
-
-/**
- * log RUM if part of the sample.
- * @param {string} checkpoint identifies the checkpoint in funnel
- * @param {Object} data additional data for RUM sample
- */
-export function sampleRUM(checkpoint, data = {}) {
- try {
- window.hlx = window.hlx || {};
- if (!window.hlx.rum) {
- const usp = new URLSearchParams(window.location.search);
- const weight = (usp.get('rum') === 'on') ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
- // eslint-disable-next-line no-bitwise
- const hashCode = (s) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0);
- const id = `${hashCode(window.location.href)}-${new Date().getTime()}-${Math.random().toString(16).substr(2, 14)}`;
- const random = Math.random();
- const isSelected = (random * weight < 1);
- // eslint-disable-next-line object-curly-newline
- window.hlx.rum = { weight, id, random, isSelected };
- }
- const { random, weight, id } = window.hlx.rum;
- if (random && (random * weight < 1)) {
- const sendPing = () => {
- // eslint-disable-next-line object-curly-newline, max-len, no-use-before-define
- const body = JSON.stringify({ weight, id, referer: window.location.href, generation: window.RUM_GENERATION, checkpoint, ...data });
- const url = `https://rum.hlx.page/.rum/${weight}`;
- // eslint-disable-next-line no-unused-expressions
- navigator.sendBeacon(url, body);
- };
- sendPing();
- // special case CWV
- if (checkpoint === 'cwv') {
- // use classic script to avoid CORS issues
- const script = document.createElement('script');
- script.src = 'https://rum.hlx.page/.rum/web-vitals/dist/web-vitals.iife.js';
- script.onload = () => {
- const storeCWV = (measurement) => {
- data.cwv = {};
- data.cwv[measurement.name] = measurement.value;
- sendPing();
- };
- // When loading `web-vitals` using a classic script, all the public
- // methods can be found on the `webVitals` global namespace.
- window.webVitals.getCLS(storeCWV);
- window.webVitals.getFID(storeCWV);
- window.webVitals.getLCP(storeCWV);
- };
- document.head.appendChild(script);
- }
- }
- } catch (e) {
- // something went wrong
- }
-}
-
-/**
- * Loads a CSS file.
- * @param {string} href The path to the CSS file
- */
-export function loadCSS(href, callback) {
- if (!document.querySelector(`head > link[href="${href}"]`)) {
- const link = document.createElement('link');
- link.setAttribute('rel', 'stylesheet');
- link.setAttribute('href', href);
- if (typeof callback === 'function') {
- link.onload = (e) => callback(e.type);
- link.onerror = (e) => callback(e.type);
- }
- document.head.appendChild(link);
- } else if (typeof callback === 'function') {
- callback('noop');
- }
-}
-
-/**
- * Retrieves the content of a metadata tag.
- * @param {string} name The metadata name (or property)
- * @returns {string} The metadata value
- */
-export function getMetadata(name) {
- const attr = name && name.includes(':') ? 'property' : 'name';
- const meta = [...document.head.querySelectorAll(`meta[${attr}="${name}"]`)].map((el) => el.content).join(', ');
- return meta;
-}
-
-/**
- * Adds one or more URLs to the dependencies for publishing.
- * @param {string|[string]} url The URL(s) to add as dependencies
- */
-export function addPublishDependencies(url) {
- const urls = Array.isArray(url) ? url : [url];
- window.hlx = window.hlx || {};
- if (window.hlx.dependencies && Array.isArray(window.hlx.dependencies)) {
- window.hlx.dependencies = window.hlx.dependencies.concat(urls);
- } else {
- window.hlx.dependencies = urls;
- }
-}
-
-/**
- * Sanitizes a name for use as class name.
- * @param {*} name The unsanitized name
- * @returns {string} The class name
- */
-export function toClassName(name) {
- return name && typeof name === 'string'
- ? name.toLowerCase().replace(/[^0-9a-z]/gi, '-')
- : '';
-}
-
-/**
- * Decorates a block.
- * @param {Element} block The block element
- */
-export function decorateBlock(block) {
- const trimDashes = (str) => str.replace(/(^\s*-)|(-\s*$)/g, '');
- const classes = Array.from(block.classList.values());
- const blockName = classes[0];
- if (!blockName) return;
- const section = block.closest('.section');
- if (section) {
- section.classList.add(`${blockName}-container`.replace(/--/g, '-'));
- }
- const blockWithVariants = blockName.split('--');
- const shortBlockName = trimDashes(blockWithVariants.shift());
- const variants = blockWithVariants.map((v) => trimDashes(v));
- block.classList.add(shortBlockName);
- block.classList.add(...variants);
-
- block.classList.add('block');
- block.setAttribute('data-block-name', shortBlockName);
- block.setAttribute('data-block-status', 'initialized');
-
- const blockWrapper = block.parentElement;
- blockWrapper.classList.add(`${shortBlockName}-wrapper`);
-}
-
-/**
- * Extracts the config from a block.
- * @param {Element} block The block element
- * @returns {object} The block config
- */
-export function readBlockConfig(block) {
- const config = {};
- block.querySelectorAll(':scope>div').forEach((row) => {
- if (row.children) {
- const cols = [...row.children];
- if (cols[1]) {
- const col = cols[1];
- const name = toClassName(cols[0].textContent);
- let value = '';
- if (col.querySelector('a')) {
- const as = [...col.querySelectorAll('a')];
- if (as.length === 1) {
- value = as[0].href;
- } else {
- value = as.map((a) => a.href);
- }
- } else if (col.querySelector('p')) {
- const ps = [...col.querySelectorAll('p')];
- if (ps.length === 1) {
- value = ps[0].textContent;
- } else {
- value = ps.map((p) => p.textContent);
- }
- } else value = row.children[1].textContent;
- config[name] = value;
- }
- }
- });
- return config;
-}
-
-/**
- * Decorates all sections in a container element.
- * @param {Element} $main The container element
- */
-export function decorateSections($main) {
- $main.querySelectorAll(':scope > div').forEach((section) => {
- const wrappers = [];
- let defaultContent = false;
- [...section.children].forEach((e) => {
- if (e.tagName === 'DIV' || !defaultContent) {
- const wrapper = document.createElement('div');
- wrappers.push(wrapper);
- defaultContent = e.tagName !== 'DIV';
- if (defaultContent) wrapper.classList.add('default-content-wrapper');
- }
- wrappers[wrappers.length - 1].append(e);
- });
- wrappers.forEach((wrapper) => section.append(wrapper));
- section.classList.add('section');
- section.setAttribute('data-section-status', 'initialized');
-
- /* process section metadata */
- const sectionMeta = section.querySelector('div.section-metadata');
- if (sectionMeta) {
- const meta = readBlockConfig(sectionMeta);
- const keys = Object.keys(meta);
- keys.forEach((key) => {
- if (key === 'style') section.classList.add(toClassName(meta.style));
- else section.dataset[key] = meta[key];
- });
- sectionMeta.remove();
- }
- });
-}
-
-/**
- * Updates all section status in a container element.
- * @param {Element} main The container element
- */
-export function updateSectionsStatus(main) {
- const sections = [...main.querySelectorAll(':scope > div.section')];
- for (let i = 0; i < sections.length; i += 1) {
- const section = sections[i];
- const status = section.getAttribute('data-section-status');
- if (status !== 'loaded') {
- const loadingBlock = section.querySelector('.block[data-block-status="initialized"], .block[data-block-status="loading"]');
- if (loadingBlock) {
- section.setAttribute('data-section-status', 'loading');
- break;
- } else {
- section.setAttribute('data-section-status', 'loaded');
- }
- }
- }
-}
-
-/**
- * Decorates all blocks in a container element.
- * @param {Element} main The container element
- */
-export function decorateBlocks(main) {
- main
- .querySelectorAll('div.section > div > div')
- .forEach((block) => decorateBlock(block));
-}
-
-/**
- * Builds a block DOM Element from a two dimensional array
- * @param {string} blockName name of the block
- * @param {any} content two dimensional array or string or object of content
- */
-export function buildBlock(blockName, content) {
- const table = Array.isArray(content) ? content : [[content]];
- const blockEl = document.createElement('div');
- // build image block nested div structure
- blockEl.classList.add(blockName);
- table.forEach((row) => {
- const rowEl = document.createElement('div');
- row.forEach((col) => {
- const colEl = document.createElement('div');
- const vals = col.elems ? col.elems : [col];
- vals.forEach((val) => {
- if (val) {
- if (typeof val === 'string') {
- colEl.innerHTML += val;
- } else {
- colEl.appendChild(val);
- }
- }
- });
- rowEl.appendChild(colEl);
- });
- blockEl.appendChild(rowEl);
- });
- return (blockEl);
-}
-
-/**
- * Loads JS and CSS for a block.
- * @param {Element} block The block element
- */
-export async function loadBlock(block, eager = false) {
- if (!(block.getAttribute('data-block-status') === 'loading' || block.getAttribute('data-block-status') === 'loaded')) {
- block.setAttribute('data-block-status', 'loading');
- const blockName = block.getAttribute('data-block-name');
- const { list } = window.milo?.libs?.blocks;
- // Determine if block should be loaded from milo libs
- const isMiloBlock = !!(list && list.includes(blockName));
- let base;
- if (isMiloBlock) {
- base = window.milo.libs.base;
- } else {
- base = window.hlx.codeBasePath;
- }
- try {
- const cssLoaded = new Promise((resolve) => {
- loadCSS(`${base}/blocks/${blockName}/${blockName}.css`, resolve);
- if (isMiloBlock) {
- loadCSS(`${base}/styles/variables.css`, resolve);
- }
- });
- const decorationComplete = new Promise((resolve) => {
- (async () => {
- try {
- const mod = await import(`${base}/blocks/${blockName}/${blockName}.js`);
- if (mod.default) {
- await mod.default(block, blockName, document, eager);
- }
- } catch (err) {
- // eslint-disable-next-line no-console
- console.log(`failed to load module for ${blockName}`, err);
- }
- resolve();
- })();
- });
- await Promise.all([cssLoaded, decorationComplete]);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.log(`failed to load block ${blockName}`, err);
- }
- block.setAttribute('data-block-status', 'loaded');
- }
-}
-
-/**
- * Loads JS and CSS for all blocks in a container element.
- * @param {Element} main The container element
- */
-export async function loadBlocks(main) {
- updateSectionsStatus(main);
- const blocks = [...main.querySelectorAll('div.block')];
- for (let i = 0; i < blocks.length; i += 1) {
- // eslint-disable-next-line no-await-in-loop
- await loadBlock(blocks[i]);
- updateSectionsStatus(main);
- }
-}
-
-/**
- * Returns a picture element with webp and fallbacks
- * @param {string} src The image URL
- * @param {boolean} eager load image eager
- * @param {Array} breakpoints breakpoints and corresponding params (eg. width)
- */
-export function createOptimizedPicture(src, alt = '', eager = false, breakpoints = [{ media: '(min-width: 400px)', width: '2000' }, { width: '750' }]) {
- const url = new URL(src, window.location.href);
- const picture = document.createElement('picture');
- const { pathname } = url;
- const ext = pathname.substring(pathname.lastIndexOf('.') + 1);
-
- // webp
- breakpoints.forEach((br) => {
- const source = document.createElement('source');
- if (br.media) source.setAttribute('media', br.media);
- source.setAttribute('type', 'image/webp');
- source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`);
- picture.appendChild(source);
- });
-
- // fallback
- breakpoints.forEach((br, i) => {
- if (i < breakpoints.length - 1) {
- const source = document.createElement('source');
- if (br.media) source.setAttribute('media', br.media);
- source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
- picture.appendChild(source);
- } else {
- const img = document.createElement('img');
- img.setAttribute('loading', eager ? 'eager' : 'lazy');
- img.setAttribute('alt', alt);
- picture.appendChild(img);
- img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
- }
- });
-
- return picture;
-}
-
-/**
- * Normalizes all headings within a container element.
- * @param {Element} el The container element
- * @param {[string]]} allowedHeadings The list of allowed headings (h1 ... h6)
- */
-export function normalizeHeadings(el, allowedHeadings) {
- const allowed = allowedHeadings.map((h) => h.toLowerCase());
- el.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((tag) => {
- const h = tag.tagName.toLowerCase();
- if (allowed.indexOf(h) === -1) {
- // current heading is not in the allowed list -> try first to "promote" the heading
- let level = parseInt(h.charAt(1), 10) - 1;
- while (allowed.indexOf(`h${level}`) === -1 && level > 0) {
- level -= 1;
- }
- if (level === 0) {
- // did not find a match -> try to "downgrade" the heading
- while (allowed.indexOf(`h${level}`) === -1 && level < 7) {
- level += 1;
- }
- }
- if (level !== 7) {
- tag.outerHTML = `
${category}
`], - [h1], - [` -${publicationDate}
`], - [{ elems: [picture.closest('p'), getImageCaption(picture)] }], - ]); - div.append(articleHeaderBlockEl); - mainEl.prepend(div); -} - -function buildTagsBlock(mainEl) { - const tags = getMetadata('article:tag'); - if (tags) { - const tagsBlock = buildBlock('tags', [ - [`${tags}
`], - ]); - const recBlock = mainEl.querySelector('.recommended-articles'); - if (recBlock) { - recBlock.parentNode.insertBefore(tagsBlock, recBlock); - } else { - mainEl.lastElementChild.append(tagsBlock); - } - } -} - -function buildTagHeader(mainEl) { - const div = mainEl.querySelector('div'); - const h1 = mainEl.querySelector('h1'); - const picture = mainEl.querySelector('picture'); - const tagHeaderBlockEl = buildBlock('tag-header', [ - [h1], - [{ elems: [picture.closest('p')] }], - ]); - div.prepend(tagHeaderBlockEl); -} - -function buildArticleFeed(mainEl, type) { - const div = document.createElement('div'); - const title = mainEl.querySelector('h1').textContent.trim(); - const articleFeedEl = buildBlock('article-feed', [ - [type, title], - ]); - div.append(articleFeedEl); - mainEl.append(div); -} - -/** - * builds images blocks from default content. - * @param {Element} mainEl The container element - */ -function buildImageBlocks(mainEl) { - // select all non-featured, default (non-images block) images - const imgEls = [...mainEl.querySelectorAll(':scope > div > p > picture')]; - imgEls.forEach((imgEl) => { - const parentEl = imgEl.parentNode; - const imagesBlockEl = buildBlock('images', { - elems: [parentEl.cloneNode(true), getImageCaption(imgEl)], - }); - parentEl.parentNode.insertBefore(imagesBlockEl, parentEl); - parentEl.remove(); - }); -} - -/** - * Builds all synthetic blocks in a container element. - * @param {Element} main The container element - */ -function buildAutoBlocks(mainEl) { - removeStylingFromImages(mainEl); - try { - if (getMetadata('publication-date') && !mainEl.querySelector('.article-header')) { - buildArticleHeader(mainEl); - buildTagsBlock(mainEl); - } - if (window.location.pathname.includes('/tags/')) { - buildTagHeader(mainEl); - if (!document.querySelector('.article-feed')) { - buildArticleFeed(mainEl, 'tags'); - } - } - buildImageBlocks(mainEl); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Auto Blocking failed', error); - } -} - -function unwrapBlock(block) { - const section = block.parentNode; - const els = [...section.children]; - const blockSection = document.createElement('div'); - const postBlockSection = document.createElement('div'); - const nextSection = section.nextSibling; - section.parentNode.insertBefore(blockSection, nextSection); - section.parentNode.insertBefore(postBlockSection, nextSection); - - let appendTo; - els.forEach((el) => { - if (el === block) appendTo = blockSection; - if (appendTo) { - appendTo.appendChild(el); - appendTo = postBlockSection; - } - }); - if (!section.hasChildNodes()) { - section.remove(); - } - if (!blockSection.hasChildNodes()) { - blockSection.remove(); - } - if (!postBlockSection.hasChildNodes()) { - postBlockSection.remove(); - } -} - -function splitSections() { - document.querySelectorAll('main > div > div').forEach((block) => { - const blocksToSplit = ['article-header', 'article-feed', 'recommended-articles']; - if (blocksToSplit.includes(block.className)) { - unwrapBlock(block); - } - }); -} - -function removeEmptySections() { - document.querySelectorAll('main > div:empty').forEach((div) => { - div.remove(); - }); -} - -/** - * Wraps each section in an additional {@code div}. - * @param {[Element]} sections The sections - */ -function wrapSections(sections) { - sections.forEach((div) => { - if (!div.id) { - const wrapper = document.createElement('div'); - wrapper.className = 'section'; - div.parentNode.appendChild(wrapper); - wrapper.appendChild(div); - } - }); -} - -/** - * Decorates the main element. - * @param {Element} main The main element - */ -export function decorateMain(main) { - // forward compatible pictures redecoration - decoratePictures(main); - buildAutoBlocks(main); - splitSections(); - removeEmptySections(); - wrapSections(main.querySelectorAll(':scope > div')); - decorateBlocks(main); - - sampleRUM.observe(main.querySelectorAll('div[data-block-name]')); - - /* hide h1 on homepage */ - if (window.location.pathname.endsWith('/blog/')) { - const h1 = document.querySelector('h1'); - if (h1) h1.classList.add('hidden'); - } -} - -function unhideBody(id) { - try { - document.head.removeChild(document.getElementById(id)); - } catch (e) { - // nothing - } -} - -function hideBody(id) { - const style = document.createElement('style'); - style.id = id; - style.innerHTML = 'body{visibility: hidden !important}'; - - try { - document.head.appendChild(style); - } catch (e) { - // nothing - } -} - -/** - * loads everything needed to get to LCP. - */ -async function loadEager() { - const main = document.querySelector('main'); - if (main) { - const bodyHideStyleId = ( - alloy - ? 'alloy-prehiding' - : 'at-body-style' - ); - decorateMain(main); - document.querySelector('body').classList.add('appear'); - if (alloy) { - document.querySelector('body').classList.add('personalization-container'); - } - const target = getMetadata('target'); - if (target && target.toLocaleLowerCase() === 'on') { - hideBody(bodyHideStyleId); - setTimeout(() => { - unhideBody(bodyHideStyleId); - }, 3000); - } - await waitForLCP(); - } -} - -/** - * loads a script by adding a script tag to the head. - * @param {string} url URL of the js file - * @param {Function} callback callback on load - * @param {string} type type attribute of script tag - * @returns {Element} script element - */ -export function loadScript(url, callback, type) { - const head = document.querySelector('head'); - const script = document.createElement('script'); - script.setAttribute('src', url); - if (type) { - script.setAttribute('type', type); - } - head.append(script); - script.onload = callback; - return script; -} - -const LANG = { - EN: 'en', - DE: 'de', - FR: 'fr', - KO: 'kr', - ES: 'es', - IT: 'it', - JP: 'jp', - BR: 'br', -}; - -const LANG_LOC = { - en: 'en-US', - de: 'de-DE', - fr: 'fr-FR', - kr: 'ko-KR', - es: 'es-ES', // es-MX? - it: 'it-IT', - jp: 'ja-JP', - br: 'pt-BR', -}; - -const SPECIAL_LANG = [ - 'kr', - 'jp', -]; - -let language; -let taxonomy; -export const authorTaxonomy = {}; - -/** - * Get the current Helix environment - * @returns {Object} the env object - */ -export function getHelixEnv() { - let envName = sessionStorage.getItem('helix-env') || new URL(window.location.href).searchParams.get('env'); - if (!envName) envName = 'prod'; - const envs = { - stage: { - ims: 'stg1', - adobeIO: 'cc-collab-stage.adobe.io', - adminconsole: 'stage.adminconsole.adobe.com', - account: 'stage.account.adobe.com', - }, - prod: { - ims: 'prod', - adobeIO: 'cc-collab.adobe.io', - adminconsole: 'adminconsole.adobe.com', - account: 'account.adobe.com', - }, - }; - const env = envs[envName]; - - const overrideItem = sessionStorage.getItem('helix-env-overrides'); - if (overrideItem) { - const overrides = JSON.parse(overrideItem); - const keys = Object.keys(overrides); - env.overrides = keys; - - keys.forEach((value) => { - env[value] = overrides[value]; - }); - } - - if (env) { - env.name = envName; - } - return env; -} - -export function debug(message) { - const { hostname } = window.location; - const env = getHelixEnv(); - if (env.name !== 'prod' || hostname === 'localhost') { - // eslint-disable-next-line no-console - console.log(message); - } -} - -/** - * For the given list of topics, returns the corresponding computed taxonomy: - * - category: main topic - * - topics: tags as an array - * - visibleTopics: list of visible topics, including parents - * - allTopics: list of all topics, including parents - * @param {Array} topics List of topics - * @returns {Object} Taxonomy object - */ -function computeTaxonomyFromTopics(topics, path) { - // no topics: default to a randomly choosen category - const category = topics?.length > 0 ? topics[0] : 'news'; - - if (taxonomy) { - const allTopics = []; - const visibleTopics = []; - // if taxonomy loaded, we can compute more - topics.forEach((tag) => { - const tax = taxonomy.get(tag.trim()); - if (tax) { - if (!allTopics.includes(tag) && !tax.skipMeta) { - allTopics.push(tag); - if (tax.isUFT) visibleTopics.push(tag); - const parents = taxonomy.getParents(tag); - if (parents) { - parents.forEach((parent) => { - const ptax = taxonomy.get(parent); - if (!allTopics.includes(parent)) { - allTopics.push(parent); - if (ptax.isUFT) visibleTopics.push(parent); - } - }); - } - } - } else { - debug(`Unknown topic in tags list: ${tag} ${path ? `on page ${path}` : '(current page)'}`); - } - }); - return { - category, topics, visibleTopics, allTopics, - }; - } - return { - category, topics, - }; -} - -export function getLanguage() { - if (language) return language; - language = LANG.EN; - const segs = window.location.pathname.split('/'); - if (segs && segs.length > 0) { - // eslint-disable-next-line no-restricted-syntax - for (const [, value] of Object.entries(LANG)) { - if (value === segs[1]) { - language = value; - break; - } - } - } - return language; -} - -export async function loadTaxonomy(elements) { - if (!SPECIAL_LANG.includes(getLanguage())) { - return; - } - const mod = await import('./taxonomy.js'); - taxonomy = await mod.default(getLanguage()); - if (taxonomy) { - // taxonomy loaded, post loading adjustments - // fix the links which have been created before the taxonomy has been loaded - // (pre lcp or in lcp block). - elements.forEach((a) => { - const topic = a.innerText; - const tax = taxonomy.get(topic); - if (tax) { - a.href = tax.link; - } else { - // eslint-disable-next-line no-console - debug(`Trying to get a link for an unknown topic: ${topic} (current page)`); - a.href = '#'; - } - delete a.dataset.topicLink; - }); - - // adjust meta article:tag - const metaTags = getMetadata('article:tag'); - const currentTags = metaTags ? getMetadata('article:tag').split(',') : []; - const articleTax = computeTaxonomyFromTopics(currentTags); - const allTopics = articleTax.allTopics || []; - allTopics.forEach((topic) => { - if (!currentTags.includes(topic)) { - // computed topic (parent...) is not in meta -> add it - const newMetaTag = document.createElement('meta'); - newMetaTag.setAttribute('property', 'article:tag'); - newMetaTag.setAttribute('content', topic); - document.head.append(newMetaTag); - } - }); - - currentTags.forEach((tag) => { - const tax = taxonomy.get(tag); - if (tax && tax.skipMeta) { - // if skipMeta, remove from meta "article:tag" - const meta = document.querySelector(`[property="article:tag"][content="${tag}"]`); - if (meta) { - meta.remove(); - } - // but add as meta with name - const newMetaTag = document.createElement('meta'); - newMetaTag.setAttribute('name', tag); - newMetaTag.setAttribute('content', 'true'); - document.head.append(newMetaTag); - } - }); - } -} - -/** - * Load authorTaxonomy to map tanslated author name to English based author link. - */ -export async function loadAuthorTaxonomy() { - if (!SPECIAL_LANG.includes(getLanguage())) { - return; - } - // Do this process only one time. - if (Object.keys(authorTaxonomy).length) { - return; - } - const target = `/${getLanguage()}/blog/authors/author-taxonomy.json`; - const res = await fetch(target); - const json = await res.json(); - json.data.forEach((item) => { - authorTaxonomy[item.Name] = item.Link; - }); -} - -// add language to html tag -export function setLanguage() { - const lang = getLanguage(); - const html = document.querySelector('html'); - html.setAttribute('lang', LANG_LOC[lang]); -} - -async function loadMartech() { - // const target = getMetadata('target').toLocaleLowerCase() === 'on'; - const target = true; - const env = getHelixEnv(); - const prod = env.name === 'prod' && usp.get('alloy-env') !== 'stage'; - - // new alloy implementation - if (alloy) { - window.alloy_all = { - xdm: { - _adobe_corpnew: { - digitalData: { - page: { - pageInfo: { - language: LANG_LOC[getLanguage()] || '', - legacyMarketSegment: 'COM', - }, - }, - }, - }, - }, - }; - window.alloy_deferred = { - xdm: { - _adobe_corpnew: { - digitalData: { - }, - }, - }, - promises: [], - }; - window.marketingtech = { - adobe: { - target, - alloy: { - edgeConfigId: ( - prod ? '65acfd54-d9fe-405c-ba04-8342d6782ab0' : '7d1ba912-10b6-4384-a8ff-4bfb1178e869' - ), - }, - launch: { - url: ( - prod - ? 'https://assets.adobedtm.com/d4d114c60e50/cf25c910a920/launch-9e8f94c77339.min.js' - : 'https://assets.adobedtm.com/d4d114c60e50/cf25c910a920/launch-1bba233684fa-development.js' - ), - load: (l) => { - const delay = () => ( - setTimeout(l, 3500) - ); - if (document.readyState === 'complete') { - delay(); - } else { - window.addEventListener('load', delay); - } - }, - }, - }, - }; - window.alloy_deferred.promises.push(new Promise((resolve) => { - loadScript(`https://www.adobe.com/marketingtech/${( - prod ? 'main.alloy.min.js' : 'main.alloy.stage.js' - )}`, async () => { - const resp = await fetch('/blog/instrumentation.json'); - const json = await resp.json(); - const get = (obj, str) => { - const segs = str.split('.'); - let temp = obj; - let i = 0; - const il = segs.length - 1; - // get to the path - // eslint-disable-next-line no-plusplus - for (; i < il; i++) { - const seg = segs[i]; - if (!temp[seg]) { - return undefined; - } - temp = temp[seg]; - } - // get the value - return temp[segs[i]]; - }; - const set = (obj, str, value) => { - const segs = str.split('.'); - let temp = obj; - let i = 0; - const il = segs.length - 1; - // get to the path - // eslint-disable-next-line no-plusplus - for (; i < il; i++) { - const seg = segs[i]; - temp[seg] = temp[seg] || {}; - temp = temp[seg]; - } - // set the value - temp[segs[i]] = value; - return obj; - }; - // set digitalData - const digitalDataMap = json.digitaldata.data; - digitalDataMap.forEach((mapping) => { - const metaValue = getMetadata(mapping.metadata); - if (metaValue) { - set( - // eslint-disable-next-line no-underscore-dangle - window.alloy_deferred.xdm._adobe_corpnew.digitalData, - mapping.digitaldata, - metaValue, - ); - } - }); - // set lists - const digitalDataLists = json['digitaldata-lists'].data; - digitalDataLists.forEach((listEntry) => { - const metaValue = getMetadata(listEntry.metadata); - if (metaValue) { - let listValue = get( - // eslint-disable-next-line no-underscore-dangle - window.alloy_deferred.xdm._adobe_corpnew.digitalData, - listEntry.digitaldata, - ) || ''; - const name = listEntry['list-item-name']; - const metaValueArr = listEntry.delimiter - ? metaValue.split(listEntry.delimiter) - : [metaValue]; - metaValueArr.forEach((value) => { - const escapedValue = value.split('|').join(); // well, well... - listValue += `${listValue ? ' | ' : ''}${name}: ${escapedValue}`; - }); - set( - // eslint-disable-next-line no-underscore-dangle - window.alloy_deferred.xdm._adobe_corpnew.digitalData, - listEntry.digitaldata, - listValue, - ); - } - }); - resolve(); - }); - })); - - // legacy implementation - } else { - window.digitalData = { - page: { - pageInfo: { - language: LANG_LOC[getLanguage()] || '', - category: 'unknown: before setDigitalData()', - }, - }, - }; - window.marketingtech = { - adobe: { - target, - audienceManager: true, - launch: { - property: 'global', - environment: 'production', - }, - }, - }; - window.targetGlobalSettings = window.targetGlobalSettings || {}; - loadScript('https://www.adobe.com/marketingtech/main.min.js', async () => { - const digitaldata = window.digitalData; - digitaldata.page.pageInfo.category = 'unknown: before instrumentation.json'; - const resp = await fetch('/blog/instrumentation.json'); - const json = await resp.json(); - delete digitaldata.page.pageInfo.category; - const digitalDataMap = json.digitaldata.data; - digitalDataMap.forEach((mapping) => { - const metaValue = getMetadata(mapping.metadata); - if (metaValue) { - // eslint-disable-next-line no-underscore-dangle - digitaldata._set(mapping.digitaldata, metaValue); - } - }); - const digitalDataLists = json['digitaldata-lists'].data; - digitalDataLists.forEach((listEntry) => { - const metaValue = getMetadata(listEntry.metadata); - if (metaValue) { - // eslint-disable-next-line no-underscore-dangle - let listValue = digitaldata._get(listEntry.digitaldata) || ''; - const name = listEntry['list-item-name']; - const metaValueArr = listEntry.delimiter - ? metaValue.split(listEntry.delimiter) - : [metaValue]; - metaValueArr.forEach((value) => { - const escapedValue = value.split('|').join(); // well, well... - listValue += `${listValue ? ' | ' : ''}${name}: ${escapedValue}`; - }); - // eslint-disable-next-line no-underscore-dangle - digitaldata._set(listEntry.digitaldata, listValue); - } - }); - }); - } -} - -async function loadfooterBanner(main) { - const getPath = (url) => { - const u = new URL(url); - return u.pathname; - }; - // getting Banner URL from the json - const { href } = window.location; - let URLpattern; - const resp = await fetch(`${getRootPath()}/footer-banner.json`); - const json = await resp.json(); - let defaultBannerURL; - let footerBannerURL; - const metaTag = getMetadata('article:tag'); - let metaTags; - if (metaTag) { - metaTags = getMetadata('article:tag').split(', '); - } - json.data.every((entry) => { - if (entry.URL === 'default' || entry.default === 'default') { - defaultBannerURL = entry.banner; - } - - // checking URL's column - const endStrMark = entry.URL.slice(-1) !== '*' ? '$' : ''; - URLpattern = new RegExp(`${entry.URL}${endStrMark}`); - if (entry.URL && URLpattern.test(href)) { - footerBannerURL = entry.banner; - return false; - } - - // checking tag's column - if (metaTags && metaTags.find((tag) => tag.toLowerCase() === entry.tag.toLowerCase())) { - footerBannerURL = entry.banner; - return false; - } - return true; - }); - - if (!footerBannerURL) { - footerBannerURL = defaultBannerURL; - } - - // Do nothing if footerBannerURL isn't available to avoid 404 - if (!footerBannerURL) { - return; - } - - // get block body from the Banner URL - const response = await fetch(getPath(`${footerBannerURL}.plain.html`)); - if (response.ok) { - const responseEl = document.createElement('div'); - responseEl.innerHTML = await response.text(); - responseEl.classList.add('section'); - const bannerCTABlock = responseEl.querySelector('div[class^="banner-cta"]'); - main.append(responseEl); - const header = document.querySelector('header'); - const matchGnavCta = () => { - const gnavCta = header.querySelector('.gnav-cta a'); - if (gnavCta) { - bannerCTABlock.querySelector('a').href = gnavCta.href; - } - }; - matchGnavCta(); - header.addEventListener('gnav:init', () => { - matchGnavCta(); - }); - - // decorate the banner block - decorateBlock(bannerCTABlock); - loadBlock(bannerCTABlock); - } -} - -/** - * loads everything that doesn't need to be delayed. - */ -async function loadLazy() { - const main = document.querySelector('main'); - - // set lang attribute - setLanguage(); - - // Compliant headings. - compliantHeadings(); - - // post LCP actions go here - sampleRUM('lcp'); - - /* load gnav */ - const header = document.querySelector('header'); - const gnavPath = getMetadata('gnav') || `${getRootPath()}/gnav`; - header.setAttribute('data-block-name', 'gnav'); - header.setAttribute('data-gnav-source', gnavPath); - loadBlock(header); - - /* load footer */ - const footer = document.querySelector('footer'); - footer.setAttribute('data-block-name', 'footer'); - footer.setAttribute('data-footer-source', `${getRootPath()}/footer`); - loadBlock(footer); - loadfooterBanner(main); - - /* nofollow-link */ - if (getMetadata('nofollow-links') === 'on') { - const { default: nofollow } = await import(`${window.milo?.libs?.base}/features/nofollow.js`); - nofollow('/blog/seo/nofollow.json'); - } - - loadBlocks(main); - - const taxElements = document.querySelectorAll('.article-category a, .featured-article-card-category a'); - await loadTaxonomy(taxElements); - await loadAuthorTaxonomy(); - - loadCSS('/blog/styles/lazy-styles.css'); - addFavIcon('/blog/styles/favicon.svg'); - if (!window.hlx.lighthouse) loadMartech(); -} - -/** - * loads everything that happens a lot later, without impacting - * the user experience. - */ -function loadDelayed() { - // eslint-disable-next-line import/no-cycle - window.setTimeout(() => import('./delayed.js'), 3000); - // load anything that can be postponed to the latest here -} - -/** - * Build figcaption element - * @param {Element} pEl The original element to be placed in figcaption. - * @returns figCaptionEl Generated figcaption - */ -export function buildCaption(pEl) { - const figCaptionEl = document.createElement('figcaption'); - pEl.classList.add('caption'); - figCaptionEl.append(pEl); - return figCaptionEl; -} - -/** - * Build figure element - * @param {Element} blockEl The original element to be placed in figure. - * @returns figEl Generated figure - */ -export function buildFigure(blockEl) { - const figEl = document.createElement('figure'); - figEl.classList.add('figure'); - // content is picture only, no caption or link - if (blockEl.firstChild) { - if (blockEl.firstChild.nodeName === 'PICTURE' || blockEl.firstChild.nodeName === 'VIDEO') { - figEl.append(blockEl.firstChild); - } else if (blockEl.firstChild.nodeName === 'P') { - const pEls = Array.from(blockEl.children); - pEls.forEach((pEl) => { - if (pEl.firstChild) { - if (pEl.firstChild.nodeName === 'PICTURE' || pEl.firstChild.nodeName === 'VIDEO') { - figEl.append(pEl.firstChild); - } else if (pEl.firstChild.nodeName === 'EM') { - const figCapEl = buildCaption(pEl); - figEl.append(figCapEl); - } else if (pEl.firstChild.nodeName === 'A') { - const picEl = figEl.querySelector('picture'); - if (picEl) { - pEl.firstChild.textContent = ''; - pEl.firstChild.append(picEl); - } - figEl.prepend(pEl.firstChild); - } - } - }); - // catch link-only figures (like embed blocks); - } else if (blockEl.firstChild.nodeName === 'A') { - figEl.append(blockEl.firstChild); - } - } - return figEl; -} - -/** - * Build article card - * @param {Element} article The article data to be placed in card. - * @returns card Generated card - */ -export function buildArticleCard(article, type = 'article') { - const { - title, description, image, imageAlt, category, h1, - } = article; - - const picture = createOptimizedPicture(image, imageAlt || title, type === 'featured-article', [{ width: '750' }]); - const pictureTag = picture.outerHTML; - const card = document.createElement('a'); - let tagLink = `${window.location.origin}${getRootPath()}/tags/${toClassName(category)}`; - if (taxonomy && taxonomy.get(category)) { - tagLink = taxonomy.get(category).link; - } - card.className = `${type}-card`; - card.href = article.path; - card.innerHTML = `