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..394ec5a9b7 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..b8048c09d9 100644 --- a/libs/blocks/library-config/library-utils.js +++ b/libs/blocks/library-config/library-utils.js @@ -1,4 +1,24 @@ -/* global ClipboardItem */ +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())); +} + export default function createCopy(blob) { const data = [new ClipboardItem({ [blob.type]: blob })]; navigator.clipboard.write(data); 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; 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; }); });