From 6ff08f77e5830d85ff4168eb66ec5c534443b405 Mon Sep 17 00:00:00 2001 From: rgravitvl Date: Tue, 9 Jul 2024 13:15:53 +0530 Subject: [PATCH 1/5] Updated all item list schema generation --- blocks/product-category/product-category.js | 4 +- blocks/product-family/product-family.js | 4 +- blocks/side-nav/side-nav.js | 2 + blocks/timeline/timeline.js | 2 + blocks/workflow-tabs/workflow-tabs.js | 13 ++- scripts/schema.js | 122 ++++++++++++++------ 6 files changed, 108 insertions(+), 39 deletions(-) diff --git a/blocks/product-category/product-category.js b/blocks/product-category/product-category.js index c93ae88e..ea1de857 100644 --- a/blocks/product-category/product-category.js +++ b/blocks/product-category/product-category.js @@ -4,7 +4,7 @@ import { } from '../../scripts/dom-builder.js'; import { makePublicUrl, imageHelper } from '../../scripts/scripts.js'; import { getMetadata } from '../../scripts/lib-franklin.js'; -import { buildProductCategorySchema } from '../../scripts/schema.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; export function createCard(product, firstCard = false) { const cardWrapper = a( @@ -52,7 +52,7 @@ export default async function decorate(block) { products.forEach((product, index) => { cardList.append(createCard(product, index === 0)); }); - if (products.length > 0) buildProductCategorySchema(products); + if (products.length > 0) buildItemListSchema(products, 'product-category'); block.textContent = ''; block.append(cardList); diff --git a/blocks/product-family/product-family.js b/blocks/product-family/product-family.js index e80d29ef..7cf73f86 100644 --- a/blocks/product-family/product-family.js +++ b/blocks/product-family/product-family.js @@ -7,7 +7,7 @@ import { div, span, button, fieldset, ul, li, input, a, img, p, } from '../../scripts/dom-builder.js'; import { decorateIcons } from '../../scripts/lib-franklin.js'; -import { buildProductCategorySchema } from '../../scripts/schema.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; const productSkeleton = div( { class: 'coveo-skeleton flex flex-col w-full lg:flex-row grid-rows-1 lg:grid-cols-5 gap-x-10 gap-y-4' }, @@ -523,7 +523,7 @@ export async function decorateProductList(block) { block.removeChild(productSkeleton); return; } - if (res.totalCount > 0) buildProductCategorySchema(res.results); + if (res.totalCount > 0) buildItemListSchema(res.results, 'product-family'); facets(res, facetDiv); resultList(res, categoryDiv); block.removeChild(productSkeleton); diff --git a/blocks/side-nav/side-nav.js b/blocks/side-nav/side-nav.js index e9f799d8..e3e91a77 100644 --- a/blocks/side-nav/side-nav.js +++ b/blocks/side-nav/side-nav.js @@ -3,6 +3,7 @@ import { } from '../../scripts/dom-builder.js'; import ffetch from '../../scripts/ffetch.js'; import { getMetadata } from '../../scripts/lib-franklin.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; import { makePublicUrl } from '../../scripts/scripts.js'; import { fetchTopicsForCategory } from '../topic-list/topic-list.js'; @@ -60,6 +61,7 @@ export default async function decorate(block) { sideNavTitle = solutionObj?.title; sideNavItems = await ffetch('/us/en/solutions-index.json') .filter(({ solution }) => solution === solutionType).all(); + buildItemListSchema(sideNavItems, 'individual-steps'); } sideNavElements = renderSideNav(sideNavItems); selectedNavItem = sideNavElements.querySelector(`.side-nav-item a[href="${window.location.pathname}"]`)?.closest('.side-nav-item'); diff --git a/blocks/timeline/timeline.js b/blocks/timeline/timeline.js index d77a6b68..1666b7cd 100644 --- a/blocks/timeline/timeline.js +++ b/blocks/timeline/timeline.js @@ -6,6 +6,7 @@ import { label, span, } from '../../scripts/dom-builder.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; function updateMenu(target, block) { const clickedMenu = target.closest('.menu-item'); @@ -98,6 +99,7 @@ export default function decorate(block) { if (type !== 'menu') { block.classList.add(...'w-full h-full top-14 bottom-0'.split(' ')); const items = block.children; + buildItemListSchema([...block.children], 'process-steps'); [...items].forEach((item, idx) => { const picture = item.querySelector('div:last-child > p > picture'); const timeline = (idx % 2 === 0) diff --git a/blocks/workflow-tabs/workflow-tabs.js b/blocks/workflow-tabs/workflow-tabs.js index 62f912a1..a7b90c53 100644 --- a/blocks/workflow-tabs/workflow-tabs.js +++ b/blocks/workflow-tabs/workflow-tabs.js @@ -1,7 +1,8 @@ import { div, li, ul, a, span, } from '../../scripts/dom-builder.js'; -import { processEmbedFragment } from '../../scripts/scripts.js'; +import { processEmbedFragment, getFragmentFromFile } from '../../scripts/scripts.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; const classActive = 'active'; const danaherPurpleClass = 'bg-danaherpurple-500'; @@ -74,6 +75,16 @@ async function buildTabs(block) { const tabPanes = block.querySelectorAll('.workflow-tabs > div > div:last-child'); const tabList = div({ class: 'tabs-list' }); const decoratedPanes = await Promise.all([...tabPanes].map(async (pane, index) => { + // Added for SEO Schema generation + if (pane.textContent?.includes('workflow-carousels/master')) { + const fragment = await getFragmentFromFile(`${pane.textContent}.plain.html`); + const wfCarousel = document.createElement('div'); + wfCarousel.innerHTML = fragment; + const childs = wfCarousel.querySelector('div.workflow-carousel').children; + buildItemListSchema([...childs].splice(1, childs.length), 'workflow'); + } + // End of SEO Schema generation + const isActive = index === 0; pane.classList.add('tab-pane', isActive ? classActive : 'off-screen'); const decoratedPane = await processEmbedFragment(pane); diff --git a/scripts/schema.js b/scripts/schema.js index b506aef5..b05d8a21 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -87,8 +87,26 @@ export function buildProductSchema() { ); } +function generateItemListElement(type, position, url, name, image, description) { + return { + '@type': 'ListItem', + position, + item: { + '@type': type, + '@id': url, + name, + image, + description, + }, + mainEntityOfPage: { + '@type': 'WebPage', + '@id': url, + }, + }; +} + // eslint-disable-next-line import/prefer-default-export -export function buildProductCategorySchema(products) { +export function buildItemListSchema(srcObjs, type) { const data = { '@context': 'http://schema.org', '@type': 'ItemList', @@ -99,39 +117,75 @@ export function buildProductCategorySchema(products) { itemListElement: [], }; - products.forEach((product, index) => { - if (product?.raw) { - data.itemListElement.push({ - '@type': 'ListItem', - position: index + 1, - item: { - '@type': 'Product', - '@id': product.clickUri, - name: product.title, - image: product?.raw?.images?.at(0), - description: product.excerpt, - }, - mainEntityOfPage: { - '@type': 'WebPage', - '@id': product.clickUri, - }, - }); - } else { - data.itemListElement.push({ - '@type': 'ListItem', - position: index + 1, - item: { - '@type': 'Product', - '@id': `https://lifesciences.danaher.com${makePublicUrl(product.path)}`, - name: product.title, - image: `https://lifesciences.danaher.com${product.image}`, - description: product.description, - }, - mainEntityOfPage: { - '@type': 'WebPage', - '@id': `https://lifesciences.danaher.com${makePublicUrl(product.path)}`, - }, - }); + let title; + let position; + let url; + let image; + let description; + + srcObjs.forEach((obj, index) => { + switch (type) { + case 'product-family': + data.itemListElement.push(generateItemListElement( + 'Product', + index + 1, + obj.clickUri, + obj.title, + obj?.raw?.images?.at(0), + obj.excerpt, + )); + break; + case 'product-category': + data.itemListElement.push(generateItemListElement( + 'Product', + index + 1, + `https://lifesciences.danaher.com${makePublicUrl(obj.path)}`, + obj.title, + `https://lifesciences.danaher.com${obj.image}`, + obj.description, + )); + break; + case 'workflow': + title = obj.querySelector('p:nth-child(3)')?.textContent; + position = obj.querySelector('p:nth-child(2) > strong')?.textContent; + url = obj.querySelector('p:nth-child(4) > a')?.href; + image = obj.querySelector('p > picture > img')?.src; + data.itemListElement.push(generateItemListElement( + 'Workflow', + position, + url, + title, + image, + description, + )); + break; + case 'process-steps': + title = obj.querySelector('div:nth-child(2) > h2')?.textContent; + position = obj.querySelector('div:first-child')?.textContent; + url = obj.querySelector('div:nth-child(2) > p > a')?.href; + image = obj.querySelector('div:last-child > p > picture > img')?.src; + description = obj.querySelector('div:nth-child(2) > p:nth-child(3)')?.textContent; + data.itemListElement.push(generateItemListElement( + 'Workflow', + position, + url, + title, + image, + description, + )); + break; + case 'individual-steps': + data.itemListElement.push(generateItemListElement( + 'Workflow', + index + 1, + obj.path, + obj.title, + image, + description, + )); + break; + default: + break; } }); From d0be4a01f5102f3a54f334865ac3ddf2e92de348 Mon Sep 17 00:00:00 2001 From: rgravitvl Date: Tue, 9 Jul 2024 14:42:49 +0530 Subject: [PATCH 2/5] Updaetd for all resources page --- blocks/card-list/card-list.js | 3 ++- scripts/schema.js | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/blocks/card-list/card-list.js b/blocks/card-list/card-list.js index ec5d0fe2..61ea21b5 100644 --- a/blocks/card-list/card-list.js +++ b/blocks/card-list/card-list.js @@ -8,6 +8,7 @@ import createArticleCard from './articleCard.js'; import createLibraryCard from './libraryCard.js'; import createApplicationCard from './applicationCard.js'; import { makePublicUrl } from '../../scripts/scripts.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; const getSelectionFromUrl = () => (window.location.pathname.indexOf('topics') > -1 ? toClassName(window.location.pathname.replace('.html', '').split('/').pop()) : ''); const getPageFromUrl = () => toClassName(new URLSearchParams(window.location.search).get('page')) || ''; @@ -162,7 +163,7 @@ export default async function decorate(block) { (item) => toClassName(item.topics).toLowerCase().indexOf(activeTagFilter) > -1, ); } - + buildItemListSchema(filteredArticles, 'resources'); // render cards application style if (articleType === 'application' || articleType === 'info') { filteredArticles.sort((card1, card2) => card1.title.localeCompare(card2.title)); diff --git a/scripts/schema.js b/scripts/schema.js index b05d8a21..82017686 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -151,9 +151,9 @@ export function buildItemListSchema(srcObjs, type) { url = obj.querySelector('p:nth-child(4) > a')?.href; image = obj.querySelector('p > picture > img')?.src; data.itemListElement.push(generateItemListElement( - 'Workflow', + 'ListItem', position, - url, + makePublicUrl(url), title, image, description, @@ -166,9 +166,9 @@ export function buildItemListSchema(srcObjs, type) { image = obj.querySelector('div:last-child > p > picture > img')?.src; description = obj.querySelector('div:nth-child(2) > p:nth-child(3)')?.textContent; data.itemListElement.push(generateItemListElement( - 'Workflow', + 'ListItem', position, - url, + makePublicUrl(url), title, image, description, @@ -176,14 +176,24 @@ export function buildItemListSchema(srcObjs, type) { break; case 'individual-steps': data.itemListElement.push(generateItemListElement( - 'Workflow', + 'ListItem', index + 1, - obj.path, + makePublicUrl(obj.path), obj.title, image, description, )); break; + case 'resources': + data.itemListElement.push(generateItemListElement( + 'ListItem', + index + 1, + makePublicUrl(obj.path), + obj.title, + obj.image, + obj.description, + )); + break; default: break; } From 2518172b9f2e5633df28bb9da71f0a2ac73b38b7 Mon Sep 17 00:00:00 2001 From: rgravitvl Date: Thu, 11 Jul 2024 10:12:26 +0530 Subject: [PATCH 3/5] Updated schema item list --- scripts/schema.js | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/scripts/schema.js b/scripts/schema.js index 82017686..a45848e5 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -87,7 +87,7 @@ export function buildProductSchema() { ); } -function generateItemListElement(type, position, url, name, image, description) { +function generateProductSchema(type, position, url, name, image, description) { return { '@type': 'ListItem', position, @@ -105,13 +105,35 @@ function generateItemListElement(type, position, url, name, image, description) }; } +function generateItemListElement(type, position, url, name, image, description) { + return { + '@type': 'ListItem', + position, + item: { + '@type': type, + '@id': url, + name, + image, + description, + } + }; +} + +const productType = ['product-family', 'product-category']; + // eslint-disable-next-line import/prefer-default-export -export function buildItemListSchema(srcObjs, type) { +export function buildItemListSchema(srcObj, type) { + let name; + if(productType.includes(type)) name = `${document.querySelector('h1').textContent} - Types`; + else if(type === 'workflow') name = `${document.querySelector('h1').textContent} Process Steps`; + else if(type === 'individual-steps') name = `${document.querySelector('h1').textContent} - Products`; + else name = document.querySelector('h1').textContent; + const data = { '@context': 'http://schema.org', '@type': 'ItemList', '@id': `https://lifesciences.danaher.com${makePublicUrl(window.location.pathname)}`, - name: `${document.querySelector('h1').textContent} - Types`, + name, image: getMetadata('og:image'), description: getMetadata('description'), itemListElement: [], @@ -123,10 +145,10 @@ export function buildItemListSchema(srcObjs, type) { let image; let description; - srcObjs.forEach((obj, index) => { + srcObj.forEach((obj, index) => { switch (type) { case 'product-family': - data.itemListElement.push(generateItemListElement( + data.itemListElement.push(generateProductSchema( 'Product', index + 1, obj.clickUri, @@ -136,7 +158,7 @@ export function buildItemListSchema(srcObjs, type) { )); break; case 'product-category': - data.itemListElement.push(generateItemListElement( + data.itemListElement.push(generateProductSchema( 'Product', index + 1, `https://lifesciences.danaher.com${makePublicUrl(obj.path)}`, @@ -146,8 +168,8 @@ export function buildItemListSchema(srcObjs, type) { )); break; case 'workflow': - title = obj.querySelector('p:nth-child(3)')?.textContent; position = obj.querySelector('p:nth-child(2) > strong')?.textContent; + title = `${name} ${position} - ${obj.querySelector('p:nth-child(3)')?.textContent}`; url = obj.querySelector('p:nth-child(4) > a')?.href; image = obj.querySelector('p > picture > img')?.src; data.itemListElement.push(generateItemListElement( @@ -160,8 +182,8 @@ export function buildItemListSchema(srcObjs, type) { )); break; case 'process-steps': - title = obj.querySelector('div:nth-child(2) > h2')?.textContent; position = obj.querySelector('div:first-child')?.textContent; + title = `${name} ${position} - ${obj.querySelector('div:nth-child(2) > h2')?.textContent}`; url = obj.querySelector('div:nth-child(2) > p > a')?.href; image = obj.querySelector('div:last-child > p > picture > img')?.src; description = obj.querySelector('div:nth-child(2) > p:nth-child(3)')?.textContent; @@ -201,6 +223,6 @@ export function buildItemListSchema(srcObjs, type) { setJsonLd( data, - 'productItemList', + 'itemList', ); } From b806d83e2d81949fdca3ada9236da1d4bbcb24e2 Mon Sep 17 00:00:00 2001 From: rgravitvl Date: Mon, 15 Jul 2024 10:58:05 +0530 Subject: [PATCH 4/5] Updated the schema changes --- blocks/product-card/product-card.js | 5 +++++ blocks/side-nav/side-nav.js | 1 - scripts/schema.js | 17 +++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/blocks/product-card/product-card.js b/blocks/product-card/product-card.js index 0f87c873..2466ce97 100644 --- a/blocks/product-card/product-card.js +++ b/blocks/product-card/product-card.js @@ -2,6 +2,7 @@ import { getProductsOnSolutionsResponse, onClickCoveoAnalyticsResponse } from '. import { ul, a, p, div, span, h4, li, } from '../../scripts/dom-builder.js'; +import { buildItemListSchema } from '../../scripts/schema.js'; import { makePublicUrl, imageHelper } from '../../scripts/scripts.js'; export function createCard(product, idx, firstCard = false) { @@ -29,6 +30,10 @@ export function createCard(product, idx, firstCard = false) { export default async function decorate(block) { const response = await getProductsOnSolutionsResponse(); if (response?.results.length > 0) { + + if(window.location.pathname.includes('process-steps')) buildItemListSchema(response?.results, 'solution-products-steps'); + else buildItemListSchema(response?.results, 'solution-products'); + const cardList = ul({ class: 'container grid max-w-7xl w-full mx-auto gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 px-4 pt-8 sm:px-0 justify-items-center mt-3 mb-3', }); diff --git a/blocks/side-nav/side-nav.js b/blocks/side-nav/side-nav.js index e3e91a77..42dc7e55 100644 --- a/blocks/side-nav/side-nav.js +++ b/blocks/side-nav/side-nav.js @@ -61,7 +61,6 @@ export default async function decorate(block) { sideNavTitle = solutionObj?.title; sideNavItems = await ffetch('/us/en/solutions-index.json') .filter(({ solution }) => solution === solutionType).all(); - buildItemListSchema(sideNavItems, 'individual-steps'); } sideNavElements = renderSideNav(sideNavItems); selectedNavItem = sideNavElements.querySelector(`.side-nav-item a[href="${window.location.pathname}"]`)?.closest('.side-nav-item'); diff --git a/scripts/schema.js b/scripts/schema.js index a45848e5..3b5c6229 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -126,7 +126,7 @@ export function buildItemListSchema(srcObj, type) { let name; if(productType.includes(type)) name = `${document.querySelector('h1').textContent} - Types`; else if(type === 'workflow') name = `${document.querySelector('h1').textContent} Process Steps`; - else if(type === 'individual-steps') name = `${document.querySelector('h1').textContent} - Products`; + else if(type === 'solution-products-steps') name = `${document.querySelector('h1').textContent} - Products`; else name = document.querySelector('h1').textContent; const data = { @@ -169,7 +169,7 @@ export function buildItemListSchema(srcObj, type) { break; case 'workflow': position = obj.querySelector('p:nth-child(2) > strong')?.textContent; - title = `${name} ${position} - ${obj.querySelector('p:nth-child(3)')?.textContent}`; + title = `${name} - ${obj.querySelector('p:nth-child(3)')?.textContent}`; url = obj.querySelector('p:nth-child(4) > a')?.href; image = obj.querySelector('p > picture > img')?.src; data.itemListElement.push(generateItemListElement( @@ -183,7 +183,7 @@ export function buildItemListSchema(srcObj, type) { break; case 'process-steps': position = obj.querySelector('div:first-child')?.textContent; - title = `${name} ${position} - ${obj.querySelector('div:nth-child(2) > h2')?.textContent}`; + title = `${name} - ${obj.querySelector('div:nth-child(2) > h2')?.textContent}`; url = obj.querySelector('div:nth-child(2) > p > a')?.href; image = obj.querySelector('div:last-child > p > picture > img')?.src; description = obj.querySelector('div:nth-child(2) > p:nth-child(3)')?.textContent; @@ -196,14 +196,15 @@ export function buildItemListSchema(srcObj, type) { description, )); break; - case 'individual-steps': + case 'solution-products-steps': + case 'solution-products': data.itemListElement.push(generateItemListElement( - 'ListItem', + 'Product', index + 1, - makePublicUrl(obj.path), + obj.clickUri, obj.title, - image, - description, + obj?.raw?.images?.at(0), + obj.excerpt, )); break; case 'resources': From 154adade42b899eef19b5110cf98aba164955914 Mon Sep 17 00:00:00 2001 From: rgravitvl Date: Mon, 15 Jul 2024 10:59:33 +0530 Subject: [PATCH 5/5] Fixed lint issues --- blocks/product-card/product-card.js | 3 +-- blocks/side-nav/side-nav.js | 1 - scripts/schema.js | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/blocks/product-card/product-card.js b/blocks/product-card/product-card.js index 2466ce97..87166b60 100644 --- a/blocks/product-card/product-card.js +++ b/blocks/product-card/product-card.js @@ -30,8 +30,7 @@ export function createCard(product, idx, firstCard = false) { export default async function decorate(block) { const response = await getProductsOnSolutionsResponse(); if (response?.results.length > 0) { - - if(window.location.pathname.includes('process-steps')) buildItemListSchema(response?.results, 'solution-products-steps'); + if (window.location.pathname.includes('process-steps')) buildItemListSchema(response?.results, 'solution-products-steps'); else buildItemListSchema(response?.results, 'solution-products'); const cardList = ul({ diff --git a/blocks/side-nav/side-nav.js b/blocks/side-nav/side-nav.js index 42dc7e55..e9f799d8 100644 --- a/blocks/side-nav/side-nav.js +++ b/blocks/side-nav/side-nav.js @@ -3,7 +3,6 @@ import { } from '../../scripts/dom-builder.js'; import ffetch from '../../scripts/ffetch.js'; import { getMetadata } from '../../scripts/lib-franklin.js'; -import { buildItemListSchema } from '../../scripts/schema.js'; import { makePublicUrl } from '../../scripts/scripts.js'; import { fetchTopicsForCategory } from '../topic-list/topic-list.js'; diff --git a/scripts/schema.js b/scripts/schema.js index 3b5c6229..c1a1c376 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -115,7 +115,7 @@ function generateItemListElement(type, position, url, name, image, description) name, image, description, - } + }, }; } @@ -124,9 +124,9 @@ const productType = ['product-family', 'product-category']; // eslint-disable-next-line import/prefer-default-export export function buildItemListSchema(srcObj, type) { let name; - if(productType.includes(type)) name = `${document.querySelector('h1').textContent} - Types`; - else if(type === 'workflow') name = `${document.querySelector('h1').textContent} Process Steps`; - else if(type === 'solution-products-steps') name = `${document.querySelector('h1').textContent} - Products`; + if (productType.includes(type)) name = `${document.querySelector('h1').textContent} - Types`; + else if (type === 'workflow') name = `${document.querySelector('h1').textContent} Process Steps`; + else if (type === 'solution-products-steps') name = `${document.querySelector('h1').textContent} - Products`; else name = document.querySelector('h1').textContent; const data = {