From b4940932dbdb3e0edc546647fde3b6a2e79ea5f3 Mon Sep 17 00:00:00 2001 From: Ryan Clayton Date: Fri, 6 Sep 2024 16:23:09 -0600 Subject: [PATCH 1/4] MWPW-154059 - Milo templates library * Allows template-metadata block to output metadata block in template * Fixes inital listing order * Adds search ability to templates Resolves: MWPW-154059 --- libs/blocks/library-config/library-config.css | 6 ++- libs/blocks/library-config/library-config.js | 45 ++++++++++++---- libs/blocks/library-config/library-utils.js | 21 ++++++++ libs/blocks/library-config/lists/blocks.js | 14 ++--- libs/blocks/library-config/lists/templates.js | 51 +++++++++++++++++-- .../library-metadata/library-metadata.css | 1 + 6 files changed, 110 insertions(+), 28 deletions(-) diff --git a/libs/blocks/library-config/library-config.css b/libs/blocks/library-config/library-config.css index 1f7b6df019..121f79c95a 100644 --- a/libs/blocks/library-config/library-config.css +++ b/libs/blocks/library-config/library-config.css @@ -205,7 +205,8 @@ input.sk-library-search-input:focus { * Fixes block list getting cut off with search * Margin height equal to search bar height */ -.sk-library ul.con-blocks-list.inset { +.sk-library ul.con-blocks-list.inset, +.sk-library ul.con-templates-list.inset { margin-bottom: 39px; } @@ -242,7 +243,8 @@ input.sk-library-search-input:focus { font-weight: 700; } -.sk-library .block-group.is-hidden { +.sk-library .block-group.is-hidden, +.con-templates-list .template.is-hidden { display: none; } diff --git a/libs/blocks/library-config/library-config.js b/libs/blocks/library-config/library-config.js index c7bc4f2026..8ad27730f5 100644 --- a/libs/blocks/library-config/library-config.js +++ b/libs/blocks/library-config/library-config.js @@ -2,14 +2,14 @@ import { createTag } from '../../utils/utils.js'; const LIBRARY_PATH = '/docs/library/library.json'; -async function loadBlocks(content, list, query) { +async function loadBlocks(content, list, query, type) { const { default: blocks } = await import('./lists/blocks.js'); - blocks(content, list, query); + blocks(content, list, query, type); } -async function loadTemplates(content, list) { +async function loadTemplates(content, list, query, type) { const { default: templates } = await import('./lists/templates.js'); - templates(content, list); + templates(content, list, query, type); } async function loadPlaceholders(content, list) { @@ -32,7 +32,7 @@ async function loadPersonalization(content, list) { personalization(content, list); } -function addSearch(content, list) { +function addSearch(content, list, type) { const skLibrary = list.closest('.sk-library'); const header = skLibrary.querySelector('.sk-library-header'); let search = skLibrary.querySelector('.sk-library-search'); @@ -40,6 +40,7 @@ function addSearch(content, list) { search = createTag('div', { class: 'sk-library-search' }); const searchInput = createTag('input', { class: 'sk-library-search-input', placeholder: 'Search...' }); const clear = createTag('div', { class: 'sk-library-search-clear is-hidden' }); + searchInput.addEventListener('input', (e) => { const query = e.target.value; if (query === '') { @@ -47,12 +48,31 @@ function addSearch(content, list) { } else { clear.classList.remove('is-hidden'); } - loadBlocks(content, list, query); + + switch (type) { + case 'blocks': + loadBlocks(content, list, query, type); + break; + case 'templates': + loadTemplates(content, list, query, type); + break; + default: + } }); clear.addEventListener('click', (e) => { e.target.classList.add('is-hidden'); e.target.closest('.sk-library-search').querySelector('.sk-library-search-input').value = ''; - loadBlocks(content, list); + const query = e.target.value; + + switch (type) { + case 'blocks': + loadBlocks(content, list, query, type); + break; + case 'templates': + loadTemplates(content, list, query, type); + break; + default: + } }); search.append(searchInput); search.append(clear); @@ -67,11 +87,12 @@ async function loadList(type, content, list) { const query = list.closest('.sk-library').querySelector('.sk-library-search-input')?.value; switch (type) { case 'blocks': - addSearch(content, list); - loadBlocks(content, list, query); + addSearch(content, list, type); + loadBlocks(content, list, query, type); break; case 'templates': - loadTemplates(content, list); + addSearch(content, list, type); + loadTemplates(content, list, query, type); break; case 'placeholders': loadPlaceholders(content, list); @@ -198,6 +219,10 @@ function createHeader() { el.classList.remove('inset'); }); skLibrary.classList.remove('allow-back'); + + // Remove library search if it's been added + const search = skLibrary.querySelector('.sk-library-search'); + if (search) search.remove(); }); return header; } diff --git a/libs/blocks/library-config/library-utils.js b/libs/blocks/library-config/library-utils.js index 0143b851d4..50d569dd48 100644 --- a/libs/blocks/library-config/library-utils.js +++ b/libs/blocks/library-config/library-utils.js @@ -1,3 +1,24 @@ +import { getSearchTags } from './lists/blocks.js'; +import { getTemplateSearchTags } from './lists/templates.js'; + +/* search utility */ +export function isMatching(container, query, type, titleText) { + let tagsString; + + switch (type) { + case 'blocks': + tagsString = getSearchTags(container); + break; + case 'templates': + tagsString = getTemplateSearchTags(container, titleText); + break; + default: + } + if (!query || !tagsString) return false; + const searchTokens = query.split(' '); + return searchTokens.every((token) => tagsString.toLowerCase().includes(token.toLowerCase())); +} + /* global ClipboardItem */ export default function createCopy(blob) { const data = [new ClipboardItem({ [blob.type]: blob })]; diff --git a/libs/blocks/library-config/lists/blocks.js b/libs/blocks/library-config/lists/blocks.js index f5f59352f1..69de637170 100644 --- a/libs/blocks/library-config/lists/blocks.js +++ b/libs/blocks/library-config/lists/blocks.js @@ -1,5 +1,5 @@ import { createTag } from '../../../utils/utils.js'; -import createCopy from '../library-utils.js'; +import createCopy, { isMatching } from '../library-utils.js'; import { getMetadata } from '../../section-metadata/section-metadata.js'; const LIBRARY_METADATA = 'library-metadata'; @@ -132,13 +132,6 @@ export function getSearchTags(container) { return containerName; } -export function isMatching(container, query) { - const tagsString = getSearchTags(container); - if (!query || !tagsString) return false; - const searchTokens = query.split(' '); - return searchTokens.every((token) => tagsString.toLowerCase().includes(token.toLowerCase())); -} - function getBlockType(subSection, withinContainer) { if (subSection.className === LIBRARY_CONTAINER_START) return CONTAINER_START_BLOCK; if (subSection.className === LIBRARY_CONTAINER_END) return CONTAINER_END_BLOCK; @@ -247,7 +240,7 @@ export function getContainers(doc) { return containers; } -export default async function loadBlocks(blocks, list, query) { +export default async function loadBlocks(blocks, list, query, type) { list.textContent = ''; blocks.forEach(async (block) => { const titleText = createTag('p', { class: 'item-title' }, block.name); @@ -277,7 +270,6 @@ export default async function loadBlocks(blocks, list, query) { const html = await resp.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); - const containers = getContainers(doc); let matchingContainerFound = false; @@ -298,7 +290,7 @@ export default async function loadBlocks(blocks, list, query) { item.append(name, copy); if (query) { - if (isMatching(container, query)) { + if (isMatching(container, query, type)) { matchingContainerFound = true; } else { item.classList.add('is-hidden'); diff --git a/libs/blocks/library-config/lists/templates.js b/libs/blocks/library-config/lists/templates.js index 2c1f780078..b012df4c5d 100644 --- a/libs/blocks/library-config/lists/templates.js +++ b/libs/blocks/library-config/lists/templates.js @@ -1,5 +1,6 @@ import { createTag } from '../../../utils/utils.js'; -import createCopy from '../library-utils.js'; +import createCopy, { isMatching } from '../library-utils.js'; +import { getMetadata } from '../../section-metadata/section-metadata.js'; import { getTable, decorateImages, handleLinks } from './blocks.js'; function createSpace() { @@ -7,6 +8,16 @@ function createSpace() { return createTag('p', null, br); } +export function getTemplateSearchTags(template, titleText) { + const templateName = titleText.textContent; + + if (template.searchtags?.text) { + const terms = template.searchtags?.text.trim().toLowerCase(); + return `${terms} ${templateName}`; + } + return templateName; +} + function formatDom(aemDom, path) { // Decorate Links handleLinks(aemDom, path); @@ -16,11 +27,27 @@ function formatDom(aemDom, path) { // Decorate Blocks const divs = aemDom.querySelectorAll('main > div > div'); + const template = {}; + divs.forEach((div) => { + // If there is library-metadata, extract searchTags. Remove library-metadata. + if (div.classList.contains('library-metadata')) { + const libraryMetadata = getMetadata(div); + template.searchtags = libraryMetadata.searchtags; + div.remove(); + return; + } // Give table some space div.insertAdjacentElement('afterend', createSpace()); const table = getTable(div, true); + const th = table.querySelector('th'); + + // Converts to a metadata block so it can be copied/pasted. + if (th.textContent === 'template-metadata') { + th.textContent = 'metadata'; + } + div.parentElement.replaceChild(table, div); }); @@ -38,7 +65,8 @@ function formatDom(aemDom, path) { }); const flattedDom = createTag('div'); flattedDom.append(...formattedSections); - return flattedDom; + template.flattedDom = flattedDom; + return template; } async function formatTemplate(path) { @@ -50,13 +78,14 @@ async function formatTemplate(path) { return formatDom(dom, path); } -export default async function loadTemplates(templates, list) { +export default async function loadTemplates(templates, list, query, type) { + list.textContent = ''; + templates.forEach(async (template) => { const titleText = createTag('p', { class: 'item-title' }, template.name); const title = createTag('li', { class: 'template' }, titleText); const previewButton = createTag('button', { class: 'preview-group' }, 'Preview'); const copy = createTag('button', { class: 'copy' }); - const formatted = await formatTemplate(template.path); list.append(title); title.append(previewButton, copy); @@ -66,10 +95,22 @@ export default async function loadTemplates(templates, list) { window.open(template.path, '_templatepreview'); }); + // Returns an object with flattedDom and searchtags. + const formatted = await formatTemplate(template.path); + if (query) { + if (isMatching(formatted, query, type, titleText)) { + title.classList.remove('is-hidden'); + } else { + title.classList.add('is-hidden'); + } + } else { + title.classList.remove('is-hidden'); + } + copy.addEventListener('click', (e) => { e.target.classList.add('copied'); setTimeout(() => { e.target.classList.remove('copied'); }, 3000); - const blob = new Blob([formatted.outerHTML], { type: 'text/html' }); + const blob = new Blob([formatted.flattedDom.outerHTML], { type: 'text/html' }); createCopy(blob); }); }); diff --git a/libs/blocks/library-metadata/library-metadata.css b/libs/blocks/library-metadata/library-metadata.css index ce00217e59..4a9c2589b2 100644 --- a/libs/blocks/library-metadata/library-metadata.css +++ b/libs/blocks/library-metadata/library-metadata.css @@ -18,6 +18,7 @@ .library-meta-row { background-color: #EFEFEF; + color: initial; display: grid; grid-template-columns: 1fr 1fr; margin-top: 4px; From 9159659cc6089f1334adb0e4b4c639001339a3ea Mon Sep 17 00:00:00 2001 From: Ryan Clayton Date: Tue, 10 Sep 2024 16:18:00 -0600 Subject: [PATCH 2/4] Updating unit tests --- .../library-config/library-config.test.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/blocks/library-config/library-config.test.js b/test/blocks/library-config/library-config.test.js index 3478cfc4d7..4de9ad2ebf 100644 --- a/test/blocks/library-config/library-config.test.js +++ b/test/blocks/library-config/library-config.test.js @@ -1,7 +1,8 @@ import { readFile } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; -const { getContainers, getSearchTags, isMatching, getHtml } = await import('../../../libs/blocks/library-config/lists/blocks.js'); +const { getContainers, getSearchTags, getHtml } = await import('../../../libs/blocks/library-config/lists/blocks.js'); +const { isMatching } = await import('../../../libs/blocks/library-config/library-utils.js'); const BLOCK_PAGE_URL = 'https://main--milo--adobecom.hlx.page/path/to/block/page'; function verifyContainer(container, elementsLength, hasLibraryMetadata) { @@ -35,8 +36,8 @@ describe('Library Config: text', () => { const searchTags = getSearchTags(containers[0]); expect(searchTags).to.equal('tb-2up-gr10 tb-3up-gr12 text'); // verify isMatching() - expect(isMatching(containers[0], 'tb-2up-gr10')).to.be.true; - expect(isMatching(containers[0], 'non-existing')).to.be.false; + expect(isMatching(containers[0], 'tb-2up-gr10', 'blocks')).to.be.true; + expect(isMatching(containers[0], 'non-existing', 'blocks')).to.be.false; }); }); @@ -62,8 +63,8 @@ describe('Library Config: chart', () => { const searchTags = getSearchTags(containers[0]); expect(searchTags).to.equal('chart-0 chart (area, green, border)'); // verify isMatching() - expect(isMatching(containers[0], 'chart-0')).to.be.true; - expect(isMatching(containers[0], 'non-existing')).to.be.false; + expect(isMatching(containers[0], 'chart-0', 'blocks')).to.be.true; + expect(isMatching(containers[0], 'non-existing', 'blocks')).to.be.false; }); }); @@ -89,8 +90,8 @@ describe('Library Config: marquee', () => { const searchTags = getSearchTags(containers[0]); expect(searchTags).to.equal('mq-std-md-lt mq-std-md-rt mq-std-md-lt-vid marquee-dark marquee'); // verify isMatching() - expect(isMatching(containers[0], 'mq-std-md-lt')).to.be.true; - expect(isMatching(containers[0], 'non-existing')).to.be.false; + expect(isMatching(containers[0], 'mq-std-md-lt', 'blocks')).to.be.true; + expect(isMatching(containers[0], 'non-existing', 'blocks')).to.be.false; }); }); @@ -168,10 +169,10 @@ describe('Library Config: containers', () => { it('isMatching', async () => { document.body.innerHTML = mixedHtml; const containers = getContainers(document); - expect(isMatching(containers[0], 'tag1')).to.be.false; - expect(isMatching(containers[1], 'tag1')).to.be.true; - expect(isMatching(containers[2], 'tag2')).to.be.true; - expect(isMatching(containers[3], 'tag3')).to.be.true; - expect(isMatching(containers[4], 'tag4')).to.be.true; + expect(isMatching(containers[0], 'tag1', 'blocks')).to.be.false; + expect(isMatching(containers[1], 'tag1', 'blocks')).to.be.true; + expect(isMatching(containers[2], 'tag2', 'blocks')).to.be.true; + expect(isMatching(containers[3], 'tag3', 'blocks')).to.be.true; + expect(isMatching(containers[4], 'tag4', 'blocks')).to.be.true; }); }); From c08c58b425ffd4574d1a8eecb591460c3ac3bce4 Mon Sep 17 00:00:00 2001 From: Ryan Clayton Date: Wed, 11 Sep 2024 16:17:14 -0600 Subject: [PATCH 3/4] Addressing eslint issue --- libs/blocks/library-config/library-utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/blocks/library-config/library-utils.js b/libs/blocks/library-config/library-utils.js index 50d569dd48..b8048c09d9 100644 --- a/libs/blocks/library-config/library-utils.js +++ b/libs/blocks/library-config/library-utils.js @@ -19,7 +19,6 @@ export function isMatching(container, query, type, titleText) { return searchTokens.every((token) => tagsString.toLowerCase().includes(token.toLowerCase())); } -/* global ClipboardItem */ export default function createCopy(blob) { const data = [new ClipboardItem({ [blob.type]: blob })]; navigator.clipboard.write(data); From 4360a426395a2832b393ac1df612fb0c4b36980b Mon Sep 17 00:00:00 2001 From: Ryan Clayton Date: Wed, 18 Sep 2024 18:01:49 -0600 Subject: [PATCH 4/4] Address PR feedback --- libs/blocks/library-config/library-config.js | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/blocks/library-config/library-config.js b/libs/blocks/library-config/library-config.js index 8ad27730f5..394ec5a9b7 100644 --- a/libs/blocks/library-config/library-config.js +++ b/libs/blocks/library-config/library-config.js @@ -2,12 +2,12 @@ import { createTag } from '../../utils/utils.js'; const LIBRARY_PATH = '/docs/library/library.json'; -async function loadBlocks(content, list, query, type) { +async function loadBlocks({ content, list, query, type }) { const { default: blocks } = await import('./lists/blocks.js'); blocks(content, list, query, type); } -async function loadTemplates(content, list, query, type) { +async function loadTemplates({ content, list, query, type }) { const { default: templates } = await import('./lists/templates.js'); templates(content, list, query, type); } @@ -32,7 +32,7 @@ async function loadPersonalization(content, list) { personalization(content, list); } -function addSearch(content, list, type) { +function addSearch({ content, list, type }) { const skLibrary = list.closest('.sk-library'); const header = skLibrary.querySelector('.sk-library-header'); let search = skLibrary.querySelector('.sk-library-search'); @@ -51,10 +51,10 @@ function addSearch(content, list, type) { switch (type) { case 'blocks': - loadBlocks(content, list, query, type); + loadBlocks({ content, list, query, type }); break; case 'templates': - loadTemplates(content, list, query, type); + loadTemplates({ content, list, query, type }); break; default: } @@ -66,10 +66,10 @@ function addSearch(content, list, type) { switch (type) { case 'blocks': - loadBlocks(content, list, query, type); + loadBlocks({ content, list, query, type }); break; case 'templates': - loadTemplates(content, list, query, type); + loadTemplates({ content, list, query, type }); break; default: } @@ -87,12 +87,12 @@ async function loadList(type, content, list) { const query = list.closest('.sk-library').querySelector('.sk-library-search-input')?.value; switch (type) { case 'blocks': - addSearch(content, list, type); - loadBlocks(content, list, query, type); + addSearch({ content, list, type }); + loadBlocks({ content, list, query, type }); break; case 'templates': - addSearch(content, list, type); - loadTemplates(content, list, query, type); + addSearch({ content, list, type }); + loadTemplates({ content, list, query, type }); break; case 'placeholders': loadPlaceholders(content, list);