diff --git a/scripts/scripts.js b/scripts/scripts.js index ee7e4e8..c342de4 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { setLibs } from './utils.js'; +import { setLibs, buildAutoBlocks } from './utils.js'; // Add project-wide style path here. const STYLES = '/styles/styles.css'; @@ -109,6 +109,7 @@ const CONFIG = { // geoRouting: 'on', productionDomain: 'business.adobe.com', contentRoot: '/blog', + taxonomyRoot: '/tags', }; // Load LCP image immediately @@ -140,5 +141,6 @@ const miloLibs = setLibs(LIBS); const { loadArea, setConfig } = await import(`${miloLibs}/utils/utils.js`); setConfig({ ...CONFIG, miloLibs }); + await buildAutoBlocks(); await loadArea(); }()); diff --git a/scripts/utils.js b/scripts/utils.js index 9abfcc8..c719153 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -34,3 +34,67 @@ export const [setLibs, getLibs] = (() => { * Edit above at your own risk. * ------------------------------------------------------------ */ + +/** + * Builds a block DOM Element from a two dimensional array + * @param {string} blockName name of the block + * @param {any} content two dimensional array or string or object of content + */ +function buildBlock(blockName, content) { + const table = Array.isArray(content) ? content : [[content]]; + const blockEl = document.createElement('div'); + + blockEl.classList.add(blockName); + table.forEach((row) => { + const rowEl = document.createElement('div'); + row.forEach((col) => { + const colEl = document.createElement('div'); + const vals = col.elems || [col]; + vals.forEach((val) => { + if (val) { + if (typeof val === 'string') { + colEl.innerHTML += val; + } else { + colEl.appendChild(val); + } + } + }); + rowEl.appendChild(colEl); + }); + blockEl.appendChild(rowEl); + }); + return (blockEl); +} + +function buildTagsBlock() { + const metadata = document.head.querySelectorAll('meta[property="article:tag"]'); + if (!metadata.length) return; + const tagsArray = [...metadata].map((el) => el.content); + const tagsBlock = buildBlock('tags', tagsArray.join(', ')); + const main = document.querySelector('main'); + const recBlock = main.querySelector('.recommended-articles'); + if (recBlock) { + // Put tags block before recommended articles block + if (recBlock.parentElement.childElementCount === 1) { + recBlock.parentElement.previousElementSibling.append(tagsBlock); + } else { + recBlock.before(tagsBlock); + } + } else { + main.lastElementChild.append(tagsBlock); + } +} + +export async function buildAutoBlocks() { + const miloLibs = getLibs(); + const { getMetadata } = await import(`${miloLibs}/utils/utils.js`); + const mainEl = document.querySelector('main'); + try { + if (getMetadata('publication-date') && !mainEl.querySelector('.article-header')) { + buildTagsBlock(mainEl); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Auto Blocking failed', error); + } +} diff --git a/styles/styles.css b/styles/styles.css index a3c0659..e2226e5 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -98,7 +98,8 @@ main p picture img { font-size: var(--body-font-size-l); } - main .section .content { + main .section .content, + main .section .tags { max-width: var(--body-max-width); margin-left: auto; margin-right: auto; diff --git a/test/scripts/mocks/tagsBody.html b/test/scripts/mocks/tagsBody.html new file mode 100644 index 0000000..0e6c213 --- /dev/null +++ b/test/scripts/mocks/tagsBody.html @@ -0,0 +1,6 @@ +
+
+

Connecting external data sources to Adobe Experience Manager Guides is now a breeze

+

Rohit Bansal is a principal product marketing manager at Adobe, leading global go-to-market strategy for Adobe Experience Manager Guides. With over 14 years of experience, Rohit has led marketing for product and services firms in the B2B domain — and managed key functions like product marketing, digital marketing, thought leadership, demand generation, and partner relations. A passionate data-driven marketer, he is also a big advocate of the customer experience.

+
+
diff --git a/test/scripts/mocks/tagsHead.html b/test/scripts/mocks/tagsHead.html new file mode 100644 index 0000000..f869b4e --- /dev/null +++ b/test/scripts/mocks/tagsHead.html @@ -0,0 +1,3 @@ + + + diff --git a/test/scripts/mocks/tagsWithRecBody.html b/test/scripts/mocks/tagsWithRecBody.html new file mode 100644 index 0000000..8eaf8e2 --- /dev/null +++ b/test/scripts/mocks/tagsWithRecBody.html @@ -0,0 +1,15 @@ +
+
+

Customer experience — what it is, why it’s important, and how to deliver it

+

See what Adobe Experience Cloud can do for you. Request a demo today.

+ +
+
diff --git a/test/scripts/mocks/tagsWithRecSectionBody.html b/test/scripts/mocks/tagsWithRecSectionBody.html new file mode 100644 index 0000000..1484d89 --- /dev/null +++ b/test/scripts/mocks/tagsWithRecSectionBody.html @@ -0,0 +1,17 @@ +
+
+

Customer experience — what it is, why it’s important, and how to deliver it

+

See what Adobe Experience Cloud can do for you. Request a demo today.

+
+
+ +
+
diff --git a/test/scripts/utils.test.js b/test/scripts/utils.test.js index ccc0ac9..22d31de 100644 --- a/test/scripts/utils.test.js +++ b/test/scripts/utils.test.js @@ -1,5 +1,7 @@ import { expect } from '@esm-bundle/chai'; -import { setLibs } from '../../scripts/utils.js'; +import { readFile } from '@web/test-runner-commands'; +import sinon from 'sinon'; +import { setLibs, buildAutoBlocks } from '../../scripts/utils.js'; describe('Libs', () => { it('Default Libs', () => { @@ -43,3 +45,47 @@ describe('Libs', () => { expect(libs).to.equal('https://awesome--milo--forkedowner.hlx.live/libs'); }); }); + +const metadata = await readFile({ path: './mocks/tagsHead.html' }); + +describe('Auto Blocks', () => { + before(() => { + setLibs('/libs'); + }); + + beforeEach(() => { + sinon.stub(console, 'error'); + }); + + afterEach(() => { + console.error.restore(); + }); + + it('catches errors', async () => { + document.head.innerHTML = metadata; + document.body.innerHTML = ''; + await buildAutoBlocks(); + expect(console.error.calledWith('Auto Blocking failed')).to.be.true; + }); + + it('builds the tags block', async () => { + document.head.innerHTML = metadata; + document.body.innerHTML = await readFile({ path: './mocks/tagsBody.html' }); + await buildAutoBlocks(); + expect(document.querySelector('.tags')).to.exist; + }); + + it('inserts the tags block before recommended articles if present', async () => { + document.head.innerHTML = metadata; + document.body.innerHTML = await readFile({ path: './mocks/tagsWithRecBody.html' }); + await buildAutoBlocks(); + expect(document.querySelector('.tags + .recommended-articles')).to.exist; + }); + + it('inserts the tags block in section before recommended articles if present', async () => { + document.head.innerHTML = metadata; + document.body.innerHTML = await readFile({ path: './mocks/tagsWithRecSectionBody.html' }); + await buildAutoBlocks(); + expect(document.querySelector('.before-rec .tags')).to.exist; + }); +});