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

MWPW-146743 Improve Article Header Performance #2577

Merged
merged 1 commit into from
Jul 23, 2024
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
108 changes: 72 additions & 36 deletions libs/blocks/article-header/article-header.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { createTag, getMetadata, getConfig } from '../../utils/utils.js';
import { copyToClipboard } from '../../utils/tools.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is copyToClipboard in tools.js used outside of article header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not in milo but I didn't want to delete it in case a consumer is using it.

import { loadTaxonomy, getLinkForTopic, getTaxonomyModule } from '../article-feed/article-helpers.js';
import { replaceKey } from '../../features/placeholders.js';
import { fetchIcons } from '../../features/icons/icons.js';
import { buildFigure } from '../figure/figure.js';

let copyText = 'Copied to clipboard';

async function validateAuthorUrl(url) {
if (!url) return null;

const resp = await fetch(`${url.toLowerCase()}.plain.html`);
if (!resp?.ok) {
console.log(`Could not retrieve metadata for ${url}`);
/* c8 ignore next 3 */
window.lana?.log(`Could not retrieve metadata for ${url}`, { tags: 'errorType=warn,module=article-header' });
return null;
}

Expand All @@ -34,7 +33,6 @@ function openPopup(e) {

async function buildAuthorInfo(authorEl, bylineContainer) {
const { href, textContent } = authorEl;

const config = getConfig();
const base = config.miloLibs || config.codeRoot;
const authorImg = createTag('div', { class: 'article-author-image' });
Expand All @@ -57,6 +55,7 @@ async function buildAuthorInfo(authorEl, bylineContainer) {
authorImg.style.backgroundImage = 'none';
});
img.addEventListener('error', () => {
/* c8 ignore next 1 */
img.remove();
});
} else {
Expand All @@ -65,33 +64,63 @@ async function buildAuthorInfo(authorEl, bylineContainer) {
}
}

async function copyToClipboard(button, copyTxt) {
try {
await navigator.clipboard.writeText(window.location.href);
button.setAttribute('title', copyTxt);
button.setAttribute('aria-label', copyTxt);

const tooltip = createTag('div', { role: 'status', 'aria-live': 'polite', class: 'copied-to-clipboard' }, copyTxt);
button.append(tooltip);

setTimeout(() => {
/* c8 ignore next 1 */
tooltip.remove();
}, 3000);
button.classList.remove('copy-failure');
button.classList.add('copy-success');
} catch (e) {
button.classList.add('copy-failure');
button.classList.remove('copy-success');
}
}

async function updateShareText(shareBlock) {
const { replaceKey } = await import('../../features/placeholders.js');
const config = getConfig();
const labels = [
`${await replaceKey('share-twitter', config)}`,
`${await replaceKey('share-linkedin', config)}`,
`${await replaceKey('share-facebook', config)}`,
`${await replaceKey('copy-to-clipboard', config)}`,
];
const shareLinks = shareBlock.querySelectorAll('a');
[...shareLinks].forEach((el, index) => el.setAttribute('aria-label', labels[index]));
copyText = await replaceKey('copied-to-clipboard', config);
}

async function buildSharing() {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent(document.querySelector('h1').textContent);
const description = encodeURIComponent(getMetadata('description'));

const platformMap = {
twitter: {
'data-href': `https://www.twitter.com/share?&url=${url}&text=${title}`,
alt: `${await replaceKey('share-twitter', getConfig())}`,
'aria-label': `${await replaceKey('share-twitter', getConfig())}`,
'aria-label': 'share twitter',
},
linkedin: {
'data-type': 'LinkedIn',
'data-href': `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${title}&summary=${description || ''}`,
alt: `${await replaceKey('share-linkedin', getConfig())}`,
'aria-label': `${await replaceKey('share-linkedin', getConfig())}`,
'aria-label': 'share linkedin',
},
facebook: {
'data-type': 'Facebook',
'data-href': `https://www.facebook.com/sharer/sharer.php?u=${url}`,
alt: `${await replaceKey('share-facebook', getConfig())}`,
'aria-label': `${await replaceKey('share-facebook', getConfig())}`,
'aria-label': 'share facebook',
},
link: {
id: 'copy-to-clipboard',
alt: `${await replaceKey('copy-to-clipboard', getConfig())}`,
'aria-label': `${await replaceKey('copy-to-clipboard', getConfig())}`,
'aria-label': 'copy to clipboard',
},
};

Expand All @@ -115,37 +144,46 @@ async function buildSharing() {
link.addEventListener('click', openPopup);
});
const copyButton = sharing.querySelector('#copy-to-clipboard');
copyButton.addEventListener('click', async () => {
const copyText = await replaceKey('copied-to-clipboard', getConfig());
await copyToClipboard(copyButton, copyText);
});
copyButton.addEventListener('click', () => copyToClipboard(copyButton, copyText));

return sharing;
}

