Skip to content

Commit

Permalink
MWPW-154059 - Milo templates library (#2855)
Browse files Browse the repository at this point in the history
* 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

* Updating unit tests

* Addressing eslint issue

* Address PR feedback

---------

Co-authored-by: Ryan Clayton <rclayton@adobe.com>
  • Loading branch information
rgclayton and Ryan Clayton authored Sep 19, 2024
1 parent c80cf18 commit 549c71f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 41 deletions.
6 changes: 4 additions & 2 deletions libs/blocks/library-config/library-config.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
45 changes: 35 additions & 10 deletions libs/blocks/library-config/library-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -32,27 +32,47 @@ 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');
if (!search) {
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 === '') {
clear.classList.add('is-hidden');
} 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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
22 changes: 21 additions & 1 deletion libs/blocks/library-config/library-utils.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
14 changes: 3 additions & 11 deletions libs/blocks/library-config/lists/blocks.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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');
Expand Down
51 changes: 46 additions & 5 deletions libs/blocks/library-config/lists/templates.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
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() {
const br = createTag('br');
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);
Expand All @@ -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);
});

Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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);
});
});
Expand Down
1 change: 1 addition & 0 deletions libs/blocks/library-metadata/library-metadata.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

.library-meta-row {
background-color: #EFEFEF;
color: initial;
display: grid;
grid-template-columns: 1fr 1fr;
margin-top: 4px;
Expand Down
25 changes: 13 additions & 12 deletions test/blocks/library-config/library-config.test.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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;
});
});

Expand All @@ -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;
});
});

Expand All @@ -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;
});
});

Expand Down Expand Up @@ -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;
});
});

0 comments on commit 549c71f

Please sign in to comment.