From 8477c46ee215ec56bcb0277c9a04be68fcc025f6 Mon Sep 17 00:00:00 2001 From: chelms Date: Mon, 26 Jun 2023 10:51:48 -0400 Subject: [PATCH 01/46] feat: add new sidekick library plugin version --- scripts/lib-franklin.js | 39 +------------------- scripts/scripts.js | 73 ++++++++++++++++++++++++++++++++++++- tools/sidekick/config.json | 2 +- tools/sidekick/library.html | 50 +++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 40 deletions(-) create mode 100644 tools/sidekick/library.html diff --git a/scripts/lib-franklin.js b/scripts/lib-franklin.js index 4e264455..b1391a15 100644 --- a/scripts/lib-franklin.js +++ b/scripts/lib-franklin.js @@ -389,45 +389,8 @@ export async function loadBlocks(main) { } } -/** - * Returns a picture element with webp and fallbacks - * @param {string} src The image URL - * @param {boolean} eager load image eager - * @param {Array} breakpoints breakpoints and corresponding params (eg. width) - */ -export function createOptimizedPicture(src, alt = '', eager = false, breakpoints = [{ media: '(min-width: 600px)', width: '2000' }, { width: '750' }]) { - const url = new URL(src, window.location.href); - const picture = document.createElement('picture'); - const { pathname } = url; - const ext = pathname.substring(pathname.lastIndexOf('.') + 1); - - // webp - breakpoints.forEach((br) => { - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('type', 'image/webp'); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`); - picture.appendChild(source); - }); - - // fallback - breakpoints.forEach((br, i) => { - if (i < breakpoints.length - 1) { - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - picture.appendChild(source); - } else { - const img = document.createElement('img'); - img.setAttribute('loading', eager ? 'eager' : 'lazy'); - img.setAttribute('alt', alt); - picture.appendChild(img); - img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - } - }); +// createOptimizedPicture moved to scripts.js - return picture; -} /** * Normalizes all headings within a container element. diff --git a/scripts/scripts.js b/scripts/scripts.js index b1fe5d97..53b37f29 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -11,7 +11,6 @@ import { waitForLCP, loadBlocks, loadCSS, - createOptimizedPicture, readBlockConfig, } from './lib-franklin.js'; @@ -19,6 +18,78 @@ const LCP_BLOCKS = ['leadspace', 'blog-home']; // add your LCP blocks to the lis window.hlx.RUM_GENERATION = 'merative'; // add your RUM generation information here /** + * Added 2023-06-26 per new sidekick instructions + * Returns the true origin of the current page in the browser. + * If the page is running in an iframe with srcdoc, the ancestor origin is returned. + * @returns {String} The true origin + */ +export function getOrigin() { + const { location } = window; + return location.href === 'about:srcdoc' ? window.parent.location.origin : location.origin; +} + +/** + * Returns the true of the current page in the browser.mac + * If the page is running in a iframe with srcdoc, + * the ancestor origin + the path query param is returned. + * @returns {String} The href of the current page or the href of the block running in the library + */ + +export function getHref() { + if (window.location.href !== 'about:srcdoc') return window.location.href; + + const { location: parentLocation } = window.parent; + const urlParams = new URLSearchParams(parentLocation.search); + return `${parentLocation.origin}${urlParams.get('path')}`; +} + +/** moved from lib-franklin.js + * Returns a picture element with webp and fallbacks + * @param {string} src The image URL + * @param {boolean} eager load image eager + * @param {Array} breakpoints breakpoints and corresponding params (eg. width) + */ + +// TODO: Due to new sidekick, need to move this into scripts.js and modify it to use +// the getHref() function. https://github.com/adobe/franklin-sidekick-library#considerations-when-building-blocks-for-the-library +export function createOptimizedPicture(src, alt = '', eager = false, breakpoints = [{ + media: '(min-width: 600px)', + width: '2000', +}, { width: '750' }]) { + const url = new URL(src, window.location.href); + const picture = document.createElement('picture'); + const { pathname } = url; + const ext = pathname.substring(pathname.lastIndexOf('.') + 1); + + // webp + breakpoints.forEach((br) => { + const source = document.createElement('source'); + if (br.media) source.setAttribute('media', br.media); + source.setAttribute('type', 'image/webp'); + source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`); + picture.appendChild(source); + }); + + // fallback + breakpoints.forEach((br, i) => { + if (i < breakpoints.length - 1) { + const source = document.createElement('source'); + if (br.media) source.setAttribute('media', br.media); + source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); + picture.appendChild(source); + } else { + const img = document.createElement('img'); + img.setAttribute('loading', eager ? 'eager' : 'lazy'); + img.setAttribute('alt', alt); + picture.appendChild(img); + img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); + } + }); + + return picture; +} + +/** BLOCK LIBRARY * Determine if we are serving content for the block-library, if so don't load the header or footer * @returns {boolean} True if we are loading block library content */ diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json index 9060a5e8..eb575f40 100644 --- a/tools/sidekick/config.json +++ b/tools/sidekick/config.json @@ -8,7 +8,7 @@ "environments": [ "edit" ], "isPalette": true, "paletteRect": "top: auto; bottom: 20px; left: 20px; height: 398px; width: 360px;", - "url": "/block-library/library", + "url": "/tools/sidekick/library.html", "includePaths": [ "**.docx**" ] }, { diff --git a/tools/sidekick/library.html b/tools/sidekick/library.html new file mode 100644 index 00000000..0f8d1da9 --- /dev/null +++ b/tools/sidekick/library.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + Sidekick Library + + + + + + + \ No newline at end of file From 3e29f2ab23aed1fe56bedbf7b686aa7c6dfaac91 Mon Sep 17 00:00:00 2001 From: chelms Date: Mon, 26 Jun 2023 11:00:30 -0400 Subject: [PATCH 02/46] feat: add json location for block-library --- tools/sidekick/library.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sidekick/library.html b/tools/sidekick/library.html index 0f8d1da9..a4808be8 100644 --- a/tools/sidekick/library.html +++ b/tools/sidekick/library.html @@ -41,7 +41,7 @@ - \ No newline at end of file + diff --git a/tools/sidekick/plugins/blocks/blocks.css b/tools/sidekick/plugins/blocks/blocks.css deleted file mode 100644 index dc4bd1e8..00000000 --- a/tools/sidekick/plugins/blocks/blocks.css +++ /dev/null @@ -1 +0,0 @@ -@import url('../../library-config.css'); \ No newline at end of file diff --git a/tools/sidekick/plugins/blocks/blocks.js b/tools/sidekick/plugins/blocks/blocks.js deleted file mode 100644 index 7452c28e..00000000 --- a/tools/sidekick/plugins/blocks/blocks.js +++ /dev/null @@ -1,37 +0,0 @@ -import { - appendListGroup, - createGroupItem, - fetchListDocument, - createTable, -} from '../../library-utils.js'; - -function getAuthorName(block) { - const blockSib = block.previousElementSibling; - if (!blockSib) return null; - if (['H2', 'H3'].includes(blockSib.nodeName)) { - return blockSib.textContent; - } - return null; -} - -function getBlockName(block) { - const classes = block.className.split(' '); - const name = classes.shift(); - return classes.length > 0 ? `${name} (${classes.join(', ')})` : name; -} - -export default function loadBlocks(blocks, list) { - blocks.forEach(async (block) => { - const blockGroup = appendListGroup(list, block); - const blockDoc = await fetchListDocument(block); - const pageBlocks = blockDoc.body.querySelectorAll('div[class]'); - pageBlocks.forEach((pageBlock) => { - const blockName = getAuthorName(pageBlock) || getBlockName(pageBlock); - const blockItem = createGroupItem( - blockName, - () => createTable(pageBlock, getBlockName(pageBlock), block.path), - ); - blockGroup.append(blockItem); - }); - }); -} diff --git a/tools/sidekick/plugins/icons/icons.css b/tools/sidekick/plugins/icons/icons.css index ef45604c..e47357d7 100644 --- a/tools/sidekick/plugins/icons/icons.css +++ b/tools/sidekick/plugins/icons/icons.css @@ -16,4 +16,4 @@ display: flex; width: calc(100% - 128px); margin: 0 auto; -} \ No newline at end of file +} diff --git a/tools/sidekick/plugins/icons/icons.js b/tools/sidekick/plugins/icons/icons.js index e629e085..f286325f 100644 --- a/tools/sidekick/plugins/icons/icons.js +++ b/tools/sidekick/plugins/icons/icons.js @@ -20,7 +20,9 @@ async function processIcons(pageBlock, path) { const iconName = Array.from(icon.classList) .find((c) => c.startsWith('icon-')) .substring(5); - const response = await fetch(`https://${host}/icons/${iconName}.svg`); + // need to comment out host to run locally + // const response = await fetch(`https://${host}/icons/${iconName}.svg`); + const response = await fetch(`http://localhost:3000/icons/${iconName}.svg`); const svg = await response.text(); icons[iconText] = { label: iconText, name: iconName, svg }; })); @@ -28,7 +30,7 @@ async function processIcons(pageBlock, path) { } export async function fetchBlock(path) { - if (!window.blocks) { + if (!window.blocks) { // look for all paths from the helix-icons sheet window.blocks = {}; } if (!window.icons) { @@ -50,7 +52,8 @@ export async function fetchBlock(path) { } /** - * Called when a user tries to load the plugin + * Called when a user tries to load the plugin. + * This takes all icons from all sheets and puts in 1 gridContainer * @param {HTMLElement} container The container to render the plugin in * @param {Object} data The data contained in the plugin sheet * @param {String} query If search is active, the current search query diff --git a/tools/sidekick/plugins/sections/sections.css b/tools/sidekick/plugins/sections/sections.css new file mode 100644 index 00000000..3102d9d2 --- /dev/null +++ b/tools/sidekick/plugins/sections/sections.css @@ -0,0 +1,8 @@ +@import url('../../library-config.css'); + +iframe { + width: 100%; + height: 100%; + border: none; + display: none; +} diff --git a/tools/sidekick/plugins/sections/sections.js b/tools/sidekick/plugins/sections/sections.js new file mode 100644 index 00000000..d1bc2f38 --- /dev/null +++ b/tools/sidekick/plugins/sections/sections.js @@ -0,0 +1,170 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { + createElement, + renderScaffolding, + fetchBlockPage, + copyBlock, + getLibraryMetadata, + initSplitFrame, + renderPreview, +} from '../../library-utils.js'; + +function createBlockTable(block) { + let blockName = block.classList[0]; + if (blockName !== 'library-metadata') { + blockName = blockName + .replace('-', ' ') + .split(' ') + .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`) + .join(' '); + const rows = [...block.children]; + const maxCols = rows.reduce((cols, row) => ( + row.children.length > cols ? row.children.length : cols), 0); + const table = createElement('table', '', { + border: 1, + }, createElement('tr', '', {}, createElement('td', '', { + colspan: maxCols, + style: 'background-color:#f4cccd;', + }, blockName))); + + rows.forEach((row) => { + const tableRow = createElement('tr'); + [...row.children].forEach((col) => { + const td = createElement('td'); + td.innerHTML = col.innerHTML; + tableRow.append(td); + }); + table.append(tableRow); + }); + + return table; + } + + return block; +} + +function preCopy(docBody) { + const doc = docBody.cloneNode(true); + + const sectionBreak = createElement('p', '', {}, '---'); + const sections = doc.querySelectorAll(':scope > div'); + sections.forEach((section, i) => { + if (i < (sections.length - 1)) { + section.insertAdjacentElement('beforeend', sectionBreak.cloneNode(true)); + } + + const blocks = section.querySelectorAll(':scope > div'); + blocks.forEach((block) => { + const blockTable = createBlockTable(block); + block.replaceWith(blockTable); + }); + }); + + return doc; +} + +async function loadSections(data, container, sideNav) { + const promises = data.map(async (item) => { + const { name, path } = item; + const sectionPromise = fetchBlockPage(path); + try { + const res = await sectionPromise; + if (!res) { + throw new Error(`An error occurred fetching ${name}`); + } + const blockInfo = getLibraryMetadata(res.body); + + const sectionNavItem = createElement('sp-sidenav-item', '', { + label: name, + action: true, + }, [ + createElement('sp-icon-file-section', '', { slot: 'icon', size: 's' }), + createElement('sp-icon-copy', '', { slot: 'action-icon' }), + ]); + sectionNavItem.addEventListener('OnAction', () => { + const toCopy = preCopy(res.body); + copyBlock(toCopy, path, container); + }, false); + sectionNavItem.addEventListener('click', () => { + container.dispatchEvent(new CustomEvent('LoadSection', { + detail: { + path, + page: res.body, + blockInfo, + }, + })); + }); + sideNav.append(sectionNavItem); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e.message); + container.dispatchEvent(new CustomEvent('Toast', { detail: { message: e.message, variant: 'negative' } })); + } + }); + + await Promise.all(promises); +} + +/** + * Called when a user tries to load the plugin + * @param {HTMLElement} container The container to render the plugin in + * @param {Object} data The data contained in the plugin sheet + * @param {String} query If search is active, the current search query + */ +export async function decorate(container, data, _query) { + container.dispatchEvent(new CustomEvent('ShowLoader')); + const content = renderScaffolding(); + const sideNav = createElement('sp-sidenav', '', { 'data-testid': 'sections' }); + + await loadSections(data, container, sideNav); + + const listContainer = content.querySelector('.list-container'); + listContainer.append(sideNav); + + container.addEventListener('LoadSection', (e) => { + const { + path, + page, + blockInfo, + } = e.detail; + + initSplitFrame(content); + + const blockTitle = content.querySelector('.block-title'); + blockTitle.textContent = blockInfo.name; + + const details = content.querySelector('.details'); + details.innerHTML = ''; + if (blockInfo.description) { + const description = createElement('p', '', {}, blockInfo.description); + details.append(description); + } + + renderPreview(page, path, content.querySelector('.frame-view')); + + const copyButton = content.querySelector('.copy-button'); + copyButton?.addEventListener('click', () => { + const toCopy = preCopy(page); + copyBlock(toCopy, path, container); + }); + }); + + // Show blocks and hide loader + container.append(content); + container.dispatchEvent(new CustomEvent('HideLoader')); +} + +export default { + title: 'Sections', + searchEnabled: false, +}; From be96b340e620aed3f848f59262ba3955ed33ccd5 Mon Sep 17 00:00:00 2001 From: chelms Date: Tue, 5 Sep 2023 16:52:24 -0400 Subject: [PATCH 39/46] linting of js --- tools/sidekick/library-utils.js | 28 ++++++++++----------- tools/sidekick/plugins/icons/icons.js | 4 +-- tools/sidekick/plugins/sections/sections.js | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/sidekick/library-utils.js b/tools/sidekick/library-utils.js index 4babf05c..3ab4f6e1 100644 --- a/tools/sidekick/library-utils.js +++ b/tools/sidekick/library-utils.js @@ -107,7 +107,7 @@ export function renderScaffolding() { */ export async function createCopy(blob) { // eslint-disable-next-line no-undef - const data = [new ClipboardItem({[blob.type]: blob})]; + const data = [new ClipboardItem({ [blob.type]: blob })]; await navigator.clipboard.write(data); } @@ -118,7 +118,7 @@ export function processMarkup(pageBlock, path) { const srcSplit = img.src.split('/'); const mediaPath = srcSplit.pop(); img.src = `${url.origin}/${mediaPath}`; - const {width, height} = img; + const { width, height } = img; const ratio = width > 450 ? 450 / width : 1; img.width = width * ratio; img.height = height * ratio; @@ -131,15 +131,15 @@ export function processMarkup(pageBlock, path) { export async function copyBlock(pageBlock, path, container) { const processed = processMarkup(pageBlock, path); - const blob = new Blob([processed.innerHTML], {type: 'text/html'}); + const blob = new Blob([processed.innerHTML], { type: 'text/html' }); try { await createCopy(blob); // Show toast - container.dispatchEvent(new CustomEvent('Toast', {detail: {message: 'Copied Block'}})); + container.dispatchEvent(new CustomEvent('Toast', { detail: { message: 'Copied Block' } })); } catch (err) { // eslint-disable-next-line no-console console.error(err.message); - container.dispatchEvent(new CustomEvent('Toast', {detail: {message: err.message, variant: 'negative'}})); + container.dispatchEvent(new CustomEvent('Toast', { detail: { message: err.message, variant: 'negative' } })); } } @@ -160,21 +160,21 @@ export function initSplitFrame(content) { }, [ createElement('div', 'view', {}, [ createElement('div', 'action-bar', {}, [ - createElement('sp-action-group', '', {compact: '', selects: 'single', selected: 'desktop'}, [ - createElement('sp-action-button', '', {value: 'mobile'}, [ - createElement('sp-icon-device-phone', '', {slot: 'icon'}), + createElement('sp-action-group', '', { compact: '', selects: 'single', selected: 'desktop' }, [ + createElement('sp-action-button', '', { value: 'mobile' }, [ + createElement('sp-icon-device-phone', '', { slot: 'icon' }), 'Mobile', ]), - createElement('sp-action-button', '', {value: 'tablet'}, [ - createElement('sp-icon-device-tablet', '', {slot: 'icon'}), + createElement('sp-action-button', '', { value: 'tablet' }, [ + createElement('sp-icon-device-tablet', '', { slot: 'icon' }), 'Tablet', ]), - createElement('sp-action-button', '', {value: 'desktop'}, [ - createElement('sp-icon-device-desktop', '', {slot: 'icon'}), + createElement('sp-action-button', '', { value: 'desktop' }, [ + createElement('sp-icon-device-desktop', '', { slot: 'icon' }), 'Desktop', ]), ]), - createElement('sp-divider', '', {size: 's'}), + createElement('sp-divider', '', { size: 's' }), ]), createElement('div', 'frame-view', {}), ]), @@ -183,7 +183,7 @@ export function initSplitFrame(content) { createElement('h3', 'block-title'), createElement('div', 'actions', {}, createElement('sp-button', 'copy-button', {}, 'Copy Block')), ]), - createElement('sp-divider', '', {size: 's'}), + createElement('sp-divider', '', { size: 's' }), createElement('div', 'details'), ]), ])); diff --git a/tools/sidekick/plugins/icons/icons.js b/tools/sidekick/plugins/icons/icons.js index f286325f..41ee1df3 100644 --- a/tools/sidekick/plugins/icons/icons.js +++ b/tools/sidekick/plugins/icons/icons.js @@ -21,8 +21,8 @@ async function processIcons(pageBlock, path) { .find((c) => c.startsWith('icon-')) .substring(5); // need to comment out host to run locally - // const response = await fetch(`https://${host}/icons/${iconName}.svg`); - const response = await fetch(`http://localhost:3000/icons/${iconName}.svg`); + const response = await fetch(`https://${host}/icons/${iconName}.svg`); + // const response = await fetch(`http://localhost:3000/icons/${iconName}.svg`); const svg = await response.text(); icons[iconText] = { label: iconText, name: iconName, svg }; })); diff --git a/tools/sidekick/plugins/sections/sections.js b/tools/sidekick/plugins/sections/sections.js index d1bc2f38..328f34fb 100644 --- a/tools/sidekick/plugins/sections/sections.js +++ b/tools/sidekick/plugins/sections/sections.js @@ -121,7 +121,7 @@ async function loadSections(data, container, sideNav) { * @param {Object} data The data contained in the plugin sheet * @param {String} query If search is active, the current search query */ -export async function decorate(container, data, _query) { +export async function decorate(container, data) { container.dispatchEvent(new CustomEvent('ShowLoader')); const content = renderScaffolding(); const sideNav = createElement('sp-sidenav', '', { 'data-testid': 'sections' }); From 7422bc1dfa1e1461d0deeb4bcf352c08e3eec519 Mon Sep 17 00:00:00 2001 From: chelms Date: Mon, 2 Oct 2023 12:43:09 -0400 Subject: [PATCH 40/46] fix: library location and compound sections will copy --- tools/sidekick/library.html | 11 ++++++++--- tools/sidekick/{ => library}/plugins/icons/icons.css | 0 tools/sidekick/{ => library}/plugins/icons/icons.js | 2 +- .../{ => library}/plugins/sections/sections.css | 0 .../{ => library}/plugins/sections/sections.js | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) rename tools/sidekick/{ => library}/plugins/icons/icons.css (100%) rename tools/sidekick/{ => library}/plugins/icons/icons.js (98%) rename tools/sidekick/{ => library}/plugins/sections/sections.css (100%) rename tools/sidekick/{ => library}/plugins/sections/sections.js (99%) diff --git a/tools/sidekick/library.html b/tools/sidekick/library.html index d204c45c..244e8e13 100644 --- a/tools/sidekick/library.html +++ b/tools/sidekick/library.html @@ -1,4 +1,5 @@ - -