diff --git a/libs/blocks/library-config/library-config.css b/libs/blocks/library-config/library-config.css
index e143cfaa74..1f7b6df019 100644
--- a/libs/blocks/library-config/library-config.css
+++ b/libs/blocks/library-config/library-config.css
@@ -277,7 +277,8 @@ input.sk-library-search-input:focus {
padding: 0 24px;
}
-.sk-library li.placeholder {
+.sk-library li.placeholder,
+.sk-library li.template {
padding: 0 12px;
}
@@ -287,6 +288,22 @@ input.sk-library-search-input:focus {
padding: 0 12px;
}
+.sk-library li.template {
+ display: flex;
+ align-items: stretch;
+ justify-content: end;
+}
+
+.sk-library li.template .item-title {
+ flex: 1 1 100%;
+}
+
+.sk-library li.template button {
+ position: static;
+ height: unset;
+ width: 44px;
+}
+
.in-page {
display: flex;
justify-content: center;
diff --git a/libs/blocks/library-config/library-config.js b/libs/blocks/library-config/library-config.js
index 885e7b089b..c7bc4f2026 100644
--- a/libs/blocks/library-config/library-config.js
+++ b/libs/blocks/library-config/library-config.js
@@ -7,6 +7,11 @@ async function loadBlocks(content, list, query) {
blocks(content, list, query);
}
+async function loadTemplates(content, list) {
+ const { default: templates } = await import('./lists/templates.js');
+ templates(content, list);
+}
+
async function loadPlaceholders(content, list) {
const { default: placeholders } = await import('./lists/placeholders.js');
placeholders(content, list);
@@ -65,6 +70,9 @@ async function loadList(type, content, list) {
addSearch(content, list);
loadBlocks(content, list, query);
break;
+ case 'templates':
+ loadTemplates(content, list);
+ break;
case 'placeholders':
loadPlaceholders(content, list);
break;
@@ -122,6 +130,7 @@ async function combineLibraries(base, supplied) {
const library = {
assets: await fetchAssetsData(assetsPath),
blocks: base.blocks.data,
+ templates: base.templates?.data,
icons: base.icons?.data,
personalization_tags: base.personalization?.data,
placeholders: base.placeholders?.data,
@@ -135,6 +144,10 @@ async function combineLibraries(base, supplied) {
if (supplied.placeholders.data.length > 0) {
library.placeholders = supplied.placeholders.data;
}
+
+ if (supplied.templates?.data.length > 0) {
+ library.templates.push(...supplied.templates.data);
+ }
}
return library;
diff --git a/libs/blocks/library-config/lists/blocks.js b/libs/blocks/library-config/lists/blocks.js
index ffd35d8395..f5f59352f1 100644
--- a/libs/blocks/library-config/lists/blocks.js
+++ b/libs/blocks/library-config/lists/blocks.js
@@ -43,13 +43,14 @@ function getContainerName(container) {
return getAuthorName(container) || getBlockName(firstBlock);
}
-function getTable(block) {
+export function getTable(block, returnDom = false) {
const name = getBlockName(block);
const rows = [...block.children];
const maxCols = rows.reduce((cols, row) => (
row.children.length > cols ? row.children.length : cols), 0);
const table = document.createElement('table');
table.setAttribute('border', 1);
+ table.setAttribute('style', 'width: 100%');
const headerRow = document.createElement('tr');
headerRow.append(createTag('th', { colspan: maxCols }, name));
table.append(headerRow);
@@ -57,6 +58,7 @@ function getTable(block) {
const tr = document.createElement('tr');
[...row.children].forEach((col) => {
const td = document.createElement('td');
+ td.setAttribute('style', `width: ${100 / row.children.length}%`);
if (row.children.length < maxCols) {
td.setAttribute('colspan', maxCols);
}
@@ -65,10 +67,11 @@ function getTable(block) {
});
table.append(tr);
});
+ if (returnDom) return table;
return table.outerHTML;
}
-function handleLinks(element, path) {
+export function handleLinks(element, path) {
if (!element || !path) return;
try {
const url = new URL(path);
@@ -83,7 +86,7 @@ function handleLinks(element, path) {
}
}
-function decorateImages(element, path) {
+export function decorateImages(element, path) {
if (!element || !path) return;
try {
const url = new URL(path);
diff --git a/libs/blocks/library-config/lists/templates.js b/libs/blocks/library-config/lists/templates.js
new file mode 100644
index 0000000000..2c1f780078
--- /dev/null
+++ b/libs/blocks/library-config/lists/templates.js
@@ -0,0 +1,76 @@
+import { createTag } from '../../../utils/utils.js';
+import createCopy from '../library-utils.js';
+import { getTable, decorateImages, handleLinks } from './blocks.js';
+
+function createSpace() {
+ const br = createTag('br');
+ return createTag('p', null, br);
+}
+
+function formatDom(aemDom, path) {
+ // Decorate Links
+ handleLinks(aemDom, path);
+
+ // Decorate Images
+ decorateImages(aemDom, path);
+
+ // Decorate Blocks
+ const divs = aemDom.querySelectorAll('main > div > div');
+ divs.forEach((div) => {
+ // Give table some space
+ div.insertAdjacentElement('afterend', createSpace());
+
+ const table = getTable(div, true);
+ div.parentElement.replaceChild(table, div);
+ });
+
+ // Decorate Sections
+ const sections = aemDom.body.querySelectorAll('main > div');
+ const formattedSections = [...sections].map((section, idx) => {
+ const fragment = new DocumentFragment();
+ if (idx > 0) {
+ const divider = createTag('p', null, '---');
+ fragment.append(divider, createSpace());
+ }
+ fragment.append(...section.querySelectorAll(':scope > *'));
+
+ return fragment;
+ });
+ const flattedDom = createTag('div');
+ flattedDom.append(...formattedSections);
+ return flattedDom;
+}
+
+async function formatTemplate(path) {
+ const resp = await fetch(path);
+ if (!resp.ok) window.lana.log('Could not fetch template path', { tags: 'errorType=info,module=sidekick-templates' });
+
+ const html = await resp.text();
+ const dom = new DOMParser().parseFromString(html, 'text/html');
+ return formatDom(dom, path);
+}
+
+export default async function loadTemplates(templates, list) {
+ 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);
+
+ previewButton.addEventListener('click', (e) => {
+ e.stopPropagation();
+ window.open(template.path, '_templatepreview');
+ });
+
+ 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' });
+ createCopy(blob);
+ });
+ });
+}
diff --git a/libs/blocks/locui/locui.css b/libs/blocks/locui/locui.css
new file mode 100644
index 0000000000..2c9de0b76a
--- /dev/null
+++ b/libs/blocks/locui/locui.css
@@ -0,0 +1,8 @@
+.locui.container {
+ margin: 0 auto;
+ margin-top: 10vh;
+ max-width: 700px;
+ border-radius: 10px;
+ border: 10px solid #eee;
+ padding: 10px 30px 30px;
+}
diff --git a/libs/blocks/locui/locui.js b/libs/blocks/locui/locui.js
new file mode 100644
index 0000000000..2c9acdac1b
--- /dev/null
+++ b/libs/blocks/locui/locui.js
@@ -0,0 +1,16 @@
+import { createTag } from '../../utils/utils.js';
+
+export default function init(el) {
+ el.classList.add('container');
+ const heading = createTag('h2', null, 'Missing project details');
+ const paragraph = createTag('p', null, 'The project details were removed after you logged in. To resolve this:');
+ const steps = createTag('ol', null);
+ const stepList = ['Close this window or tab.', 'Open your project Excel file.', 'Click "Localize..." in Sidekick again.'];
+ stepList.forEach((step) => steps.append(createTag('li', null, step)));
+ const learnmore = createTag('a', {
+ class: 'con-button',
+ href: 'https://milo.adobe.com/docs/authoring/localization#:~:text=at%20render%20time.-,Troubleshooting,-Error%20matrix',
+ target: '_blank',
+ }, 'Learn More');
+ el.append(heading, paragraph, steps, learnmore);
+}
diff --git a/libs/features/personalization/personalization.js b/libs/features/personalization/personalization.js
index 7762907071..c214bafa89 100644
--- a/libs/features/personalization/personalization.js
+++ b/libs/features/personalization/personalization.js
@@ -495,10 +495,10 @@ const getVariantInfo = (line, variantNames, variants, manifestId) => {
});
};
-export function parseConfig(data, manifestId) {
+export function parseManifestVariants(data, manifestId) {
if (!data?.length) return null;
- const config = {};
+ const manifestConfig = {};
const experiences = data.map((d) => normalizeKeys(d));
try {
@@ -512,12 +512,12 @@ export function parseConfig(data, manifestId) {
experiences.forEach((line) => getVariantInfo(line, variantNames, variants, manifestId));
- config.variants = variants;
- config.variantNames = variantNames;
- return config;
+ manifestConfig.variants = variants;
+ manifestConfig.variantNames = variantNames;
+ return manifestConfig;
} catch (e) {
/* c8 ignore next 3 */
- console.log('error parsing personalization config:', e, experiences);
+ console.log('error parsing personalization manifestConfig:', e, experiences);
}
return null;
}
@@ -612,7 +612,7 @@ const createDefaultExperiment = (manifest) => ({
variants: {},
});
-export async function getPersConfig(info, variantOverride = false) {
+export async function getManifestConfig(info, variantOverride = false) {
const {
name,
manifestData,
@@ -637,17 +637,17 @@ export async function getPersConfig(info, variantOverride = false) {
if (!persData) return null;
let manifestId = getFileName(manifestPath);
- const globalConfig = getConfig();
- if (!globalConfig.mep?.preview) {
+ const config = getConfig();
+ if (!config.mep?.preview) {
manifestId = false;
} else if (name) {
manifestId = `${name}: ${manifestId}`;
}
- const config = parseConfig(persData, manifestId);
+ const manifestConfig = parseManifestVariants(persData, manifestId);
- if (!config) {
+ if (!manifestConfig) {
/* c8 ignore next 3 */
- console.log('Error loading personalization config: ', name || manifestPath);
+ console.log('Error loading personalization manifestConfig: ', name || manifestPath);
return null;
}
@@ -661,8 +661,8 @@ export async function getPersConfig(info, variantOverride = false) {
acc[item.key] = item.value;
return acc;
}, {});
- config.manifestOverrideName = infoObj?.['manifest-override-name']?.toLowerCase();
- config.manifestType = infoObj?.['manifest-type']?.toLowerCase();
+ manifestConfig.manifestOverrideName = infoObj?.['manifest-override-name']?.toLowerCase();
+ manifestConfig.manifestType = infoObj?.['manifest-type']?.toLowerCase();
const executionOrder = {
'manifest-type': 1,
'manifest-execution-order': 1,
@@ -672,43 +672,43 @@ export async function getPersConfig(info, variantOverride = false) {
const index = infoKeyMap[key].indexOf(infoObj[key]);
executionOrder[key] = index > -1 ? index : 1;
});
- config.executionOrder = `${executionOrder['manifest-execution-order']}-${executionOrder['manifest-type']}`;
+ manifestConfig.executionOrder = `${executionOrder['manifest-execution-order']}-${executionOrder['manifest-type']}`;
} else {
// eslint-disable-next-line prefer-destructuring
- config.manifestType = infoKeyMap['manifest-type'][1];
- config.executionOrder = '1-1';
+ manifestConfig.manifestType = infoKeyMap['manifest-type'][1];
+ manifestConfig.executionOrder = '1-1';
}
- config.manifestPath = normalizePath(manifestPath);
+ manifestConfig.manifestPath = normalizePath(manifestPath);
const selectedVariantName = await getPersonalizationVariant(
- config.manifestPath,
- config.variantNames,
+ manifestConfig.manifestPath,
+ manifestConfig.variantNames,
variantLabel,
);
- if (selectedVariantName && config.variantNames.includes(selectedVariantName)) {
- config.run = true;
- config.selectedVariantName = selectedVariantName;
- config.selectedVariant = config.variants[selectedVariantName];
+ if (selectedVariantName && manifestConfig.variantNames.includes(selectedVariantName)) {
+ manifestConfig.run = true;
+ manifestConfig.selectedVariantName = selectedVariantName;
+ manifestConfig.selectedVariant = manifestConfig.variants[selectedVariantName];
} else {
/* c8 ignore next 2 */
- config.selectedVariantName = 'default';
- config.selectedVariant = 'default';
+ manifestConfig.selectedVariantName = 'default';
+ manifestConfig.selectedVariant = 'default';
}
const placeholders = manifestPlaceholders || data?.placeholders?.data;
if (placeholders) {
updateConfig(
- parsePlaceholders(placeholders, getConfig(), config.selectedVariantName),
+ parsePlaceholders(placeholders, getConfig(), manifestConfig.selectedVariantName),
);
}
- config.name = name;
- config.manifest = manifestPath;
- config.manifestUrl = manifestUrl;
- config.disabled = disabled;
- config.event = event;
- return config;
+ manifestConfig.name = name;
+ manifestConfig.manifest = manifestPath;
+ manifestConfig.manifestUrl = manifestUrl;
+ manifestConfig.disabled = disabled;
+ manifestConfig.event = event;
+ return manifestConfig;
}
export const deleteMarkedEls = (rootEl = document) => {
@@ -838,7 +838,7 @@ export async function applyPers(manifests, postLCP = false) {
if (!manifests?.length) return;
let experiments = manifests;
for (let i = 0; i < experiments.length; i += 1) {
- experiments[i] = await getPersConfig(experiments[i], config.mep?.variantOverride);
+ experiments[i] = await getManifestConfig(experiments[i], config.mep?.variantOverride);
}
experiments = cleanAndSortManifestList(experiments);
diff --git a/libs/scripts/scripts.js b/libs/scripts/scripts.js
index 154e81967c..3fce88cd92 100644
--- a/libs/scripts/scripts.js
+++ b/libs/scripts/scripts.js
@@ -14,6 +14,7 @@ import {
loadArea,
loadLana,
setConfig,
+ getMetadata,
} from '../utils/utils.js';
// Production Domain
@@ -161,6 +162,7 @@ const eagerLoad = (img) => {
}());
(async function loadPage() {
+ if (getMetadata('template') === '404') window.SAMPLE_PAGEVIEWS_AT_RATE = 'high';
performance.mark('loadpage');
setConfig(config);
loadLana({ clientId: 'milo' });
diff --git a/libs/utils/samplerum.js b/libs/utils/samplerum.js
index 652a13bd06..58f0ec07aa 100644
--- a/libs/utils/samplerum.js
+++ b/libs/utils/samplerum.js
@@ -28,11 +28,12 @@ export function sampleRUM(checkpoint, data = {}) {
try {
window.hlx = window.hlx || {};
if (!window.hlx.rum) {
- const usp = new URLSearchParams(window.location.search);
- const weight = (usp.get('rum') === 'on') ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
- const id = Array.from({ length: 75 }, (_, i) => String.fromCharCode(48 + i)).filter((a) => /\d|[A-Z]/i.test(a)).filter(() => Math.random() * 75 > 70).join('');
- const random = Math.random();
- const isSelected = (random * weight < 1);
+ const weight = (window.SAMPLE_PAGEVIEWS_AT_RATE === 'high' && 10)
+ || (window.SAMPLE_PAGEVIEWS_AT_RATE === 'low' && 1000)
+ || (new URLSearchParams(window.location.search).get('rum') === 'on' && 1)
+ || 100;
+ const id = Math.random().toString(36).slice(-4);
+ const isSelected = (Math.random() * weight < 1);
const firstReadTime = Date.now();
const urlSanitizers = {
full: () => window.location.href,
@@ -44,7 +45,7 @@ export function sampleRUM(checkpoint, data = {}) {
rumSessionStorage.pages = rumSessionStorage.pages ? rumSessionStorage.pages + 1 : 1;
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(rumSessionStorage));
// eslint-disable-next-line object-curly-newline, max-len
- window.hlx.rum = { weight, id, random, isSelected, firstReadTime, sampleRUM, sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'], rumSessionStorage };
+ window.hlx.rum = { weight, id, isSelected, firstReadTime, sampleRUM, sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'], rumSessionStorage };
}
const { weight, id, firstReadTime } = window.hlx.rum;
diff --git a/libs/utils/utils.js b/libs/utils/utils.js
index ed41f1ad0d..a70602485f 100644
--- a/libs/utils/utils.js
+++ b/libs/utils/utils.js
@@ -41,6 +41,7 @@ const MILO_BLOCKS = [
'icon-block',
'iframe',
'instagram',
+ 'locui',
'marketo',
'marquee',
'marquee-anchors',
diff --git a/test/blocks/library-config/mocks/blocks/chart/docx.html b/test/blocks/library-config/mocks/blocks/chart/docx.html
index 096d052cac..957326f441 100644
--- a/test/blocks/library-config/mocks/blocks/chart/docx.html
+++ b/test/blocks/library-config/mocks/blocks/chart/docx.html
@@ -1,3 +1,3 @@
-
diff --git a/test/blocks/library-config/mocks/blocks/container/docx-container.html b/test/blocks/library-config/mocks/blocks/container/docx-container.html
index d1311e15a7..752c26d474 100644
--- a/test/blocks/library-config/mocks/blocks/container/docx-container.html
+++ b/test/blocks/library-config/mocks/blocks/container/docx-container.html
@@ -1,15 +1,15 @@
-carousel (lightbox) |
---|
Carousel lightbox |
section-metadata |
---|
style | xxl spacing |
---
Avocado surprise
+
carousel (lightbox) |
---|
Carousel lightbox |
section-metadata |
---|
style | xxl spacing |
---
Avocado surprise
-
section-metadata |
---|
carousel | Carousel lightbox |
style | Center |
---
Cabage surprise
+
section-metadata |
---|
carousel | Carousel lightbox |
style | Center |
---
Cabage surprise
-
section-metadata |
---|
carousel | Carousel lightbox |
style | Center |
---
\ No newline at end of file
+ section-metadata |
---|
carousel | Carousel lightbox |
style | Center |
---
diff --git a/test/blocks/library-config/mocks/blocks/container/docx-single-block.html b/test/blocks/library-config/mocks/blocks/container/docx-single-block.html
index 99f92a87e0..169dcc33bd 100644
--- a/test/blocks/library-config/mocks/blocks/container/docx-single-block.html
+++ b/test/blocks/library-config/mocks/blocks/container/docx-single-block.html
@@ -1 +1 @@
-carousel (container0) |
---|
Carousel container |
\ No newline at end of file
+carousel (container0) |
---|
Carousel container |
diff --git a/test/blocks/library-config/mocks/blocks/marquee/docx.html b/test/blocks/library-config/mocks/blocks/marquee/docx.html
index c2514138a5..ff240b8b7f 100644
--- a/test/blocks/library-config/mocks/blocks/marquee/docx.html
+++ b/test/blocks/library-config/mocks/blocks/marquee/docx.html
@@ -1,11 +1,11 @@
-marquee |
---|
+marquee |
---|
- |
+ |
|
+ |
- |
\ No newline at end of file
+ |
diff --git a/test/blocks/library-config/mocks/blocks/text/docx.html b/test/blocks/library-config/mocks/blocks/text/docx.html
index 71a0437c5a..2d9e67ce1d 100644
--- a/test/blocks/library-config/mocks/blocks/text/docx.html
+++ b/test/blocks/library-config/mocks/blocks/text/docx.html
@@ -1,5 +1,5 @@
-text |
---|
+
\ No newline at end of file
+ |