From 4a0e8009396dfb9d681a446f5450a69e9287eaa2 Mon Sep 17 00:00:00 2001 From: bdeffleyfamous <38252554+bdeffleyfamous@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:46:10 -0400 Subject: [PATCH] add jsonld data (#49) * add jsonld data * lint changes * lint changes * lint changes * update jsonld to use article --- cigars-for-beginners/blocks/footer/footer.js | 87 +++++++++++++++++++- cigars-for-beginners/scripts/linking-data.js | 12 +++ cigars-for-beginners/scripts/scripts.js | 53 ++++++++++++ head.html | 11 +++ helix-query.yaml | 5 +- 5 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 cigars-for-beginners/scripts/linking-data.js diff --git a/cigars-for-beginners/blocks/footer/footer.js b/cigars-for-beginners/blocks/footer/footer.js index fab8e31..91f9e3b 100644 --- a/cigars-for-beginners/blocks/footer/footer.js +++ b/cigars-for-beginners/blocks/footer/footer.js @@ -1,6 +1,88 @@ import { getMetadata, createOptimizedPicture } from '../../scripts/aem.js'; import { loadFragment } from '../fragment/fragment.js'; -import { isInternal } from '../../scripts/scripts.js'; +import { isInternal, fetchArticleInfo } from '../../scripts/scripts.js'; +import { addLdJsonScript } from '../../scripts/linking-data.js'; + +function dateToISOString(input) { + let date; + + try { + // Check if the input is a number (Unix timestamp) + if (typeof input === 'number') { + date = new Date(input * 1000); + } else if (typeof input === 'string') { + // Check if the string is a Unix timestamp + if (/^\d+$/.test(input)) { + date = new Date(parseInt(input, 10) * 1000); + } else { + // Otherwise, assume it's a date string + date = new Date(input); + } + } else { + return null; // Return null if the input is neither a number nor a string + } + + // Check if the date is valid + if (date.isNaN) { + return null; + } + // Convert the Date object to ISO string format + return date.toISOString(); + } catch (error) { + // Return null if there is an error + return null; + } +} + +async function buildLdJson(container) { + // Base page LD+JSON + const ldJson = { + '@context': 'https://schema.org', + '@type': 'Article', + '@id': window.location.href, + url: window.location.href, + headline: getMetadata('og:title'), + description: getMetadata('description'), + author: { + '@type': 'Organization', + '@id': 'https://www.famous-smoke.com', + name: 'Famous Smoke Shop', + url: 'https://www.famous-smoke.com/', + logo: { + '@type': 'ImageObject', + logo: 'https://www.famous-smoke.com/cigars-for-beginners/icons/logo.png', + width: 147, + height: 62, + }, + }, + potentialAction: { + '@type': 'ReadAction', + target: [ + 'https://www.famous-smoke.com/cigars-for-beginners/', + ], + }, + mainEntityOfPage: { + '@type': 'WebPage', + '@id': 'https://www.famous-smoke.com/cigars-for-beginners/', + }, + inLanguage: 'en-US', + }; + + // Add image from metadata + const primaryImage = getMetadata('og:image'); + if (primaryImage) { + ldJson.image = getMetadata('og:image'); + } + + // Add dateModified + const articleInfo = await fetchArticleInfo(); + if (articleInfo) { + ldJson.dateModified = dateToISOString(articleInfo.lastModified); + ldJson.datePublished = dateToISOString(articleInfo.publishedDate); + } + + addLdJsonScript(container, ldJson); +} // generate the famous logo html function getFamousLogo() { @@ -126,6 +208,9 @@ export default async function decorate(block) { const footer = document.createElement('div'); while (fragment.firstElementChild) footer.append(fragment.firstElementChild); + // add json-ld data for the page + buildLdJson(document.body); + // Add the Famous logo footer.prepend(getFamousLogo()); diff --git a/cigars-for-beginners/scripts/linking-data.js b/cigars-for-beginners/scripts/linking-data.js new file mode 100644 index 0000000..a0dd6b7 --- /dev/null +++ b/cigars-for-beginners/scripts/linking-data.js @@ -0,0 +1,12 @@ +/** + * Writes a script element with the LD JSON struct to the page + * @param {HTMLElement} parent + * @param {Object} json + */ +// eslint-disable-next-line import/prefer-default-export +export function addLdJsonScript(parent, json) { + const script = document.createElement('script'); + script.type = 'application/ld+json'; + script.innerHTML = JSON.stringify(json); + parent.append(script); +} diff --git a/cigars-for-beginners/scripts/scripts.js b/cigars-for-beginners/scripts/scripts.js index b43f018..4d01386 100644 --- a/cigars-for-beginners/scripts/scripts.js +++ b/cigars-for-beginners/scripts/scripts.js @@ -1,6 +1,59 @@ import { sampleRUM, buildBlock, loadHeader, loadFooter, decorateButtons, decorateIcons, decorateSections, decorateBlocks, decorateTemplateAndTheme, waitForLCP, loadBlocks, loadCSS } from './aem.js'; const LCP_BLOCKS = ['header', 'hero']; // add your LCP blocks to the list +const ARTICLE_INDEX_PATH = '/cigars-for-beginners/index/query-index.json'; + +let articleIndexData; + +/** + * Fetches article list. + * @returns {Promise>} - A promise that resolves to an array of article path objects. + */ +export async function fetchArticleList() { + if (!articleIndexData) { + try { + const resp = await fetch(ARTICLE_INDEX_PATH); + if (resp.ok) { + const jsonData = await resp.json(); + articleIndexData = jsonData.data.map((item) => ({ + path: item.path, + title: item.title, + lastModified: item.lastModified, + publishedDate: item.published, + image: item.image, + })); + } else { + // eslint-disable-next-line no-console + console.error('Failed to fetch article list:', resp.status); + return []; + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error article list:', error); + return []; + } + } + return articleIndexData; +} + +/** + * Fetches article information. + * @returns {Promise>} - A promise that resolves to an array of article path objects. + */ +export async function fetchArticleInfo() { + // Fetch article list + if (!articleIndexData) { + articleIndexData = await fetchArticleList(); + } + + // Get the current URL path + const currentPath = window.location.pathname; + + // Find the article that matches the current URL path + const matchingArticle = articleIndexData.find((article) => article.path === currentPath); + + return matchingArticle || null; +} // Function to check if an element is in the viewport function isInViewport(element) { diff --git a/head.html b/head.html index ef49232..0bc1612 100644 --- a/head.html +++ b/head.html @@ -3,3 +3,14 @@ + \ No newline at end of file diff --git a/helix-query.yaml b/helix-query.yaml index e4b3d4d..a47b6cb 100644 --- a/helix-query.yaml +++ b/helix-query.yaml @@ -29,4 +29,7 @@ indices: value: attribute(el, "content") lastModified: select: none - value: parseTimestamp(headers["last-modified"], "ddd, DD MMM YYYY hh:mm:ss GMT") \ No newline at end of file + value: parseTimestamp(headers["last-modified"], "ddd, DD MMM YYYY hh:mm:ss GMT") + published: + select: head > meta[name="publisheddate"] + value: parseTimestamp(attribute(el, "content"), "ddd, DD MMM YYYY hh:mm:ss GMT") \ No newline at end of file