Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 106 additions & 77 deletions head.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,93 +35,122 @@
document.head.append(miloStyles, miloUtils, miloDecorate);
}

function buildLink(language, url) {
const link = document.createElement('link');
link.setAttribute('rel', 'alternate');
link.setAttribute('hreflang', language);
link.setAttribute('href', url);
return link;
}

const userAgentMeta = document.querySelector('meta[name="hreflinksuseragents"]');
const allowedAgents = userAgentMeta && userAgentMeta.content.split(',');
const userAgentString = window.navigator.userAgent;
const isAllowedAgent = allowedAgents && allowedAgents.some((agent) => userAgentString.includes(agent.trim()));

const LOCALES = ['ae_ar', 'ae_en', 'africa', 'ar', 'at', 'au', 'be_en', 'be_fr', 'be_nl', 'bg', 'br', 'ca_fr', 'ca', 'ch_de', 'ch_fr', 'ch_it', 'cl', 'cn', 'co', 'cr', 'cy_en', 'cz', 'de', 'dk', 'ec', 'ee', 'eg_ar', 'eg_en', 'el', 'es', 'fi', 'fr', 'gr_el', 'gr_en', 'gt', 'hk_en', 'hk_zh', 'hu', 'id_en', 'id_id', 'ie', 'il_en', 'il_he', 'in_hi', 'in', 'it', 'jp', 'kr', 'kw_ar', 'kw_en', 'la', 'langstore', 'lt', 'lu_de', 'lu_en', 'lu_fr', 'lv', 'mena_ar', 'mena_en', 'mt', 'mx', 'my_en', 'my_ms', 'ng', 'nl', 'no', 'nz', 'pe', 'ph_en', 'ph_fil', 'pl', 'pr', 'pt', 'qa_ar', 'qa_en', 'ro', 'ru', 'sa_ar', 'sa_en', 'se', 'sg', 'si', 'sk', 'th_en', 'th_th', 'tr', 'tw', 'ua', 'uk', 'vn_en', 'vn_vi', 'za'];

