Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ jobs:
- name: Checkout target repo
uses: actions/checkout@v3
with:
repository: adobe/helix-website
repository: adobe/helix-tools-website
ref: main
path: _target
- name: Copy files from source to target repo
run: |
mkdir -p _target/tools/sidekick/library
cp -r dist/* _target/tools/sidekick/library/
- name: Create branch commit and pull request in target repo
uses: peter-evans/create-pull-request@v7
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Since the sidekick library is hosted on the same origin as the content, a static
<body>
<script
type="module"
src="https://www.aem.live/tools/sidekick/library/index.js"
src="https://tools.aem.live/tools/sidekick/library/index.js"
></script>
<script>
const library = document.createElement('sidekick-library')
Expand All @@ -162,7 +162,9 @@ Since the sidekick library is hosted on the same origin as the content, a static
</html>
```

In the code above we load the sidekick library from `aem.live` and then create a custom `sidekick-library` element and add it to the page. The `sidekick-library` element accepts a `config` object that is required to configure the sidekick library.
In the code above we load the sidekick library from `tools.aem.live` and then create a custom `sidekick-library` element and add it to the page. The `sidekick-library` element accepts a `config` object that is required to configure the sidekick library.

> If your `library.html` loads the library from `www.aem.live`, update the host to `tools.aem.live` to receive new releases. The path is the same.

### Supported configuration parameters
| Parameter Name | Value | Description | Required |
Expand Down Expand Up @@ -465,7 +467,7 @@ Plugin authors can dispatch events from their plugin to the parent sidekick libr
#### Toast Messages

```js
import { PLUGIN_EVENTS } from 'https://www.aem.live/tools/sidekick/library/events/events.js';
import { PLUGIN_EVENTS } from 'https://tools.aem.live/tools/sidekick/library/events/events.js';

export async function decorate(container, data, query) {
// Show a toast message
Expand All @@ -476,7 +478,7 @@ export async function decorate(container, data, query) {
#### Show and Hide Loader

```js
import { PLUGIN_EVENTS } from 'https://www.aem.live/tools/sidekick/library/events/events.js';
import { PLUGIN_EVENTS } from 'https://tools.aem.live/tools/sidekick/library/events/events.js';

export async function decorate(container, data, query) {
// Show loader
Expand Down
6 changes: 5 additions & 1 deletion src/models/app-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ const defaultState = {
};

export default class AppModel {
static libraryHost = 'https://www.hlx.live/tools/sidekick/library';
/**
* The location the library is hosted at, the plugins and locales are loaded from here.
* It's derived from the location of the library itself so that it can be hosted anywhere.
*/
static libraryHost = import.meta.url.substring(0, import.meta.url.lastIndexOf('/'));

static appStore;

Expand Down
80 changes: 72 additions & 8 deletions src/plugins/blocks/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,68 @@ export function parseDescription(description) {
}

/**
* Fetches the section metadata for a block and returns it as a table
* Classes that the library adds to a section while rendering it. They are never
* part of the authored section metadata.
*/
const LIBRARY_CLASSES = ['sidekick-library'];

/**
* Rebuilds a section metadata block from the attributes of a section element.
*
* When section metadata is processed on the server (rendering version 2), the
* `section-metadata` block is no longer part of the markup. Instead its rows are
* applied to the section itself: `Style` becomes classes, `Id` becomes the id and
* every other row becomes a `data-*` attribute.
* @param {HTMLElement} section The section element
* @returns {HTMLElement|undefined} A `section-metadata` block or undefined if the
* section has no section metadata attributes.
*/
export function getSectionMetadataFromAttributes(section) {
const rows = [];

const styles = [...section.classList].filter(name => !LIBRARY_CLASSES.includes(name));
if (styles.length > 0) {
rows.push(['Style', styles.join(', ')]);
}

if (section.id) {
rows.push(['Id', section.id]);
}

[...section.attributes]
.filter(({ name }) => name.startsWith('data-') && name.length > 'data-'.length)
.forEach(({ name, value }) => rows.push([name.substring('data-'.length), value]));

if (rows.length === 0) {
return undefined;
}

const sectionMetadata = createTag('div', { class: 'section-metadata' });
rows.forEach(([key, value]) => {
const row = createTag('div');
[key, value].forEach((text) => {
const cell = createTag('div');
cell.textContent = text;
row.append(cell);
});
sectionMetadata.append(row);
});

return sectionMetadata;
}

