Skip to content

Commit

Permalink
Merge pull request hlxsites#1205 from hlxsites/1204-itemlist-schema-p…
Browse files Browse the repository at this point in the history
…ages

[SEO] 1204 Item list schema for Workflows, Blogs and News pages
  • Loading branch information
rgravitvl authored Jul 15, 2024
2 parents b679171 + 154adad commit 7435812
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 42 deletions.
3 changes: 2 additions & 1 deletion blocks/card-list/card-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')) || '';
Expand Down Expand Up @@ -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));
Expand Down
4 changes: 4 additions & 0 deletions blocks/product-card/product-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -29,6 +30,9 @@ 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',
});
Expand Down
4 changes: 2 additions & 2 deletions blocks/product-category/product-category.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions blocks/product-family/product-family.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions blocks/timeline/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 12 additions & 1 deletion blocks/workflow-tabs/workflow-tabs.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down
159 changes: 123 additions & 36 deletions scripts/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,56 +87,143 @@ export function buildProductSchema() {
);
}

function generateProductSchema(type, position, url, name, image, description) {
return {
'@type': 'ListItem',
position,
item: {
'@type': type,
'@id': url,
name,
image,
description,
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url,
},
};
}

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 buildProductCategorySchema(products) {
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`;
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: [],
};

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;

srcObj.forEach((obj, index) => {
switch (type) {
case 'product-family':
data.itemListElement.push(generateProductSchema(
'Product',
index + 1,
obj.clickUri,
obj.title,
obj?.raw?.images?.at(0),
obj.excerpt,
));
break;
case 'product-category':
data.itemListElement.push(generateProductSchema(
'Product',
index + 1,
`https://lifesciences.danaher.com${makePublicUrl(obj.path)}`,
obj.title,
`https://lifesciences.danaher.com${obj.image}`,
obj.description,
));
break;
case 'workflow':
position = obj.querySelector('p:nth-child(2) > strong')?.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(
'ListItem',
position,
makePublicUrl(url),
title,
image,
description,
));
break;
case 'process-steps':
position = obj.querySelector('div:first-child')?.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;
data.itemListElement.push(generateItemListElement(
'ListItem',
position,
makePublicUrl(url),
title,
image,
description,
));
break;
case 'solution-products-steps':
case 'solution-products':
data.itemListElement.push(generateItemListElement(
'Product',
index + 1,
obj.clickUri,
obj.title,
obj?.raw?.images?.at(0),
obj.excerpt,
));
break;
case 'resources':
data.itemListElement.push(generateItemListElement(
'ListItem',
index + 1,
makePublicUrl(obj.path),
obj.title,
obj.image,
obj.description,
));
break;
default:
break;
}
});

setJsonLd(
data,
'productItemList',
'itemList',
);
}

0 comments on commit 7435812

Please sign in to comment.