async function validateDate(date) {
function validateDate(date) {
const { env } = getConfig();
if (env?.name === 'prod') return;
if (date && !/^[0-1]\d{1}-[0-3]\d{1}-[2]\d{3}$/.test(date.textContent.trim())) {
// match publication date to MM-DD-YYYY format
date.classList.add('article-date-invalid');
date.setAttribute('title', await replaceKey('invalid-date', getConfig()));
date.setAttribute('title', 'Invalid Date Format: Must be MM-DD-YYYY');
}
}

export default async function init(blockEl) {
if (!getTaxonomyModule()) {
await loadTaxonomy();
function decorateFigure(el) {
el.classList.add('article-feature-image');
const picture = el.querySelector('picture');
const caption = el.querySelector('em');
const figure = document.createElement('figure');

if (caption) {
caption.classList.add('caption');
const figcaption = document.createElement('figcaption');
figcaption.append(caption);
figure.append(figcaption);
}

const childrenEls = Array.from(blockEl.children);
if (childrenEls.length < 4) {
console.warn('Block does not have enough children');
}
figure.classList.add('figure-feature');
figure.prepend(picture);
el.prepend(figure);
el.lastElementChild.remove();
}

export default async function init(blockEl) {
const childrenEls = Array.from(blockEl.children);
const categoryContainer = childrenEls[0];
const categoryEl = categoryContainer.firstElementChild.firstElementChild;
if (categoryEl?.textContent) {
const { getTaxonomyModule, loadTaxonomy, getLinkForTopic } = await import('../article-feed/article-helpers.js');
if (!getTaxonomyModule()) await loadTaxonomy();
const categoryTag = getLinkForTopic(categoryEl.textContent);
categoryEl.innerHTML = categoryTag;
}
Expand All @@ -162,19 +200,17 @@ export default async function init(blockEl) {
const authorEl = authorContainer.querySelector('a');
authorContainer.classList.add('article-author');

await buildAuthorInfo(authorEl, bylineContainer);
buildAuthorInfo(authorEl, bylineContainer);

const date = bylineContainer.querySelector('.article-byline-info > p:last-child');
date.classList.add('article-date');
await validateDate(date);
validateDate(date);

const shareBlock = await buildSharing();
bylineContainer.append(shareBlock);

const featureImgContainer = childrenEls[3];
featureImgContainer.classList.add('article-feature-image');
const featureFigEl = buildFigure(featureImgContainer.firstElementChild);
featureFigEl.classList.add('figure-feature');
featureImgContainer.prepend(featureFigEl);
featureImgContainer.lastElementChild.remove();
decorateFigure(featureImgContainer);

document.addEventListener('milo:deferred', () => updateShareText(shareBlock));
}
18 changes: 18 additions & 0 deletions test/blocks/article-header/article-header.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ describe('article header', () => {
stub.restore();
});

it('updates share text after deferred event', async () => {
document.dispatchEvent(new Event('milo:deferred'));
const shareLink = document.querySelector('.article-byline-sharing a');
await delay(100);
expect(shareLink.getAttribute('aria-label')).to.equal('Click to share on twitter');
});

it('should add copy-failure class to link if the copy fails', async () => {
const writeTextStub = sinon.stub(navigator.clipboard, 'writeText').rejects();
const copyLink = document.body.querySelector('.article-byline-sharing #copy-to-clipboard');
Expand All @@ -72,6 +79,17 @@ describe('article header', () => {
writeTextStub.restore();
});

it('updates copy text after deferred event', async () => {
document.dispatchEvent(new Event('milo:deferred'));
const writeTextStub = sinon.stub(navigator.clipboard, 'writeText').resolves();
const copyLink = document.body.querySelector('.article-byline-sharing #copy-to-clipboard');
sinon.fake();
copyLink.click();
const tooltip = await waitForElement('.copied-to-clipboard');
expect(tooltip.textContent).to.equal('Link copied to clipboard');
writeTextStub.restore();
});

it('sets default taxonomy path to "topics"', () => {
const categoryLink = document.querySelector('.article-category a');
expect(categoryLink.href.includes('/topics/')).to.be.true;
Expand Down
1 change: 1 addition & 0 deletions test/blocks/article-header/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ <h1 id="celebrating-a-special-milestone-10-things-you-might-not-know-about-adobe
src="/en/publish/2022/12/05/media_165ac27f7158c040400bd6666c6086a9c0b3c29cc.jpeg?width=750&amp;format=jpeg&amp;optimize=medium"
loading="eager" alt="Virtual background in red and blue."> -->
</picture>
<em>Caption</em>
</p>
</div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions test/blocks/article-header/mocks/placeholders.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
{
"key": "no-results",
"value": "No results found"
},
{
"key": "share-twitter",
"value": "Click to share on twitter"
},
{
"key": "copied-to-clipboard",
"value": "Link copied to clipboard"
}
],
":type": "sheet"
Expand Down
Loading