Skip to content

Commit

Permalink
MWPW-146703 [MILO][MEP][GNAV2.0] Apply MEP actions to the contents of…
Browse files Browse the repository at this point in the history
… the gnav file (#2252)

* Merging inline-block to milo branch for further updates (#2170)

* MWPW-135821 introduce custom action

- and inaugurating first custom action for replacing cards in collection

* MWPW-135821 use prefix in selector

* MWPW-135821 adding preview data

---------

Co-authored-by: Nicolas Peltier <npeltier@adobe.com>
Co-authored-by: Nicolas Peltier <1032754+npeltier@users.noreply.github.com>

* initial commit with TODO

* registerCustomAction updated

* fragment, highlight fragment and merch-card update

* chain fragment support added

* inBlock fragments with URL and path as keys

* css selectors starting to work

* Add forceInline to handleCommands

* stash

* unit tests mostly done

* fix mocking issue

* add martech-metadata to unit test mock and check for replace

* change name of registerCustomAction to registerInBlockActions

* indent issue

* remove mep useBlockCode Example

* merch-card-collection-fix

* update merch-card-collection unit test

* ignore lines that cannot be reached by unit tests

* rename CUSTOM_SELECTOR_PREFIX

* Update libs/features/personalization/personalization.js

Co-authored-by: Chris Peyer <chrischrischris@users.noreply.github.com>

* Update libs/features/personalization/personalization.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update libs/features/personalization/personalization.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* call addHash once, not twice

* Update libs/features/personalization/personalization.js

Co-authored-by: Chris Peyer <chrischrischris@users.noreply.github.com>

---------

Co-authored-by: Nicolas Peltier <npeltier@adobe.com>
Co-authored-by: Nicolas Peltier <1032754+npeltier@users.noreply.github.com>
Co-authored-by: markpadbe <markp@adobe.com>
Co-authored-by: Chris Peyer <chrischrischris@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Okan Sahin <39759830+mokimo@users.noreply.github.com>
  • Loading branch information
7 people authored May 8, 2024
1 parent de12698 commit 2caf32a
Show file tree
Hide file tree
Showing 18 changed files with 317 additions and 63 deletions.
2 changes: 1 addition & 1 deletion libs/blocks/fragment/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default async function init(a) {
const resp = await customFetch({ resource: `${a.href}.plain.html`, withCacheRules: true })
.catch(() => ({}));

if (!resp.ok) {
if (!resp?.ok) {
window.lana?.log(`Could not get fragment: ${a.href}.plain.html`);
return;
}
Expand Down
18 changes: 10 additions & 8 deletions libs/blocks/global-navigation/global-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
toFragment,
trigger,
yieldToMain,
addMepHighlight,
} from './utilities/utilities.js';

import { replaceKey, replaceKeyArray } from '../../features/placeholders.js';
Expand Down Expand Up @@ -908,16 +909,16 @@ class Gnav {
observer.observe(dropdownTrigger, { attributeFilter: ['aria-expanded'] });

delayDropdownDecoration({ template: triggerTemplate });
return triggerTemplate;
return addMepHighlight(triggerTemplate, item);
}
case 'primaryCta':
case 'secondaryCta':
// Remove its 'em' or 'strong' wrapper
item.parentElement.replaceWith(item);

return toFragment`<div class="feds-navItem feds-navItem--centered">
return addMepHighlight(toFragment`<div class="feds-navItem feds-navItem--centered">
${decorateCta({ elem: item, type: itemType, index: index + 1 })}
</div>`;
</div>`, item);
case 'link': {
const linkElem = item.querySelector('a');
linkElem.className = 'feds-navLink';
Expand All @@ -934,16 +935,17 @@ class Gnav {
<div class="feds-navItem${activeModifier}">
${linkElem}
</div>`;
return linkTemplate;
return addMepHighlight(linkTemplate, item);
}
case 'text':
return toFragment`<div class="feds-navItem feds-navItem--centered">
return addMepHighlight(toFragment`<div class="feds-navItem feds-navItem--centered">
${item.textContent}
</div>`;
</div>`, item);
default:
return toFragment`<div class="feds-navItem feds-navItem--centered">
/* c8 ignore next 3 */
return addMepHighlight(toFragment`<div class="feds-navItem feds-navItem--centered">
${item}
</div>`;
</div>`, item);
}
};

Expand Down
3 changes: 3 additions & 0 deletions libs/blocks/global-navigation/utilities/menu/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
toFragment,
trigger,
yieldToMain,
addMepHighlight,
} from '../utilities.js';

const decorateHeadline = (elem, index) => {
Expand Down Expand Up @@ -321,6 +322,8 @@ const decorateMenu = (config) => logErrorFor(async () => {
${menuContent}
</div>
</div>`;
addMepHighlight(menuTemplate, content);

decorateCrossCloudMenu(menuTemplate);

await decorateColumns({ content: menuContent });
Expand Down
25 changes: 23 additions & 2 deletions libs/blocks/global-navigation/utilities/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ export const logErrorFor = async (fn, message, tags) => {
}
};

export function addMepHighlight(el, source) {
let { manifestId } = source.dataset;
if (!manifestId) {
const closestManifestId = source?.closest('[data-manifest-id]');
if (closestManifestId) manifestId = closestManifestId.dataset.manifestId;
}
if (manifestId) el.dataset.manifestId = manifestId;
return el;
}

export function toFragment(htmlStrings, ...values) {
const templateStr = htmlStrings.reduce((acc, htmlString, index) => {
if (values[index] instanceof HTMLElement) {
Expand Down Expand Up @@ -313,11 +323,22 @@ export function trigger({ element, event, type } = {}) {
export const yieldToMain = () => new Promise((resolve) => { setTimeout(resolve, 0); });

export async function fetchAndProcessPlainHtml({ url, shouldDecorateLinks = true } = {}) {
const path = getFederatedUrl(url);
let path = getFederatedUrl(url);
const mepGnav = getConfig()?.mep?.inBlock?.['global-navigation'];
const mepFragment = mepGnav?.fragments?.[path];
if (mepFragment && mepFragment.action === 'replace') {
path = mepFragment.target;
}
const res = await fetch(path.replace(/(\.html$|$)/, '.plain.html'));
const text = await res.text();
const { body } = new DOMParser().parseFromString(text, 'text/html');

if (mepFragment?.manifestId) body.dataset.manifestId = mepFragment.manifestId;
const commands = mepGnav?.commands;
if (commands?.length) {
const { handleCommands, deleteMarkedEls } = await import('../../../features/personalization/personalization.js');
handleCommands(commands, commands[0].manifestId, body, true);
deleteMarkedEls(body);
}
const inlineFrags = [...body.querySelectorAll('a[href*="#_inline"]')];
if (inlineFrags.length) {
const { default: loadInlineFrags } = await import('../../fragment/fragment.js');
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/merch-card-collection/merch-card-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export default async function init(el) {
}

const cardsRoot = await cardsRootPromise;
const overridePromises = mep?.custom?.[BLOCK_NAME]?.map(
const overridePromises = mep?.inBlock?.[BLOCK_NAME]?.commands.map(
(action) => fetchOverrideCard(action, config),
);
const overrides = await overrideCards(cardsRoot, overridePromises, config);
Expand Down
93 changes: 72 additions & 21 deletions libs/features/personalization/personalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '../../utils/utils.js';
import { getEntitlementMap } from './entitlements.js';

/* c20 ignore start */
/* c8 ignore start */
const PHONE_SIZE = window.screen.width < 768 || window.screen.height < 768;
export const PERSONALIZATION_TAGS = {
all: () => true,
Expand All @@ -25,12 +25,13 @@ export const PERSONALIZATION_TAGS = {
loggedin: () => !!window.adobeIMS?.isSignedInUser(),
};
const PERSONALIZATION_KEYS = Object.keys(PERSONALIZATION_TAGS);
/* c20 ignore stop */
/* c8 ignore stop */

const CLASS_EL_DELETE = 'p13n-deleted';
const CLASS_EL_REPLACE = 'p13n-replaced';
const COLUMN_NOT_OPERATOR = 'not';
const TARGET_EXP_PREFIX = 'target-';
const INLINE_HASH = '_inline';
const PAGE_URL = new URL(window.location.href);

export const TRACKED_MANIFEST_TYPE = 'personalization';
Expand All @@ -51,11 +52,11 @@ const DATA_TYPE = {
TEXT: 'text',
};

export const CUSTOM_SELECTOR_PREFIX = 'in-block:';
const IN_BLOCK_SELECTOR_PREFIX = 'in-block:';

export const appendJsonExt = (path) => (path.endsWith('.json') ? path : `${path}.json`);

export const normalizePath = (p) => {
export const normalizePath = (p, localize = true) => {
let path = p;

if (!path?.includes('/')) {
Expand All @@ -70,7 +71,11 @@ export const normalizePath = (p) => {
try {
const url = new URL(path);
const firstFolder = url.pathname.split('/')[1];
if (config.locale.ietf === 'en-US' || url.hash === '#_dnt' || firstFolder in config.locales || path.includes('.json')) {
if (!localize
|| config.locale.ietf === 'en-US'
|| url.hash.includes('#_dnt')
|| firstFolder in config.locales
|| path.includes('.json')) {
path = url.pathname;
} else {
path = `${config.locale.prefix}${url.pathname}`;
Expand Down Expand Up @@ -156,7 +161,7 @@ const COMMANDS = {
};

function checkSelectorType(selector) {
return selector?.includes('/fragments/') ? 'fragment' : 'css';
return selector?.startsWith('/') || selector?.startsWith('http') ? 'fragment' : 'css';
}

const fetchData = async (url, type = DATA_TYPE.JSON) => {
Expand Down Expand Up @@ -291,13 +296,44 @@ function getSection(rootEl, idx) {
: rootEl.querySelector(`:scope > div:nth-child(${idx})`);
}

function registerCustomAction(cmd, manifestId) {
const { action, selector, target } = cmd;
function registerInBlockActions(cmd, manifestId) {
const { action, target, selector } = cmd;
const command = { action, target, manifestId };

const blockAndSelector = selector.substring(IN_BLOCK_SELECTOR_PREFIX.length).trim().split(/\s+/);
const [blockName] = blockAndSelector;

const config = getConfig();
const blockName = selector.substring(CUSTOM_SELECTOR_PREFIX.length);
config.mep.custom ??= {};
config.mep.custom[blockName] ??= [];
config.mep.custom[blockName].push({ manifestId, action, target });
config.mep.inBlock ??= {};
config.mep.inBlock[blockName] ??= {};

let blockSelector;
if (blockAndSelector.length > 1) {
blockSelector = blockAndSelector.slice(1).join(' ');
command.selector = blockSelector;
if (checkSelectorType(blockSelector) === 'fragment') {
config.mep.inBlock[blockName].fragments ??= {};
const { fragments } = config.mep.inBlock[blockName];
delete command.selector;
if (blockSelector in fragments) return;

// eslint-disable-next-line no-restricted-syntax
for (const key in fragments) {
if (fragments[key].target === blockSelector) fragments[key] = command;
}
fragments[blockSelector] = command;

blockSelector = normalizePath(blockSelector);
// eslint-disable-next-line no-restricted-syntax
for (const key in fragments) {
if (fragments[key].target === blockSelector) fragments[key] = command;
}
fragments[blockSelector] = command;
return;
}
}
config.mep.inBlock[blockName].commands ??= [];
config.mep.inBlock[blockName].commands.push(command);
}

function getSelectedElement(selector, action, rootEl) {
Expand All @@ -309,7 +345,7 @@ function getSelectedElement(selector, action, rootEl) {
}
if (checkSelectorType(selector) === 'fragment') {
try {
const fragment = document.querySelector(`a[href*="${normalizePath(selector)}"]`);
const fragment = document.querySelector(`a[href*="${normalizePath(selector, false)}"], a[href*="${normalizePath(selector, true)}"]`);
if (fragment) return fragment.parentNode;
return null;
} catch (e) {
Expand Down Expand Up @@ -364,19 +400,34 @@ function getSelectedElement(selector, action, rootEl) {
return selectedEl;
}

function handleCommands(commands, manifestId, rootEl = document) {
const addHash = (url, newHash) => {
if (!newHash) return url;
try {
const { origin, pathname, search } = new URL(url);
return `${origin}${pathname}${search}#${newHash}`;
} catch (e) {
return `${url}#${newHash}`;
}
};

export function handleCommands(commands, manifestId, rootEl = document, forceInline = false) {
commands.forEach((cmd) => {
const { action, selector, target } = cmd;
if (selector.startsWith(CUSTOM_SELECTOR_PREFIX)) {
registerCustomAction(cmd, manifestId);
const { action, selector, target: trgt } = cmd;
const target = forceInline ? addHash(trgt, INLINE_HASH) : trgt;
if (selector.startsWith(IN_BLOCK_SELECTOR_PREFIX)) {
registerInBlockActions(cmd, manifestId);
return;
}

if (action in COMMANDS) {
const el = getSelectedElement(selector, action, rootEl);
COMMANDS[action](el, target, manifestId);
} else if (action in CREATE_CMDS) {
const el = getSelectedElement(selector, action, rootEl);
el?.insertAdjacentElement(CREATE_CMDS[action], createFrag(el, target, manifestId));
el?.insertAdjacentElement(
CREATE_CMDS[action],
createFrag(el, target, manifestId),
);
} else {
/* c8 ignore next 2 */
console.log('Invalid command found: ', cmd);
Expand Down Expand Up @@ -638,8 +689,8 @@ export async function getPersConfig(info, override = false) {
return config;
}

const deleteMarkedEls = () => {
[...document.querySelectorAll(`.${CLASS_EL_DELETE}`)]
export const deleteMarkedEls = (rootEl = document) => {
[...rootEl.querySelectorAll(`.${CLASS_EL_DELETE}`)]
.forEach((el) => el.remove());
};

Expand Down Expand Up @@ -761,7 +812,7 @@ export async function applyPers(manifests) {
const config = getConfig();

if (!manifests?.length) return;
if (!config?.mep) config.mep = {};
config.mep ??= {};
config.mep.handleFragmentCommand = handleFragmentCommand;
let experiments = manifests;
for (let i = 0; i < experiments.length; i += 1) {
Expand Down
4 changes: 2 additions & 2 deletions libs/features/personalization/preview.css
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,12 @@ input#new-manifest {
}

/* mepHighlight */
body[data-mep-highlight='true'] [data-manifest-id],
body[data-mep-highlight='true'] [data-manifest-id]:not(section.feds-navItem),
body[data-mep-highlight='true'] section.feds-navItem[data-manifest-id] > *,
body[data-mep-highlight='true'] [data-code-manifest-id],
body[data-mep-highlight='true'] [data-removed-manifest-id] {
outline: 3px #26ceef dashed !important;
box-shadow: 3px 3px 13px 0 #8f8f8f !important;
position: relative;
}

body[data-mep-highlight='true'] [data-code-manifest-id] {
Expand Down
21 changes: 21 additions & 0 deletions test/blocks/global-navigation/mocks/mep-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default {
inBlock: {
'global-navigation': {
commands: [
{
action: 'replace',
target: '/test/blocks/global-navigation/mocks/mep-large-menu-table',
manifestId: 'manifest.json',
selector: '.large-menu',
},
],
fragments: {
'/old/navigation': {
action: 'replace',
target: '/test/blocks/global-navigation/mocks/mep-global-navigation',
manifestId: 'manifest.json',
},
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

<div>
<div id="only-in-new-menu" class="gnav-brand logo">
<div>
<div><a href="https://www.adobe.com/">Adobe</a></div>
</div>
</div>
</div>
<div>
<div class="large-menu section">
<div>
<div>
<h2 id="creative-cloud"><a href="/cc-shared/gnav/section-menu-creativecloud">Creative Cloud</a></h2>
</div>
</div>
</div>
</div>
<div>
<div class="large-menu section">
<div>
<div>
<h2 id="pdf--e-signatures"><a href="/cc-shared/gnav/section-menu-acrobat">PDF &#x26; E-signatures</a></h2>
</div>
</div>
</div>
</div>
<div>
<div class="profile"></div>
</div>
<div>
<div class="martech-metadata">
<div>
<div>
buy-now
</div>
<div>
Buy Now
</div>
</div>
</div>
</div>
9 changes: 9 additions & 0 deletions test/blocks/global-navigation/mocks/mep-large-menu-table
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div>
<div class="large-menu section">
<div>
<div>
<h2 id="mep-large-menu-table-title"><a href="/cc-shared/gnav/section-menu-creativecloud">MEP Large Menu Table Title</a></h2>
</div>
</div>
</div>
</div>
Loading

0 comments on commit 2caf32a

Please sign in to comment.