(() => {
async function fetchAndParseSitemap() {
const sitemapOrigin = 'https://business.adobe.com';
const { origin, pathname } = window.location;

let sitemapPath = '/sitemap.xml';
const localeMatch = LOCALES.find(locale => pathname.startsWith(`/${locale}/`));
if (!headAlreadyLoaded) {
(async () => {
// --- hreflang config (change these two lines per property) ---
const HREFLANG_ORIGIN = 'https://business.adobe.com';
const HREFLANG_TEMPLATE = '/{locale}/sitemap.xml';
// ------------------------------------------------------------
const HREFLANG_CACHE_PREFIX = 'hreflang-';
const HREFLANG_TIMEOUT_MS = 5000;

function buildHreflangMap(xmlDoc) {
const map = {};
xmlDoc.querySelectorAll('url').forEach((urlEl) => {
const loc = urlEl.querySelector('loc')?.textContent;
if (!loc) return;
const links = [...urlEl.querySelectorAll('link[rel="alternate"]')]
.map((el) => ({ hreflang: el.getAttribute('hreflang'), href: el.getAttribute('href') }))
.filter((l) => l.hreflang && l.href);
if (links.length) map[loc] = links;
});
return map;
}

if (localeMatch) {
sitemapPath = `/${localeMatch}/sitemap.xml`;
function getCachedMap(cacheKey) {
try { return JSON.parse(sessionStorage.getItem(cacheKey)); } catch { return null; }
}

try {
const response = await fetch(`${origin}${sitemapPath}`);
if (!response.ok) {
console.warn('Failed to fetch sitemap:', response.status);
return;
}

const xmlText = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
const parseError = xmlDoc.querySelector('parsererror');
if (parseError) {
return;

function setCachedMap(cacheKey, map) {
try {
Object.keys(sessionStorage)
.filter((k) => k.startsWith(HREFLANG_CACHE_PREFIX) && k !== cacheKey)
.forEach((k) => sessionStorage.removeItem(k));
sessionStorage.setItem(cacheKey, JSON.stringify(map));
} catch {
window.lana?.log('hreflang: sessionStorage quota exceeded', { tags: 'hreflang', severity: 'warning' });
}

const urlElements = xmlDoc.querySelectorAll('url');
let currentUrlElement = null;
const correctedPath = pathname.includes('html') ? pathname : `${pathname}.html`
const isLocalRoot = `${sitemapOrigin}/${localeMatch}/` === `${sitemapOrigin}${pathname}`;
const rootPage = isLocalRoot ? `${sitemapOrigin}/${localeMatch}/` : `${sitemapOrigin}/`;
const currentPageUrl = pathname !== '/' && pathname !== `/${localeMatch}/` ? `${sitemapOrigin}${correctedPath}` : `${rootPage}`;

for (const urlElement of urlElements) {
const loc = urlElement.querySelector('loc')?.textContent;
if (loc === currentPageUrl || loc === currentPageUrl.replace(/\/$/, '')) {
currentUrlElement = urlElement;
break;
}

async function fetchSitemapMap(sitemapUrl) {
const ctrl = new AbortController();
const tid = setTimeout(() => ctrl.abort(), HREFLANG_TIMEOUT_MS);
try {
const res = await fetch(sitemapUrl, { signal: ctrl.signal });
if (!res.ok) {
window.lana?.log(`hreflang: fetch failed (${res.status}) ${sitemapUrl}`, { tags: 'hreflang', severity: 'error' });
return null;
}
}

if (!currentUrlElement) {
console.warn('Current page not found in sitemap');
return;
}

const alternateLinks = currentUrlElement.querySelectorAll('link[rel="alternate"]');
const linkElements = [];

alternateLinks.forEach(altLink => {
const hreflang = altLink.getAttribute('hreflang');
const href = altLink.getAttribute('href');

if (hreflang && href) {
linkElements.push(buildLink(hreflang, href));
const xmlDoc = new DOMParser().parseFromString(await res.text(), 'text/xml');
if (xmlDoc.querySelector('parsererror')) {
window.lana?.log(`hreflang: parse failed ${sitemapUrl}`, { tags: 'hreflang', severity: 'error' });
return null;
}
});

const titleElement = document.head.querySelector('title');
if (linkElements.length > 0 && titleElement) {
titleElement.after(...linkElements);
return buildHreflangMap(xmlDoc);
} catch (e) {
const msg = e.name === 'AbortError'
? `hreflang: timeout fetching ${sitemapUrl}`
: `hreflang: error fetching ${sitemapUrl} - ${e.message}`;
window.lana?.log(msg, { tags: 'hreflang', severity: 'error' });
return null;
} finally {
clearTimeout(tid);
}
} catch (error) {
console.error('Error fetching or parsing sitemap:', error);
}
}

if (isAllowedAgent && !headAlreadyLoaded) {
fetchAndParseSitemap();
}
})();
function getSitemapPath(localeMatch) {
if (!HREFLANG_TEMPLATE.includes('{locale}')) {
window.lana?.log(`hreflang: HREFLANG_TEMPLATE missing {locale} placeholder: ${HREFLANG_TEMPLATE}`, { tags: 'hreflang', severity: 'error' });
return HREFLANG_TEMPLATE;
}
return localeMatch
? HREFLANG_TEMPLATE.replace('{locale}', localeMatch)
: HREFLANG_TEMPLATE.replace(/\/?{locale}/, '');
}

function getPageUrl(pathname, localeMatch) {
const isLocaleRoot = localeMatch && pathname === `/${localeMatch}/`;
if (pathname === '/' || isLocaleRoot) return `${HREFLANG_ORIGIN}${pathname}`;
const normalized = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
return `${HREFLANG_ORIGIN}${normalized.endsWith('.html') ? normalized : `${normalized}.html`}`;
}

function injectHreflangLinks(links) {
const titleEl = document.head.querySelector('title');
if (!titleEl) return;
titleEl.after(...links.map(({ hreflang, href }) => {
const el = document.createElement('link');
el.setAttribute('rel', 'alternate');
el.setAttribute('hreflang', hreflang);
el.setAttribute('href', href);
return el;
}));
}

const uaMeta = document.querySelector('meta[name="hreflinksuseragents"]');
if (!uaMeta?.content) return;
if (!uaMeta.content.split(',').some((a) => navigator.userAgent.includes(a.trim()))) return;

const { pathname } = window.location;
const localeMatch = LOCALES.find((l) => pathname.startsWith(`/${l}/`));
const sitemapPath = getSitemapPath(localeMatch);
const sitemapUrl = `${window.location.origin}${sitemapPath}`;
const cacheKey = HREFLANG_CACHE_PREFIX + sitemapPath;

let map = getCachedMap(cacheKey);
if (!map) {
map = await fetchSitemapMap(sitemapUrl);
if (!map) return;
setCachedMap(cacheKey, map);
}

const pageUrl = getPageUrl(pathname, localeMatch);
const links = map[pageUrl] ?? map[pageUrl.replace(/\/$/, '')];
if (links?.length) injectHreflangLinks(links);
})();
}

const sheetSchema = document.querySelector('[type="application/ld+json"]');

Expand Down
6 changes: 0 additions & 6 deletions nala/blocks/demo/demo.page.js

This file was deleted.

17 changes: 0 additions & 17 deletions nala/blocks/demo/demo.spec.js

This file was deleted.

20 changes: 0 additions & 20 deletions nala/blocks/demo/demo.test.js

This file was deleted.

2 changes: 1 addition & 1 deletion nala/blocks/event-speakers/event-speakers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
{
tcid: '0',
name: '@event-speakers',
path: '/test/features/blocks/event-speakers',
path: '/drafts/nala/blocks/event-speakers/event-speakers',
speakerIdx: 1,
tags: '@event-speakers @smoke @regression @bacom @bacomSmoke',
},
Expand Down
2 changes: 1 addition & 1 deletion nala/blocks/tree-view/tree-view.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
{
tcid: '1',
name: '@BACOM-TreeView-Checks',
path: '/test/features/blocks/tree-view',
path: '/drafts/nala/blocks/tree-view/tree-view',
tags: '@tree-view @smoke @regression @bacom @bacomSmoke',
},
],
Expand Down
2 changes: 1 addition & 1 deletion nala/blocks/tree-view/tree-view.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const miloLibs = process.env.MILO_LIBS || '';

test.describe('BACOM Tree-View Block Test Suite', () => {
test(
`${features[0].name}, @bacom_live, ${features[0].tags}, https://bacom.adobe.com`,
`${features[0].name}, @bacom_live, ${features[0].tags}`,
async ({ page, baseURL }) => {
const treeView = new TreeView(page);
const testPage = `${baseURL}${features[0].path}${miloLibs}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = {
industry: '',
seoTitle: 'Enterprise Analytics Guide | Adobe',
seoDescription: 'Download our comprehensive analytics guide for enterprise teams.',
socialShareImage: SAMPLE_PNG,
primaryProductName: 'Analytics',
experienceFragment: 'Generic',
pdfAsset: SAMPLE_PDF,
Expand Down Expand Up @@ -82,6 +83,7 @@ module.exports = {
industry: '',
seoTitle: 'State of Digital Marketing 2025 | Adobe',
seoDescription: 'Discover key trends shaping the marketing landscape.',
socialShareImage: SAMPLE_PNG,
primaryProductName: 'Analytics',
experienceFragment: 'Generic',
pdfAsset: SAMPLE_PDF,
Expand Down Expand Up @@ -114,6 +116,7 @@ module.exports = {
industry: '',
seoTitle: 'Adobe Analytics Demo | Adobe',
seoDescription: 'Watch our product demo showcasing Adobe Analytics capabilities.',
socialShareImage: SAMPLE_PNG,
primaryProductName: 'Analytics',
experienceFragment: 'Generic',
videoUrl: 'https://video.tv.adobe.com/v/3456789',
Expand Down Expand Up @@ -146,6 +149,7 @@ module.exports = {
industry: '',
seoTitle: 'Analytics Trends Infographic | Adobe',
seoDescription: 'See the key analytics trends in a concise infographic.',
socialShareImage: SAMPLE_PNG,
primaryProductName: 'Analytics',
experienceFragment: 'Generic',
pdfAsset: SAMPLE_PDF,
Expand Down Expand Up @@ -181,6 +185,7 @@ module.exports = {
industry: '',
seoTitle: 'Analytics Trends Infographic | Adobe',
seoDescription: 'See the key analytics trends in a concise infographic.',
socialShareImage: SAMPLE_PNG,
primaryProductName: 'Analytics',
experienceFragment: 'Generic',
pdfAsset: SAMPLE_PDF,
Expand Down
Loading
Loading