Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] Stage to Main #3102

Merged
merged 9 commits into from
Oct 30, 2024
1 change: 0 additions & 1 deletion libs/blocks/article-feed/article-feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ async function buildFilter(type, tax, block, config) {
applyBtn.classList.add('button', 'small', 'apply');
applyBtn.textContent = await replacePlaceholder('apply');
applyBtn.addEventListener('click', () => {
// sampleRUM('apply-topic-filter');
delete config.selectedProducts;
delete config.selectedIndustries;
closeCurtain();
Expand Down
6 changes: 3 additions & 3 deletions libs/blocks/bulk-publish-v2/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const setUserData = (event) => {

const isPushedDown = async () => {
const callback = () => {
const sidekick = document.querySelector('helix-sidekick');
const sidekick = document.querySelector('aem-sidekick, helix-sidekick');
const pushdown = sidekick?.getAttribute('pushdown');
const bulkPub = document.querySelector('.bulk-publish-v2');
if (pushdown && !bulkPub.classList.contains('pushdown')) {
Expand All @@ -81,13 +81,13 @@ const isPushedDown = async () => {
const authenticate = async (tool = null) => {
isPushedDown();
const setUser = (event) => { tool.user = setUserData(event); };
const openSideKick = document.querySelector('helix-sidekick');
const openSideKick = document.querySelector('aem-sidekick, helix-sidekick');
if (openSideKick) {
openSideKick.addEventListener('statusfetched', setUser);
/* c8 ignore next 6 */
} else {
document.addEventListener('sidekick-ready', () => {
const sidekick = document.querySelector('helix-sidekick');
const sidekick = document.querySelector('aem-sidekick, helix-sidekick');
sidekick.addEventListener('statusfetched', setUser);
}, { once: true });
}
Expand Down
1 change: 0 additions & 1 deletion libs/blocks/library-config/library-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ function createList(libraries) {
list.classList.add('inset');
skLibrary.classList.add('allow-back');
loadList(type, libraries[type], list);
window.hlx?.rum.sampleRUM('click', { source: e.target });
});
});

Expand Down
1 change: 0 additions & 1 deletion libs/blocks/library-config/lists/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ export default async function loadBlocks(blocks, list, query, type) {
setTimeout(() => { e.target.classList.remove('copied'); }, 3000);
const blob = new Blob([`${BLOCK_SPACING}${containerHtml}${BLOCK_SPACING}`], { type: 'text/html' });
createCopy(blob);
window.hlx?.rum.sampleRUM('click', { source: e.target });
});
item.append(name, copy);

Expand Down
1 change: 0 additions & 1 deletion libs/blocks/library-config/lists/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default async function iconList(content, list) {
const formatted = `:${key}:`;
const blob = new Blob([formatted], { type: 'text/plain' });
createCopy(blob);
window.hlx?.rum.sampleRUM('click', { source: e.target });
});
title.append(copy);
list.append(title);
Expand Down
1 change: 0 additions & 1 deletion libs/blocks/library-config/lists/personalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const getCopyBtn = (tagName) => {
setTimeout(() => { e.target.classList.remove('copied'); }, 3000);
const blob = new Blob([tagName], { type: 'text/plain' });
createCopy(blob);
window.hlx?.rum.sampleRUM('click', { source: e.target });
});
return copy;
};
Expand Down
1 change: 0 additions & 1 deletion libs/blocks/library-config/lists/placeholders.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default async function placeholderList(content, list) {
const formatted = `{{${placeholder.key}}}`;
const blob = new Blob([formatted], { type: 'text/plain' });
createCopy(blob);
window.hlx?.rum.sampleRUM('click', { source: e.target });
});
title.append(copy);
list.append(title);
Expand Down
7 changes: 4 additions & 3 deletions libs/blocks/media/media.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ div[class*="-up"] .media .foreground > .media-row {
}

.media.qr-code .qr-button-container {
display: inline;
margin-right: var(--spacing-s);
display: inline-block;
margin: 0;
}

.media.qr-code .google-play,
Expand All @@ -179,7 +179,8 @@ div[class*="-up"] .media .foreground > .media-row {
justify-content: center;
padding: 0;
display: inline-flex;
margin: var(--spacing-xs) var(--spacing-s) 0 0;
margin-top: var(--spacing-xs);
margin-inline-end: var(--spacing-s);
}

.media.qr-code .google-play {
Expand Down
5 changes: 5 additions & 0 deletions libs/blocks/media/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,16 @@ function decorateQr(el) {
const googlePlay = text.children[(text.children.length - 2)]?.querySelector('a');
const qrImage = text.children[(text.children.length - 3)];
if (!qrImage || !appStore || !googlePlay) return;
[appStore, googlePlay].forEach(({ parentElement }) => {
parentElement.classList.add('qr-button-container');
});
qrImage.classList.add('qr-code-img');
appStore.classList.add('app-store');
appStore.textContent = '';
appStore.setAttribute('aria-label', 'Apple App Store');
googlePlay.classList.add('google-play');
googlePlay.textContent = '';
googlePlay.setAttribute('aria-label', 'Google Play Store');
}

export default async function init(el) {
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/preflight/panels/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async function setContent() {
};

getStatuses();
const sk = document.querySelector('helix-sidekick');
const sk = document.querySelector('aem-sidekick, helix-sidekick');
sk?.addEventListener('statusfetched', async () => {
getStatuses();
});
Expand Down
265 changes: 265 additions & 0 deletions libs/blocks/preflight/panels/performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { html, signal, useEffect } from '../../../deps/htm-preact.js';
import { getMetadata } from '../../../utils/utils.js';

const icons = {
pass: 'green',
fail: 'red',
empty: 'empty',
};
// TODO MEP/Personalization
// TODO mobile / tablet?
// TODO create ticket for PSI API
// TODO link to documentation directly within the sections
const text = {
lcpEl: {
key: 'lcpEl',
title: 'Valid LCP',
passed: { description: 'Valid LCP in the first section detected.' },
failed: { description: 'No LCP image or video in the first section detected.' },
},
singleBlock: {
key: 'singleBlock',
title: 'Single Block',
passed: { description: 'First section has exactly one block.' },
failed: { description: 'First section has more than one block.' },
},
imageSize: {
key: 'imageSize',
title: 'Images size',
empty: { description: 'No image as LCP element.' },
passed: { description: 'LCP image is less than 100KB.' },
failed: { description: 'LCP image is over 100KB.' },
},
videoPoster: {
key: 'videoPoster',
title: 'Videos',
empty: { description: 'No video as LCP element.' },
passed: { description: 'LCP video has a poster attribute' },
failed: { description: 'LCP video has no poster attribute.' },
},
fragments: {
key: 'fragments',
title: 'Fragments',
passed: { description: 'No fragments used within the LCP section.' },
failed: { description: 'Fragments used within the LCP section.' },
},
personalization: {
key: 'personalization',
title: 'Personalization',
passed: { description: 'Personalization is currently not enabled.' },
failed: { description: 'MEP or Target enabled.' },
},
placeholders: {
key: 'placeholders',
title: 'Placeholders',
passed: { description: 'No placeholders found within the LCP section.' },
failed: { description: 'Placeholders found within the LCP section.' },
},
icons: {
key: 'icons',
title: 'Icons',
passed: { description: 'No icons found within the LCP section.' },
failed: { description: 'Icons found within the LCP section.' },
},
};

export const config = {
items: signal([
...Object.values(text).map(({ key, title }) => ({
key,
title,
icon: icons.empty,
description: 'Loading...',
})),
]),
lcp: null,
cls: 0,
};

export const findItem = (key) => config.items.value.find((item) => item.key === key);
export const updateItem = ({ key, ...updates }) => {
const { items } = config;
items.value = items.value.map((item) => (item.key === key ? { ...item, ...updates } : item));
};

export function conditionalItemUpdate({ emptyWhen, failsWhen, key }) {
const icon = (emptyWhen && icons.empty) || (failsWhen && icons.fail) || icons.pass;
const descriptionKey = (emptyWhen && 'empty') || (failsWhen && 'failed') || 'passed';
updateItem({
key,
icon,
description: text[key][descriptionKey].description,
});
}

// TODO do we also want to check the content-length header?
// https://www.w3.org/TR/largest-contentful-paint/#largest-contentful-paint-candidate-element
// candidate’s element is a text node, or candidate’s request's response's content length
// in bytes is >= candidate’s element's effective visual size * 0.004
export async function checkImageSize() {
const { lcp } = config;
let hasValidImage = lcp?.url && !lcp.url.match('media_.*.mp4');
let blob;
let isSizeValid;
if (hasValidImage) {
try {
blob = await fetch(lcp.url).then((res) => res.blob());
isSizeValid = blob.size / 1024 <= 100;
} catch (error) {
hasValidImage = false;
}

Check warning on line 110 in libs/blocks/preflight/panels/performance.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/preflight/panels/performance.js#L109-L110

Added lines #L109 - L110 were not covered by tests
}

conditionalItemUpdate({
failsWhen: !isSizeValid,
emptyWhen: !hasValidImage,
key: text.imageSize.key,
});
}

export function checkLCP() {
const { lcp } = config;
const firstSection = document.querySelector('main > div.section');
const validLcp = lcp?.element && lcp?.url && firstSection?.contains(lcp.element);
conditionalItemUpdate({
failsWhen: !validLcp,
key: text.lcpEl.key,
description: text.lcpEl.passed.description,
});
return validLcp;
}

export const checkFragments = () => conditionalItemUpdate({
failsWhen: config.lcp.element.closest('.fragment') || config.lcp.element.closest('.section')?.querySelector('[data-path*="fragment"]'),
key: text.fragments.key,
});

export const checkPlaceholders = () => conditionalItemUpdate({
failsWhen: config.lcp.element.closest('.section').dataset.hasPlaceholders === 'true',
key: text.placeholders.key,
});

export const checkForPersonalization = () => conditionalItemUpdate({
failsWhen: getMetadata('personalization') || getMetadata('target') === 'on',
key: text.personalization.key,
});

export const checkVideosWithoutPosterAttribute = () => conditionalItemUpdate({
failsWhen: !config.lcp.element.poster,
emptyWhen: !config.lcp.url.match('media_.*.mp4'),
key: text.videoPoster.key,
});

export const checkIcons = () => conditionalItemUpdate({
failsWhen: config.lcp.element.closest('.section').querySelector('.icon-milo'),
key: text.icons.key,

Check warning on line 155 in libs/blocks/preflight/panels/performance.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/preflight/panels/performance.js#L154-L155

Added lines #L154 - L155 were not covered by tests
});

export function PerformanceItem({ icon, title, description }) {
return html` <div class="preflight-item">
<div class="result-icon ${icon}"></div>
<div class="preflight-item-text">
<p class="preflight-item-title">${title}</p>
<p class="preflight-item-description">${description}</p>
</div>
</div>`;
}

export const checkForSingleBlock = () => conditionalItemUpdate({
failsWhen: document.querySelector('main > div.section').childElementCount > 1,
key: text.singleBlock.key,
});

export const createPerformanceItem = ({
icon,
title,
description,
} = {}) => html`<${PerformanceItem}
icon=${icon}
title=${title}
description=${description}
/>`;

let clonedLcpSection;
function highlightElement(event) {
if (!config.lcp) return;
const lcpSection = config.lcp?.element.closest('.section');
const tooltip = document.querySelector('.lcp-tooltip-modal');
const { offsetHeight, offsetWidth } = lcpSection;
const scaleFactor = Math.min(500 / offsetWidth, 500 / offsetHeight);
if (!clonedLcpSection) {
clonedLcpSection = lcpSection.cloneNode(true);
clonedLcpSection.classList.add('lcp-clone');
}
Object.assign(clonedLcpSection.style, {
width: `${lcpSection.offsetWidth}px`,
height: `${lcpSection.offsetHeight}px`,
transform: `scale(${scaleFactor})`,
transformOrigin: 'top left',
});
if (!tooltip.children.length) tooltip.appendChild(clonedLcpSection);
const { top, left } = event.currentTarget.getBoundingClientRect();
Object.assign(tooltip.style, {
width: `${offsetWidth * scaleFactor}px`,
height: `${offsetHeight * scaleFactor}px`,
top: `${top + window.scrollY - offsetHeight * scaleFactor - 10}px`,
left: `${left + window.scrollX}px`,
});
document.querySelector('.lcp-tooltip-modal').classList.add('show');
}

Check warning on line 209 in libs/blocks/preflight/panels/performance.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/preflight/panels/performance.js#L184-L209

Added lines #L184 - L209 were not covered by tests

const removeHighlight = () => { document.querySelector('.lcp-tooltip-modal').classList.remove('show'); };

function observePerfMetrics() {
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
if (lastEntry) config.lcp = lastEntry;
if (!checkLCP()) {
Object.values(text).forEach(({ key }) => {
if (key === 'lcpEl') return;
updateItem({ key, description: 'No LCP element found.' });
});
return;
}
checkFragments();
checkForPersonalization();
checkVideosWithoutPosterAttribute();
checkIcons();
checkForSingleBlock();
checkPlaceholders();
Promise.all([checkImageSize()]);

Check warning on line 231 in libs/blocks/preflight/panels/performance.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/preflight/panels/performance.js#L215-L231

Added lines #L215 - L231 were not covered by tests
}).observe({ type: 'largest-contentful-paint', buffered: true });

new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach((entry) => {
if (!entry.hadRecentInput) {
config.cls += entry.value;
}
});
if (config.cls > 0) {
// TODO - Lana log? We should not have any CLS.
}

Check warning on line 243 in libs/blocks/preflight/panels/performance.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/preflight/panels/performance.js#L235-L243

Added lines #L235 - L243 were not covered by tests
}).observe({ type: 'layout-shift', buffered: true });
}

export function Panel() {
useEffect(() => observePerfMetrics(), []);

return html`
<div class="preflight-columns">
<div class="preflight-column">${config.items.value.slice(0, 4).map((item) => createPerformanceItem(item))}</div>
<div class="preflight-column">${config.items.value.slice(4, 8).map((item) => createPerformanceItem(item))}</div>
<div>Unsure on how to get this page fully into the green? Check out the <a class="performance-guidelines" href="https://milo.adobe.com/docs/authoring/performance/" target="_blank">Milo Performance Guidelines</a>.</div>
<div>
<span class="performance-element-preview" onMouseEnter=${highlightElement} onMouseLeave=${removeHighlight}>
Highlight the found LCP section
</span>
</div>
<div class="lcp-tooltip-modal"></div>
</div>
`;
}

export default Panel;
Loading
Loading