/**
* Fetches the section metadata for a section and returns it as a table.
* Supports both the `section-metadata` block that is part of the markup and
* section metadata that has been applied to the section's attributes by the server.
* @param {Object} context The library context
* @param {HTMLElement} block The block element
* @param {HTMLElement} section The section element
* @param {String} baseURL The base URL of the block
* @returns
*/
async function getSectionMetadata(context, block, baseURL) {
const sectionMetadata = block.querySelector(':scope > .section-metadata');
async function getSectionMetadata(context, section, baseURL) {
const sectionMetadata = section.querySelector(':scope > .section-metadata')
?? getSectionMetadataFromAttributes(section);
if (sectionMetadata) {
// Create a table for the section metadata
return convertBlockToTable(
Expand Down Expand Up @@ -485,8 +539,9 @@ export async function copyDefaultContentToClipboard(context, wrapper, blockURL)
if (sectionMetadataTable) {
wrapperClone.append(sectionMetadataTable);

const sectionMetadata = wrapperClone.querySelector(':scope > .section-metadata');
sectionMetadata.remove();
// Remove the section metadata block (if there is one, it's not there when
// the section metadata has been applied to the section's attributes)
wrapperClone.querySelector(':scope > .section-metadata')?.remove();
}

return wrapperClone;
Expand Down Expand Up @@ -528,8 +583,10 @@ export async function copyPageToClipboard(context, wrapper, blockURL, pageMetada
let index = 0;
for (const section of sections) {
// If not the last section, add a section delimeter
let sectionDelimiter;
if (index < sections.length - 1) {
section.insertAdjacentElement('beforeend', sectionBreak.cloneNode(true));
sectionDelimiter = sectionBreak.cloneNode(true);
section.insertAdjacentElement('beforeend', sectionDelimiter);
}

// Create a br element to space out tables
Expand All @@ -556,7 +613,14 @@ export async function copyPageToClipboard(context, wrapper, blockURL, pageMetada
const sectionMetadata = section.querySelector(':scope > div.section-metadata');
const sectionMetadataTable = await getSectionMetadata(ctx, section, blockURL);
if (sectionMetadataTable) {
sectionMetadata.replaceWith(createTag('br'), sectionMetadataTable);
if (sectionMetadata) {
sectionMetadata.replaceWith(createTag('br'), sectionMetadataTable);
} else if (sectionDelimiter) {
// Section metadata applied to the section's attributes goes at the end of the section
sectionDelimiter.before(createTag('br'), sectionMetadataTable);
} else {
section.append(createTag('br'), sectionMetadataTable);
}
}

index += 1;
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { DEFAULT_CONTENT_STUB } from './stubs/default-content.js';
import { COMPOUND_BLOCK_STUB } from './stubs/compound-block.js';
import { TEMPLATE_STUB } from './stubs/template.js';
import { TESTIMONIAL_CAROUSEL_PLAIN_HTML } from './stubs/testimonial-carousel.js';

export function mockBlock(html, variants = [], wrap = false) {
const clone = html.cloneNode(true);
Expand Down Expand Up @@ -142,3 +143,9 @@ export const mockFetchTabsPlainHTMLSuccess = () => fetchMock.get(tabsBlockUrl, {
mockBlock(TABS_DEFAULT_STUB_SECTION_3, [], false).outerHTML,
mockBlock(TABS_DEFAULT_STUB_SECTION_4, [], false).outerHTML].join('\n'),
});

export const testimonialCarouselBlockUrl = 'https://example.hlx.test/tools/sidekick/blocks/testimonial-carousel/testimonial-carousel.plain.html';
export const mockFetchTestimonialCarouselPlainHTMLSuccess = () => fetchMock.get(
testimonialCarouselBlockUrl,
{ status: 200, body: TESTIMONIAL_CAROUSEL_PLAIN_HTML },
);
6 changes: 6 additions & 0 deletions test/fixtures/libraries.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export const TEMPLATE_LIBRARY_ITEM = {
url: 'https://example.hlx.test/tools/sidekick/blocks/blog-post/blog-post',
};

export const TESTIMONIAL_CAROUSEL_LIBRARY_ITEM = {
name: 'Testimonial Carousel',
path: '/tools/sidekick/blocks/testimonial-carousel/testimonial-carousel',
url: 'https://example.hlx.test/tools/sidekick/blocks/testimonial-carousel/testimonial-carousel',
};

export const MIXED_LIBRARY_ITEM = {
name: 'Mixed item with both compound and regular blocks',
path: '/tools/sidekick/blocks/mixed/mixed',
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './stubs/tabs.js';
import { COMPOUND_BLOCK_STUB } from './stubs/compound-block.js';
import { TEMPLATE_STUB } from './stubs/template.js';
import { TESTIMONIAL_CAROUSEL_SECTIONS } from './stubs/testimonial-carousel.js';

export function mockBlock(html, variants = [], wrap = false) {
const clone = html.cloneNode(true);
Expand Down Expand Up @@ -97,3 +98,12 @@ export const mockFetchInlinePageDependenciesSuccess = (blockName = 'cards') => {
fetchMock.get('path:/media_4.jpeg', { status: 200, body: 'foobar', headers: { 'content-type': 'image/jpeg' } }, { overwriteRoutes: true });
fetchMock.get('path:/media_5.jpeg', { status: 200, body: 'foobar', headers: { 'content-type': 'image/jpeg' } }, { overwriteRoutes: true });
};

export const testimonialCarouselPageUrl = 'https://example.hlx.test/tools/sidekick/blocks/testimonial-carousel/testimonial-carousel';
export const mockFetchTestimonialCarouselDocumentSuccess = () => fetchMock.get(
testimonialCarouselPageUrl,
{
status: 200,
body: stubPage(stubHead('testimonial-carousel'), TESTIMONIAL_CAROUSEL_SECTIONS()),
},
);
Loading
Loading