diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..b6f27f13
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..097b5acb
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,53 @@
+# EDS Release Notes
+
+Source: [PR #579 — EDS release March 30th, 2026](https://github.com/AdobeDocs/adp-devsite/pull/579) (`stage` → `main`).
+
+## 3/30/26 EDS Release:
+
+- GetCredential Fixes:
+
+ - **Fix:** credential design issue
+
+ - **Fix:** sign-in component problem
+
+ - **Fix:** component loading issue
+
+ - **Fix:** populate `templateData.apis` from the template response before credential creation (template install / license configuration)
+
+ - `fetchTemplateEntitlement()` loads the full template, including APIs with `licenseConfigs`, but that data was not always stored on `templateData`, leaving `templateData.apis` undefined.
+ - The `/install` endpoint could receive an empty `apis` array and fail with errors such as “Service CJA SDK requires selection of a product”.
+ - Ensures `fetchTemplateEntitlement()` runs in the relevant auth flows so API data is available, and maps only the required `licenseConfig` fields (`id`, `productId`, `op`) in the install request payload.
+
+- **Fix:** “On this page” styles
+
+- Data Playground Fixes:
+
+ - **Fix:** code playground metadata and script updates
+
+ - [DEVSITE-2304](https://jira.corp.adobe.com/browse/DEVSITE-2304)
+
+- **Fix:** dropdown width, selector, and description max width
+
+ - [DEVSITE-2295](https://jira.corp.adobe.com/browse/DEVSITE-2295), [DEVSITE-2305](https://jira.corp.adobe.com/browse/DEVSITE-2305)
+
+- **Feat:** enforce Node 24+ via `.npmrc` and the `engines` field
+
+ - [DEVSITE-2296](https://jira.corp.adobe.com/browse/DEVSITE-2296)
+
+- **Fix:** reverse image and text order in columns
+
+ - [DEVSITE-2301](https://jira.corp.adobe.com/browse/DEVSITE-2301), [DEVSITE-2303](https://jira.corp.adobe.com/browse/DEVSITE-2303)
+
+- **Fix:** rename `B_app_PremierePro.svg` to `premierepro.svg`
+
+- Embed and media fixes:
+
+ - **Fix:** embed layout and related design issues
+
+ - [DEVSITE-2276](https://jira.corp.adobe.com/browse/DEVSITE-2276), [DEVSITE-2303](https://jira.corp.adobe.com/browse/DEVSITE-2303)
+
+ - **Fix:** video layout (including extra letterboxing) and shorts vs video behavior
+
+ - [DEVSITE-2276](https://jira.corp.adobe.com/browse/DEVSITE-2276), [DEVSITE-2303](https://jira.corp.adobe.com/browse/DEVSITE-2303)
+
+ - **Fix:** remove stray console logging in embed/video paths
diff --git a/hlx_statics/blocks/columns/columns.css b/hlx_statics/blocks/columns/columns.css
index e639396e..6a0003ce 100644
--- a/hlx_statics/blocks/columns/columns.css
+++ b/hlx_statics/blocks/columns/columns.css
@@ -28,6 +28,14 @@ main div.columns-wrapper div.columns > div:nth-child(even) {
flex-direction: row-reverse;
}
+main div.columns-wrapper div.columns.isReversed > div:nth-child(odd) {
+ flex-direction: row-reverse;
+}
+
+main div.columns-wrapper div.columns.isReversed > div:nth-child(even) {
+ flex-direction: row;
+}
+
main div.columns-wrapper .checkmark ul {
list-style: none;
font-size: 16px;
diff --git a/hlx_statics/blocks/columns/columns.js b/hlx_statics/blocks/columns/columns.js
index 121a9865..7b11739e 100644
--- a/hlx_statics/blocks/columns/columns.js
+++ b/hlx_statics/blocks/columns/columns.js
@@ -33,6 +33,8 @@ function processImages(block) {
export default async function decorate(block) {
const container = getBlockSectionContainer(block);
const variant = block.getAttribute('data-variant')
+ const isReversed = block.getAttribute('data-isreversed') === 'true';
+ isReversed && block.classList.add('isReversed');
const isDocs = IS_DEV_DOCS;
isDocs && block.classList.add('isDocs')
diff --git a/hlx_statics/blocks/embed/embed.css b/hlx_statics/blocks/embed/embed.css
index 126b9a08..f663133e 100644
--- a/hlx_statics/blocks/embed/embed.css
+++ b/hlx_statics/blocks/embed/embed.css
@@ -2,6 +2,21 @@ main div.embed-wrapper:has(.embed.background-color-white) {
background-color: white;
}
+main div.embed-wrapper .ytShort {
+ position: relative;
+ width: 315px;
+ height: 560px;
+ aspect-ratio: 9 / 16;
+ margin: 0 auto;
+}
+
+@media screen and (max-width: 700px) {
+ main div.embed-wrapper .ytShort {
+ width: 100%;
+ max-width: 315px;
+ }
+}
+
main div.embed-wrapper {
align-items: center;
justify-content: center;
diff --git a/hlx_statics/blocks/embed/embed.js b/hlx_statics/blocks/embed/embed.js
index 6cfb7c76..0cc47794 100644
--- a/hlx_statics/blocks/embed/embed.js
+++ b/hlx_statics/blocks/embed/embed.js
@@ -40,15 +40,15 @@ return embedHTML;
const embedYTShort = (url, loop, controls, vidTitle) => {
const [, videoCode] = url.pathname.split('/shorts/');
- return `
-
+ return `
+ allowfullscreen
+ ${vidTitle ? `title="${vidTitle}"` : `title="Content from YouTube"`}
+ loading="lazy">
+
`;
};
@@ -82,7 +82,7 @@ const embedTikTok = (url, loop, controls, vidTitle) => {
`;
}
-const embedYoutube = (url, loop, controls, vidTitle) => {
+const embedYoutube = (url, loop, controls, vidTitle, isShort) => {
let vid;
const embed = url.pathname;
@@ -100,6 +100,19 @@ const embedYoutube = (url, loop, controls, vidTitle) => {
if (embed.includes('playlist')) {
return embedYTPlaylist(url, loop, controls, vidTitle);
}
+ if (isShort && vid) {
+
+ return `
+
+
`;
+ }
if (vid) {
return `
@@ -173,7 +186,8 @@ const loadEmbed = (block, link) => {
let controls = 1;
const attrs = block?.parentElement?.parentElement?.attributes;
const vidTitle = attrs?.getNamedItem('data-videotitle')?.value;
- // changes the values of these attributes based on section metadata
+ const isShort = block.getAttribute('data-short')?.toLowerCase() == 'true';
+ // changes the values of these attributes based on section metadata
if (attrs?.getNamedItem('data-loop'))
{
loop = (attrs.getNamedItem('data-loop').value.toLowerCase() === 'true') ? 1: 0;
@@ -184,7 +198,7 @@ const loadEmbed = (block, link) => {
}
const url = new URL(link);
if (config) {
- block.innerHTML = config.embed(url, loop, controls, vidTitle);
+ block.innerHTML = config.embed(url, loop, controls, vidTitle, isShort);
block.classList.add('block', 'embed', `embed-${config.match[0]}`);
} else {
block.innerHTML = getDefaultEmbed(url);
diff --git a/hlx_statics/blocks/getcredential/getcredential.css b/hlx_statics/blocks/getcredential/getcredential.css
index 4799cc33..11c42a03 100644
--- a/hlx_statics/blocks/getcredential/getcredential.css
+++ b/hlx_statics/blocks/getcredential/getcredential.css
@@ -207,14 +207,12 @@
}
.credential-form {
- flex: 1;
- width: 40%;
+ width: 100%;
max-width: 600px;
}
.return-left-content,
.return-right-content,
-.card-content,
.project-title-group {
flex: 1;
}
@@ -944,6 +942,7 @@
.card-content {
gap: 0;
+ width: 100%;
max-width: 560px;
}
diff --git a/hlx_statics/blocks/getcredential/getcredential.js b/hlx_statics/blocks/getcredential/getcredential.js
index 8a10a953..f09d06d0 100644
--- a/hlx_statics/blocks/getcredential/getcredential.js
+++ b/hlx_statics/blocks/getcredential/getcredential.js
@@ -193,13 +193,13 @@ async function createCredential() {
throw new Error('Template configuration missing');
}
- // Prepare APIs data
+ // Prepare APIs data with licenseConfigs from the template response
const apis = templateData.apis?.map(api => ({
code: api.code,
credentialType: api.credentialType,
flowType: api.flowType,
licenseConfigs: Array.isArray(api.licenseConfigs) && api.licenseConfigs.length > 0
- ? [{ ...api.licenseConfigs[0], op: 'add' }]
+ ? [{ id: api.licenseConfigs[0].id, productId: api.licenseConfigs[0].productId, op: 'add' }]
: [],
})) || [];
@@ -343,6 +343,9 @@ async function fetchTemplateEntitlement() {
const response = await fetch(url, { method: 'GET', headers });
if (!response.ok) return null;
const data = await response.json();
+ if (templateData && Array.isArray(data?.apis)) {
+ templateData.apis = data.apis;
+ }
const userEntitled = data?.userEntitled !== false;
const orgEntitled = data?.orgEntitled !== false;
const disEntitledReasons = data?.disEntitledReasons;
@@ -1346,6 +1349,20 @@ function createSignInContent(config) {
return signInWrapper;
}
+const ensureSignedInContextAndOrganizations = async () => {
+ if (!window.adobeIMS?.isSignedInUser?.()) return;
+ if (organizationsData && organizationsData.length > 0 && selectedOrganization) return;
+ try {
+ const orgs = await fetchOrganizations();
+ if (orgs && orgs.length > 0) {
+ organizationsData = orgs;
+ await switchOrganization(null);
+ }
+ } catch (e) {
+ // Keep existing fallback org selection when org fetch fails.
+ }
+};
+
// ============================================================================
// RETURN PAGE (Previously Created Projects)
// ============================================================================
@@ -2106,24 +2123,33 @@ export default async function decorate(block) {
}).catch(showFormFromLoading);
};
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+ const fetchTemplateEntitlementWithRetry = async (attempts = 4, delayMs = 300) => {
+ for (let i = 0; i < attempts; i += 1) {
+ const entitlement = await fetchTemplateEntitlement();
+ if (entitlement) return entitlement;
+ if (i < attempts - 1) {
+ await sleep(delayMs);
+ }
+ }
+ return null;
+ };
+
// Re-render page after organization change: show loading, then entitlement check (if Request Access) and show correct view.
renderPageAfterOrgChange = (sourceContainer) => {
setLoadingText(loadingContainer, 'Loading...');
navigateTo(sourceContainer, loadingContainer);
- if (requestAccessContainer) {
- fetchTemplateEntitlement().then(async (entitlement) => {
- if (entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
- const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
- const profile = await window.adobeIMS?.getProfile().catch(() => null);
- updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
- navigateTo(loadingContainer, requestAccessContainer);
- } else {
- runFormOrReturnFlow();
- }
- }).catch(() => runFormOrReturnFlow());
- } else {
- runFormOrReturnFlow();
- }
+ fetchTemplateEntitlementWithRetry().then(async (entitlement) => {
+ if (requestAccessContainer && entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
+ const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
+ const profile = await window.adobeIMS?.getProfile().catch(() => null);
+ updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
+ navigateTo(loadingContainer, requestAccessContainer);
+ } else {
+ runFormOrReturnFlow();
+ }
+ }).catch(() => runFormOrReturnFlow());
};
// Create success card container
@@ -2337,17 +2363,16 @@ export default async function decorate(block) {
navigateTo(loadingContainer, requestAccessContainer);
return;
}
- // If Request Access config exists, check entitlement first (same as React: !template.userEntitled || !template.orgEntitled -> RequestAccess)
- if (requestAccessContainer) {
- const entitlement = await fetchTemplateEntitlement();
- if (entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
- lastRequestAccessEntitlement = entitlement;
- const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
- const profile = await window.adobeIMS?.getProfile().catch(() => null);
- updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
- navigateTo(loadingContainer, requestAccessContainer);
- return;
- }
+ // Fetch template data (populates templateData.apis for credential creation)
+ // and check entitlement (same as React: !template.userEntitled || !template.orgEntitled -> RequestAccess)
+ const entitlement = await fetchTemplateEntitlement();
+ if (requestAccessContainer && entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
+ lastRequestAccessEntitlement = entitlement;
+ const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
+ const profile = await window.adobeIMS?.getProfile().catch(() => null);
+ updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
+ navigateTo(loadingContainer, requestAccessContainer);
+ return;
}
runFormOrReturnFlow();
});
@@ -2361,8 +2386,40 @@ export default async function decorate(block) {
}
};
+ let signInStateWatcherInterval = null;
+ let signInStateWatcherTimeout = null;
+ let isResolvingSignedInState = false;
+ const stopSignInStateWatcher = () => {
+ if (signInStateWatcherInterval) {
+ clearInterval(signInStateWatcherInterval);
+ signInStateWatcherInterval = null;
+ }
+ if (signInStateWatcherTimeout) {
+ clearTimeout(signInStateWatcherTimeout);
+ signInStateWatcherTimeout = null;
+ }
+ };
+ const startSignInStateWatcher = () => {
+ stopSignInStateWatcher();
+ const trySyncSignedInState = () => {
+ if (signInContainer?.classList.contains('hidden')) {
+ stopSignInStateWatcher();
+ return;
+ }
+ if (window.adobeIMS?.isSignedInUser?.()) {
+ void checkAlreadySignedIn();
+ stopSignInStateWatcher();
+ }
+ };
+ trySyncSignedInState();
+ signInStateWatcherInterval = setInterval(trySyncSignedInState, 250);
+ signInStateWatcherTimeout = setTimeout(() => {
+ stopSignInStateWatcher();
+ }, 10000);
+ };
+
// Check if user is already signed in when page loads
- const checkAlreadySignedIn = () => {
+ const checkAlreadySignedIn = async () => {
// Don't check if we're handling an OAuth callback
const hash = window.location.hash;
if (hash && hash.includes('access_token=')) {
@@ -2371,41 +2428,50 @@ export default async function decorate(block) {
// Check if user is already signed in
if (window.adobeIMS && window.adobeIMS.isSignedInUser()) {
+ if (!signInContainer || signInContainer.classList.contains('hidden')) {
+ return;
+ }
+ if (isResolvingSignedInState) {
+ return;
+ }
+ isResolvingSignedInState = true;
+
// User is already signed in - hide sign-in page and show appropriate content
if (signInContainer && !signInContainer.classList.contains('hidden')) {
+ try {
// Navigate from sign-in to loading
setLoadingText(loadingContainer, 'Loading...');
navigateTo(signInContainer, loadingContainer, true);
const forceParams = getForceRequestAccessParams();
if (forceParams && requestAccessContainer) {
- window.adobeIMS?.getProfile().then((profile) => {
- updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, forceParams.edgeCase, { selectedOrganization, userEmail: profile?.email });
- navigateTo(loadingContainer, requestAccessContainer);
- }).catch(() => {
- updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, forceParams.edgeCase, { selectedOrganization });
- navigateTo(loadingContainer, requestAccessContainer);
- });
+ const profile = await window.adobeIMS?.getProfile().catch(() => null);
+ updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, forceParams.edgeCase, { selectedOrganization, userEmail: profile?.email });
+ navigateTo(loadingContainer, requestAccessContainer);
return;
}
- // Same as React: if !template.userEntitled || !template.orgEntitled -> show RequestAccess (RestrictedAccess or EdgeCase)
- if (requestAccessContainer) {
- fetchTemplateEntitlement().then(async (entitlement) => {
- if (entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
- lastRequestAccessEntitlement = entitlement;
- const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
- const profile = await window.adobeIMS?.getProfile().catch(() => null);
- updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
- navigateTo(loadingContainer, requestAccessContainer);
- return;
- }
- runFormOrReturnFlow();
- }).catch(() => runFormOrReturnFlow());
- } else {
- runFormOrReturnFlow();
+
+ await ensureSignedInContextAndOrganizations();
+
+ // Fetch template data (populates templateData.apis for credential creation)
+ // and check entitlement (same as React: !template.userEntitled || !template.orgEntitled -> RequestAccess)
+ const entitlement = await fetchTemplateEntitlementWithRetry();
+ if (requestAccessContainer && entitlement && (entitlement.userEntitled === false || entitlement.orgEntitled === false)) {
+ lastRequestAccessEntitlement = entitlement;
+ const leftCardKey = getRequestAccessLeftCardKey(entitlement, selectedOrganization, credentialData.RequestAccess);
+ const profile = await window.adobeIMS?.getProfile().catch(() => null);
+ updateRequestAccessLeftColumn(requestAccessContainer, credentialData.RequestAccess, leftCardKey, { selectedOrganization, userEmail: profile?.email });
+ navigateTo(loadingContainer, requestAccessContainer);
+ return;
}
+ runFormOrReturnFlow();
+ } catch (error) {
+ runFormOrReturnFlow();
+ } finally {
+ isResolvingSignedInState = false;
}
}
+ }
};
// Check for IMS callback on page load
@@ -2414,12 +2480,20 @@ export default async function decorate(block) {
// Check when IMS loads if user is already signed in
window.addEventListener('adobeIMS:loaded', () => {
handleIMSCallback();
- checkAlreadySignedIn();
+ void checkAlreadySignedIn();
+ startSignInStateWatcher();
+ });
+
+ window.addEventListener('imsReady', () => {
+ handleIMSCallback();
+ void checkAlreadySignedIn();
+ startSignInStateWatcher();
});
- // Also check immediately in case IMS is already loaded
if (window.adobeIMS && window.adobeIMS.isSignedInUser()) {
- checkAlreadySignedIn();
+ void checkAlreadySignedIn();
+ } else {
+ startSignInStateWatcher();
}
// Copy button functionality for API key
diff --git a/hlx_statics/blocks/header/header.css b/hlx_statics/blocks/header/header.css
index acc9654a..1b1c2f12 100644
--- a/hlx_statics/blocks/header/header.css
+++ b/hlx_statics/blocks/header/header.css
@@ -325,6 +325,7 @@ header.global-nav-header > ul#navigation-links > li > button.is-open .spectrum-P
margin-top: -6px;
margin-left: -1px;
max-height: 75vh;
+ overflow-x: hidden;
overflow-y: auto;
}
@@ -346,17 +347,48 @@ header.global-nav-header > ul#navigation-links > li {
}
header.global-nav-header ul.nav-sub-menu {
- max-width: 230px;
+ width: max-content;
}
-header.global-nav-header li .nav-dropdown-item{
+header.global-nav-header .nav-sub-menu .spectrum-Menu-item {
+ position: relative;
+ padding-left: 24px !important;
+}
+
+header.global-nav-header .nav-sub-menu .spectrum-Menu-item.is-selected a {
+ margin-left: 0px;
+}
+
+header.global-nav-header .nav-sub-menu .spectrum-Menu-item a:hover {
+ background-color: rgba(0, 0, 0, .001);
+}
+
+header.global-nav-header .nav-sub-menu .spectrum-Menu-item.is-selected .spectrum-Menu-checkmark {
+ position: absolute;
+ left: 0px;
+ top: 50%;
+ transform: translateY(-50%);
+ display: block;
+ width: 18px !important;
+ height: 18px !important;
+ color: var(--spectrum-global-color-blue-500, #1473e6);
+ fill: currentColor;
+}
+
+header.global-nav-header li .nav-dropdown-item {
display: flex;
flex-direction: column;
}
+header.global-nav-header li .nav-dropdown-item .nav-dropdown-name {
+ white-space: nowrap;
+}
+
header.global-nav-header li .nav-dropdown-item .nav-dropdown-description {
font-size: 12px;
+ max-width: 200px;
text-wrap: initial;
+
}
header.global-nav-header a.nav-dropdown-links {
diff --git a/hlx_statics/blocks/iframe/iframe.css b/hlx_statics/blocks/iframe/iframe.css
index d509fa7e..6b405f6a 100644
--- a/hlx_statics/blocks/iframe/iframe.css
+++ b/hlx_statics/blocks/iframe/iframe.css
@@ -47,6 +47,22 @@ main div.iframe-wrapper div.iframe iframe {
border: 0;
}
+main div.iframe-wrapper iframe.iframe-container.youtube-iframe {
+ width: 560px !important;
+ height: 315px !important;
+ max-width: 100%;
+}
+main div.iframe-wrapper:has(iframe.youtube-iframe) {
+ background: transparent;
+}
+@media screen and (max-width: 600px) {
+ main div.iframe-wrapper iframe.iframe-container.youtube-iframe {
+ width: 100% !important;
+ height: auto !important;
+ aspect-ratio: 16 / 9;
+ }
+}
+
@media screen and (max-width: 1280px) {
main div.iframe-wrapper {
padding: 0;
diff --git a/hlx_statics/blocks/iframe/iframe.js b/hlx_statics/blocks/iframe/iframe.js
index 9e65175b..bfdeec4d 100644
--- a/hlx_statics/blocks/iframe/iframe.js
+++ b/hlx_statics/blocks/iframe/iframe.js
@@ -167,6 +167,28 @@ export default async function decorate(block) {
id: "penpalIframe",
});
+
+ const width = block.getAttribute("data-width");
+ const height = block.getAttribute("data-height");
+
+ const isYouTube = iframeSrcUrl.hostname.includes('youtube.com') ||
+ iframeSrcUrl.hostname.includes('youtu.be');
+ if (isYouTube) {
+ if (!width) iframe.style.width = '560px';
+ if (!height) iframe.style.height = '315px';
+ iframe.style.border = '0';
+ iframeContainer.style.display = 'flex';
+ iframeContainer.style.justifyContent = 'center';
+ iframe.classList.add('youtube-iframe');
+ }
+
+ if (width) {
+ iframe.style.setProperty('width', width, 'important');
+ }
+ if (height) {
+ iframe.style.setProperty('height', height, 'important');
+ }
+
penpalScript.onload = () => {
iframeContainer.append(iframe);
penpalOnLoad();
diff --git a/hlx_statics/blocks/onthispage/onthispage.css b/hlx_statics/blocks/onthispage/onthispage.css
index f18bb540..51615e1c 100644
--- a/hlx_statics/blocks/onthispage/onthispage.css
+++ b/hlx_statics/blocks/onthispage/onthispage.css
@@ -2,6 +2,8 @@ main .onthispage-wrapper a {
text-decoration: none;
font-size: 16px;
color: rgb(0, 84, 182);
+ overflow-wrap: break-word;
+ word-break: break-word;
}
main .onthispage-wrapper a:hover {
diff --git a/hlx_statics/icons/B_app_PremierePro.svg b/hlx_statics/icons/premierepro.svg
similarity index 100%
rename from hlx_statics/icons/B_app_PremierePro.svg
rename to hlx_statics/icons/premierepro.svg
diff --git a/hlx_statics/scripts/lib-adobeio.js b/hlx_statics/scripts/lib-adobeio.js
index 58a73b29..51fbc8a9 100644
--- a/hlx_statics/scripts/lib-adobeio.js
+++ b/hlx_statics/scripts/lib-adobeio.js
@@ -555,6 +555,14 @@ function activateTab(tabItem, isMainPage) {
if (tabItem.closest('.nav-dropdown-popover')){
// if the item is within a dropdown, it needs to find the parent item to be underlined.
underlineItem = tabItem.closest('.nav-dropdown-popover');
+ const menuItem = tabItem.closest('.spectrum-Menu-item');
+ if (menuItem) {
+ menuItem.classList.add('is-selected');
+ const checkmark = ``;
+ menuItem.insertAdjacentHTML('afterbegin', checkmark);
+ }
}
underlineItem.parentElement.classList.add("activeTab");
}
diff --git a/hlx_statics/scripts/lib-helix.js b/hlx_statics/scripts/lib-helix.js
index 06aef770..a6437eec 100644
--- a/hlx_statics/scripts/lib-helix.js
+++ b/hlx_statics/scripts/lib-helix.js
@@ -256,6 +256,26 @@ export async function hasContributorsJson() {
.catch(() => false);
}
+export async function getCodePlaygroundJsonPath() {
+ const metadata = await fetchSiteMetadata();
+ const metadataPath = metadata?.get('code-playground');
+ if (metadata && !metadataPath) return null;
+
+ const pathPrefix = getMetadata('pathprefix').replace(/^\/|\/$/g, '');
+ return metadataPath
+ ? `${window.location.origin}/${pathPrefix}/${metadataPath}`
+ : `${window.location.origin}/${pathPrefix}/code-playground.json`;
+
+}
+
+export async function hasCodePlaygroundJson() {
+ const path = await getCodePlaygroundJsonPath();
+ if (!path) return false;
+ return fetch(path)
+ .then(r => r.ok)
+ .catch(() => false);
+}
+
/**
* Retrieves the nav with the specified name from the config.
* @param {string} name The nav name
diff --git a/hlx_statics/scripts/scripts.js b/hlx_statics/scripts/scripts.js
index abf4dacf..61c32ae4 100644
--- a/hlx_statics/scripts/scripts.js
+++ b/hlx_statics/scripts/scripts.js
@@ -19,7 +19,9 @@ import {
toCamelCase,
toClassName,
githubActionsBlock,
- hasContributorsJson
+ hasContributorsJson,
+ hasCodePlaygroundJson,
+ getCodePlaygroundJsonPath
} from './lib-helix.js';
import {
@@ -834,7 +836,31 @@ function loadTitle() {
}
}
-function loadPrism(document) {
+async function loadPrism(document) {
+ const codePlaygroundData = {};
+
+ if (await hasCodePlaygroundJson()) {
+ const jsonPath = await getCodePlaygroundJsonPath();
+ if (jsonPath) {
+ const response = await fetch(jsonPath);
+ if (!response.ok) {
+ console.warn('Network response was not ok:', jsonPath);
+ return null;
+ } else {
+ const json = await response.json();
+ json.data.forEach((item) => {
+ codePlaygroundData[item.key] = item.value;
+ });
+ }
+ }
+ else {
+ return null;
+ }
+ }
+
+ const defaultPlaygroundStagingURL = codePlaygroundData['code-playground-staging-url'];
+ const defaultPlaygroundProductionURL = codePlaygroundData['code-playground-production-url'];
+
const codeBlocks = document.querySelectorAll('code[class*="language-"], [class*="language-"] code');
if (!codeBlocks.length) return;
@@ -864,9 +890,13 @@ function loadPrism(document) {
const playgroundMode = pre?.getAttribute('data-playground-mode');
const playgroundExecutionMode = pre?.getAttribute('data-playground-execution-mode');
+ const stagingFromPre = pre?.getAttribute('data-playground-url-stage');
+ const productionFromPre = pre?.getAttribute('data-playground-url');
+ const codePlaygroundStagingURL = stagingFromPre || defaultPlaygroundStagingURL;
+ const codePlaygroundProductionURL = productionFromPre || defaultPlaygroundProductionURL;
const playgroundURL = isStageEnvironment(window.location.host, true) || isLocalHostEnvironment(window.location.host)
- ? (pre?.getAttribute('data-playground-url-stage') || pre?.getAttribute('data-playground-url'))
- : pre?.getAttribute('data-playground-url');
+ ? (codePlaygroundStagingURL || codePlaygroundProductionURL)
+ : (codePlaygroundProductionURL);
if (!sessionId || !playgroundMode || !playgroundExecutionMode || !playgroundURL) return null;
const btn = createTag('button', {
class: 'try-code-button',
diff --git a/hlx_statics/styles/styles.css b/hlx_statics/styles/styles.css
index af879317..679c577f 100644
--- a/hlx_statics/styles/styles.css
+++ b/hlx_statics/styles/styles.css
@@ -290,9 +290,10 @@ main.dev-docs .aside-wrapper .aside {
top: 128px;
bottom: 0;
left: 0;
- margin: 32px 32px 0 32px;
+ margin: 32px 16px 0 16px;
max-height: calc(100vh - 128px);
- overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
box-sizing: border-box;
padding-bottom: 16px;
}
diff --git a/package.json b/package.json
index 766edead..9487b29e 100644
--- a/package.json
+++ b/package.json
@@ -30,5 +30,8 @@
"@octokit/rest": "^22.0.1",
"jszip": "^3.10.1",
"minimist": "^1.2.6"
+ },
+ "engines": {
+ "node": ">=24"
}
}