Skip to content

Commit

Permalink
feat: autoblocks and default content support (#47)
Browse files Browse the repository at this point in the history
* feat: autoblocks and default content support

* fix: skip search

* fix: link to autoblocks docs

* fix: remove event listeners between block loads

* fix: Don't use `section-metadata` as block name in default content

* fix: broken test
  • Loading branch information
dylandepass authored Jun 28, 2023
1 parent a555915 commit 6dc5ee8
Show file tree
Hide file tree
Showing 12 changed files with 489 additions and 80 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ To generate content for the blocks plugin, you need to prepare a separate Word d

![Library.xlsx](https://github.com/adobe/franklin-sidekick-library/assets/3231084/5f645ab8-cc30-4cd6-932b-94024d01713b)

#### (Optional) Authoring block names and descriptions.
### (Optional) Authoring block names and descriptions.

By default the block name (with variation) will be used to render the item in the blocks plugin. For example, if the name of the block is `columns (center, background)` than that name will be used as the label when it’s rendered in the blocks plugin. This can be customized by creating a library metadata section within the same section as the block. Library metadata can also be used to author a description of the block as well as adding `searchTags` to include an alias for the block when using the search feature.

Expand All @@ -59,6 +59,10 @@ Example block with custom name and description

![Screenshot 2023-06-08 at 1 13 32 PM](https://github.com/adobe/franklin-sidekick-library/assets/3231084/fce6f59c-775c-457c-bab5-8b3c85c0efa6)

### Autoblocks and Default Content

The blocks plugin is capable of rendering [default content](https://www.hlx.live/developer/markup-sections-blocks#default-content) and [autoblocks](https://www.hlx.live/developer/markup-sections-blocks#auto-blocking). In order to achieve this, it is necessary to place your `default content` or `autoblock` within a dedicated section, which should include a library metadata table defining a name property, as previously described. If no name is specified in the library metadata, the item will be labeled as "Unnamed Item."

## Sidekick plugin setup

Since the sidekick library is hosted on the same origin as the content, a static HTML page needs to be created to load and configure the content.
Expand Down
20 changes: 16 additions & 4 deletions src/components/block-list/block-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export class BlockList extends LitElement {
display: none;
}
.list-container sp-sidenav sp-sidenav-item[label="Unnamed Item"] {
--spectrum-sidenav-item-text-color: var(--spectrum-negative-color-700);
}
.message-container {
padding-top: 50px;
}
Expand Down Expand Up @@ -186,25 +192,31 @@ export class BlockList extends LitElement {
const { body } = blockDocument.cloneNode(true);

// Check for default library metadata
const defaultLibraryMetadata = getDefaultLibraryMetadata(body);
const defaultLibraryMetadata = getDefaultLibraryMetadata(body) ?? {};

// Query all variations of the block in the container
const pageBlocks = [...body.querySelectorAll(':scope > div')];
const pageBlocks = body.querySelectorAll(':scope > div');

pageBlocks.forEach((blockWrapper, index) => {
// Check if the variation has library metadata
const sectionLibraryMetadata = getLibraryMetadata(blockWrapper) ?? {};
const blockElement = blockWrapper.querySelector('div[class]');
const authoredBlockName = sectionLibraryMetadata.name ?? getBlockName(blockElement);
let itemName = sectionLibraryMetadata.name ?? getBlockName(blockElement);
const blockNameWithVariant = getBlockName(blockElement, true);
const searchTags = sectionLibraryMetadata.searchtags
?? sectionLibraryMetadata['search-tags']
?? defaultLibraryMetadata.searchtags
?? defaultLibraryMetadata['search-tags']
?? '';

// If the item doesn't have an authored or default
// name (default content), set to 'Unnamed Item'
if (!itemName || itemName === 'section-metadata') {
itemName = 'Unnamed Item';
}

const blockVariantItem = createSideNavItem(
authoredBlockName,
itemName,
'sp-icon-file-code',
false,
true,
Expand Down
36 changes: 26 additions & 10 deletions src/components/block-renderer/block-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class BlockRenderer extends LitElement {
activeOverlayContent: { state: false },
selectedContentEditable: { state: false },
blockData: { state: false },
isBlock: { state: false, type: Boolean },
extendedBlock: { state: true, type: Boolean },
};

Expand Down Expand Up @@ -254,13 +255,27 @@ export class BlockRenderer extends LitElement {
? context.extendedLibraryOrigin
: context.baseLibraryOrigin;

// Change all media relative paths to absolute paths in the block
blockWrapper.innerHTML = blockWrapper.innerHTML
.replace(/\.\/media/g, `${origin}/media`)
.replace(/src="\/media/g, `src="${origin}/media`);

// Store the active block
this.blockWrapperHTML = blockWrapper;

// Store the block data
this.blockData = blockData;

const block = this.getBlockElement();
// Assume what we are trying to load is a block and not an autoblock or default content
this.isBlock = true;

let block = this.getBlockElement();

// If there is no block, then we are rendering an autoblock or default content
if (!block) {
this.isBlock = false;
block = this.getBlockWrapper();
}

// Add the sidekick-library class to the block element
const sidekickLibraryClass = 'sidekick-library';
Expand All @@ -272,11 +287,6 @@ export class BlockRenderer extends LitElement {
// Clone the block and decorate it
const blockClone = blockWrapper.cloneNode(true);

// Change all media relative paths to absolute paths in the block
blockClone.innerHTML = blockClone.innerHTML
.replace(/\.\/media/g, `${origin}/media`)
.replace(/src="\/media/g, `src="${origin}/media`);

// Fetch the container page markup
const containerPageMarkup = await this.fetchContainerPageMarkup(blockURL, origin);
const containerDocument = new DOMParser().parseFromString(containerPageMarkup, 'text/html');
Expand Down Expand Up @@ -353,12 +363,18 @@ export class BlockRenderer extends LitElement {

// Load the block and lazy CSS
const codePath = `${origin}${hlx?.codeBasePath ?? ''}`;
const styleLink = createTag('link', { rel: 'stylesheet', href: `${codePath}/blocks/${blockName}/${blockName}.css` });

// If we are rendering a block, load the block CSS
if (this.isBlock) {
const styleLink = createTag('link', { rel: 'stylesheet', href: `${codePath}/blocks/${blockName}/${blockName}.css` });
frame.contentWindow.document.head.append(styleLink);
}

// Load the lazy CSS
const lazyStyleLink = createTag('link', { rel: 'stylesheet', href: `${codePath}/styles/lazy-styles.css` });
frame.contentWindow.document.head.append(lazyStyleLink);
frame.contentWindow.document.head.append(styleLink);

styleLink.onload = () => {
lazyStyleLink.onload = () => {
// Show the iframe
frame.style.display = 'block';

Expand All @@ -375,7 +391,7 @@ export class BlockRenderer extends LitElement {
}
});
};
styleLink.onerror = (e) => {
lazyStyleLink.onerror = (e) => {
// eslint-disable-next-line no-console
console.error(e);
};
Expand Down
75 changes: 67 additions & 8 deletions src/plugins/blocks/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
getBlockName,
getTable,
parseDescription,
prepareIconsForCopy,
prepareImagesForCopy,
} from './utils.js';
import {
createTag, setURLParams,
Expand Down Expand Up @@ -98,7 +100,19 @@ function renderFrameSplitContainer() {
`;
}

function copyBlockToClipboard(wrapper, name, blockURL) {
function removeAllEventListeners(element) {
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
return clone;
}

/**
* Copies a block to the clipboard
* @param {HTMLElement} wrapper The wrapper element
* @param {string} name The name of the block
* @param {string} blockURL The URL of the block
*/
export function copyBlockToClipboard(wrapper, name, blockURL) {
// Get the first block element ignoring any section metadata blocks
const element = wrapper.querySelector(':scope > div:not(.section-metadata)');
let blockTable = '';
Expand Down Expand Up @@ -130,6 +144,35 @@ function copyBlockToClipboard(wrapper, name, blockURL) {
sampleRUM('library:blockcopied', { target: blockURL });
}

/**
* Copies default content to the clipboard
* @param {HTMLElement} wrapper The wrapper element
* @param {string} blockURL The URL of the block
*/
export function copyDefaultContentToClipboard(wrapper, blockURL) {
const wrapperClone = wrapper.cloneNode(true);
prepareIconsForCopy(wrapperClone);
prepareImagesForCopy(wrapperClone, blockURL, 100);

// Does the block have section metadata?
let sectionMetadataTable;
const sectionMetadata = wrapperClone.querySelector('.section-metadata');
if (sectionMetadata) {
// Create a table for the section metadata
sectionMetadataTable = getTable(
sectionMetadata,
'Section metadata',
blockURL,
);
sectionMetadata.remove();
}

copyBlock(wrapperClone.outerHTML, sectionMetadataTable);

// Track block copy event
sampleRUM('library:blockcopied', { target: blockURL });
}

/**
* Called when a user tries to load the plugin
* @param {HTMLElement} container The container to render the plugin in
Expand Down Expand Up @@ -194,6 +237,7 @@ export async function decorate(container, data) {

const blockRenderer = content.querySelector('block-renderer');

// If the block element exists, load the block
blockRenderer.loadBlock(
blockName,
blockData,
Expand All @@ -204,28 +248,35 @@ export async function decorate(container, data) {
// Append the path and index of the current block to the url params
setURLParams([['path', blockData.path], ['index', e.detail.index]]);

const copyButton = content.querySelector('.copy-button');
copyButton?.addEventListener('click', () => {
const copyButton = removeAllEventListeners(content.querySelector('.copy-button'));
copyButton.addEventListener('click', () => {
const copyElement = blockRenderer.getBlockElement();
const copyWrapper = blockRenderer.getBlockWrapper();
const copyBlockData = blockRenderer.getBlockData();

copyBlockToClipboard(copyWrapper, getBlockName(copyElement, true), copyBlockData.url);
// Are we trying to copy a block or default content?
// The copy operation is slightly different depending on which
if (blockRenderer.isBlock) {
copyBlockToClipboard(copyWrapper, getBlockName(copyElement, true), copyBlockData.url);
} else {
copyDefaultContentToClipboard(copyWrapper, copyBlockData.url);
}

container.dispatchEvent(new CustomEvent('Toast', { detail: { message: 'Copied Block' } }));
});

const frameView = content.querySelector('.frame-view');
const mobileViewButton = content.querySelector('sp-action-button[value="mobile"]');
const mobileViewButton = removeAllEventListeners(content.querySelector('sp-action-button[value="mobile"]'));
mobileViewButton?.addEventListener('click', () => {
frameView.style.width = '480px';
});

const tabletViewButton = content.querySelector('sp-action-button[value="tablet"]');
const tabletViewButton = removeAllEventListeners(content.querySelector('sp-action-button[value="tablet"]'));
tabletViewButton?.addEventListener('click', () => {
frameView.style.width = '768px';
});

const desktopViewButton = content.querySelector('sp-action-button[value="desktop"]');
const desktopViewButton = removeAllEventListeners(content.querySelector('sp-action-button[value="desktop"]'));
desktopViewButton?.addEventListener('click', () => {
frameView.style.width = '100%';
});
Expand All @@ -236,7 +287,15 @@ export async function decorate(container, data) {

blockList.addEventListener('CopyBlock', (e) => {
const { blockWrapper: wrapper, blockNameWithVariant: name, blockURL } = e.detail;
copyBlockToClipboard(wrapper, name, blockURL);

// We may not have rendered the block yet, so we need to check for a block to know if
// we are dealing with a block or default content
const block = wrapper.querySelector(':scope > div:not(.section-metadata)');
if (block) {
copyBlockToClipboard(wrapper, name, blockURL);
} else {
copyDefaultContentToClipboard(wrapper, blockURL);
}
container.dispatchEvent(new CustomEvent('Toast', { detail: { message: 'Copied Block' } }));
});

Expand Down
Loading

0 comments on commit 6dc5ee8

Please sign in to comment.