diff --git a/404.html b/404.html index 94288e04da..159f44a7ee 100644 --- a/404.html +++ b/404.html @@ -18,6 +18,42 @@ + - diff --git a/scripts/aem.js b/scripts/aem.js index ec275180fb..825d2e680e 100644 --- a/scripts/aem.js +++ b/scripts/aem.js @@ -503,20 +503,35 @@ function decorateSections(main) { */ // eslint-disable-next-line import/prefer-default-export async function fetchPlaceholders(prefix = 'default') { + const overrides = getMetadata('placeholders') || ''; + const [fallback, override] = overrides.split('\n'); window.placeholders = window.placeholders || {}; if (!window.placeholders[prefix]) { window.placeholders[prefix] = new Promise((resolve) => { - const url = getMetadata('placeholders') || `${prefix === 'default' ? '' : prefix}/placeholders.json`; - fetch(url) - .then((resp) => { + const url = fallback || `${prefix === 'default' ? '' : prefix}/placeholders.json`; + Promise.all([fetch(url), override ? fetch(override) : Promise.resolve()]) + .then(async ([resp, oResp]) => { if (resp.ok) { - return resp.json(); + if (oResp?.ok) { + return Promise.all([resp.json(), await oResp.json()]); + } + return Promise.all([resp.json(), {}]); } return {}; }) - .then((json) => { + + .then(([json, oJson]) => { const placeholders = {}; + // build placeholders object json.data.forEach(({ Key, Value }) => { + // check for overrides + if (oJson?.data) { + const overrideItem = oJson.data.find((item) => item.Key === Key); + if (overrideItem) { + // eslint-disable-next-line no-param-reassign + Value = overrideItem.Value; + } + } if (Key) { const keys = Key.split('.'); const lastKey = keys.pop(); @@ -527,10 +542,13 @@ async function fetchPlaceholders(prefix = 'default') { target[lastKey] = Value; } }); + // cache placeholders window.placeholders[prefix] = placeholders; + // return placeholders resolve(window.placeholders[prefix]); }) - .catch(() => { + .catch((error) => { + console.error('error loading placeholders', error); // error loading placeholders window.placeholders[prefix] = {}; resolve(window.placeholders[prefix]); diff --git a/scripts/configs.js b/scripts/configs.js index 93e69e2cbe..95dd232739 100644 --- a/scripts/configs.js +++ b/scripts/configs.js @@ -1,3 +1,7 @@ +/* eslint-disable import/no-cycle */ + +import { getRootPath } from './scripts.js'; + const ALLOWED_CONFIGS = ['prod', 'stage', 'dev']; /** @@ -29,21 +33,22 @@ export const calcEnvironment = () => { return environment; }; -function buildConfigURL(environment) { +function buildConfigURL(environment, root = '/') { const env = environment || calcEnvironment(); let fileName = 'configs.json'; if (env !== 'prod') { fileName = `configs-${env}.json`; } - const configURL = new URL(`${window.location.origin}/${fileName}`); + const configURL = new URL(`${window.location.origin}${root}${fileName}`); return configURL; } const getConfigForEnvironment = async (environment) => { const env = environment || calcEnvironment(); + const root = getRootPath() || '/'; try { - const configJSON = window.sessionStorage.getItem(`config:${env}`); + const configJSON = window.sessionStorage.getItem(`config:${env}:${root}`); if (!configJSON) { throw new Error('No config in session storage'); } @@ -55,13 +60,13 @@ const getConfigForEnvironment = async (environment) => { return parsedConfig; } catch (e) { - let configJSON = await fetch(buildConfigURL(env)); + let configJSON = await fetch(buildConfigURL(env, root)); if (!configJSON.ok) { throw new Error(`Failed to fetch config for ${env}`); } configJSON = await configJSON.json(); configJSON[':expiry'] = Math.round(Date.now() / 1000) + 7200; - window.sessionStorage.setItem(`config:${env}`, JSON.stringify(configJSON)); + window.sessionStorage.setItem(`config:${env}:${root}`, JSON.stringify(configJSON)); return configJSON; } }; @@ -87,13 +92,21 @@ export const getConfigValue = async (configParam, environment) => { export const getHeaders = async (scope, environment) => { const env = environment || calcEnvironment(); const config = await getConfigForEnvironment(env); - const configElements = config.data.filter((el) => el?.key.includes(`headers.${scope}`)); + const configElements = config.data.filter((el) => el?.key.includes('headers.all') || el?.key.includes(`headers.${scope}`)); return configElements.reduce((obj, item) => { let { key } = item; + + // global values + if (key.includes('commerce.headers.all.')) { + key = key.replace('commerce.headers.all.', ''); + } + + // scoped values if (key.includes(`commerce.headers.${scope}.`)) { key = key.replace(`commerce.headers.${scope}.`, ''); } + return { ...obj, [key]: item.value }; }, {}); }; diff --git a/scripts/initializers/order.js b/scripts/initializers/order.js index 6a10da04df..8cfc58439d 100644 --- a/scripts/initializers/order.js +++ b/scripts/initializers/order.js @@ -16,6 +16,7 @@ import { ORDER_STATUS_PATH, CUSTOMER_PATH, SALES_GUEST_VIEW_PATH, SALES_ORDER_VIEW_PATH, } from '../constants.js'; +import { rootLink } from '../scripts.js'; await initializeDropin(async () => { const { pathname, searchParams } = new URL(window.location.href); @@ -81,11 +82,11 @@ async function handleUserOrdersRedirects( events.on('order/error', () => { if (checkIsAuthenticated()) { - window.location.href = CUSTOMER_ORDERS_PATH; + window.location.href = rootLink(CUSTOMER_ORDERS_PATH); } else if (isTokenProvided) { - window.location.href = orderNumber ? `${ORDER_STATUS_PATH}?orderRef=${orderNumber}` : ORDER_STATUS_PATH; + window.location.href = orderNumber ? rootLink(`${ORDER_STATUS_PATH}?orderRef=${orderNumber}`) : rootLink(ORDER_STATUS_PATH); } else { - window.location.href = `${ORDER_STATUS_PATH}?orderRef=${orderRef}`; + window.location.href = rootLink(`${ORDER_STATUS_PATH}?orderRef=${orderRef}`); } }); @@ -106,7 +107,7 @@ async function handleUserOrdersRedirects( } if (targetPath) { - window.location.href = targetPath; + window.location.href = rootLink(targetPath); } else { await initializers.mountImmediately(initialize, { langDefinitions, diff --git a/scripts/initializers/pdp.js b/scripts/initializers/pdp.js index 2b5223e26f..8871141599 100644 --- a/scripts/initializers/pdp.js +++ b/scripts/initializers/pdp.js @@ -1,5 +1,5 @@ +/* eslint-disable import/no-cycle */ /* eslint-disable import/prefer-default-export */ -/* eslint import/no-cycle: [2, { maxDepth: 1 }] */ import { initializers } from '@dropins/tools/initializer.js'; import { Image, provider as UI } from '@dropins/tools/components.js'; diff --git a/scripts/scripts.js b/scripts/scripts.js index d2edf055ec..3fb82727df 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -150,6 +150,7 @@ export function decorateMain(main) { buildAutoBlocks(main); decorateSections(main); decorateBlocks(main); + // decorateLinks(main); // author can decide when to localize links } function preloadFile(href, as) { @@ -356,6 +357,51 @@ export async function fetchIndex(indexFile, pageSize = 500) { return newIndex; } +/** + * Get root path + */ +export function getRootPath() { + window.ROOT_PATH = window.ROOT_PATH || getMetadata('root') || '/'; + return window.ROOT_PATH; +} + +/** + * Decorates links. + * @param {Element} main The main element + */ +export function decorateLinks(main) { + const root = getRootPath(); + if (root === '/') return; + const links = main.querySelectorAll('a'); + + links.forEach((link) => { + const url = new URL(link.href, window.location.origin); + const { pathname } = url; + + // If the link is already localized, do nothing + if (pathname.startsWith('//') || pathname.startsWith(root)) return; + + if ( + link.href.startsWith('/') + || link.href.startsWith(window.location.origin) + ) { + link.href = `${root}${url.pathname.substring(1)}${url.search}${url.hash}`; + } + }); +} + +/** + * Decorates links. + * @param {string} [url] url to be localized + */ +export function rootLink(link) { + const root = getRootPath().replace(/\/$/, ''); + + // If the link is already localized, do nothing + if (link.startsWith(root)) return link; + return `${root}${link}`; +} + /** * Check if consent was given for a specific topic. * @param {*} topic Topic identifier diff --git a/scripts/wishlist/api.js b/scripts/wishlist/api.js index cf7f807d3b..731728ccbe 100644 --- a/scripts/wishlist/api.js +++ b/scripts/wishlist/api.js @@ -1,8 +1,9 @@ import { getSignInToken, performMonolithGraphQLQuery } from '../commerce.js'; import { CUSTOMER_LOGIN_PATH } from '../constants.js'; +import { rootLink } from '../scripts.js'; const redirectToSignin = () => { - window.location = CUSTOMER_LOGIN_PATH; + window.location = rootLink(CUSTOMER_LOGIN_PATH); }; const getWishlistsQuery = `