diff --git a/.github/workflows/high-impact-alert.yml b/.github/workflows/high-impact-alert.yml
index bf198f5080..13ecbbb8e1 100644
--- a/.github/workflows/high-impact-alert.yml
+++ b/.github/workflows/high-impact-alert.yml
@@ -1,7 +1,7 @@
name: High Impact Alert
on:
- pull_request:
+ pull_request_target:
types:
- labeled
@@ -15,6 +15,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.4
+ with:
+ ref: ${{ github.event.pull_request.base.ref }}
- name: Send Slack message for high impact PRs
uses: actions/github-script@v7.0.1
diff --git a/.github/workflows/pr-reminders.js b/.github/workflows/pr-reminders.js
index 975ec743ae..9e8e33539e 100644
--- a/.github/workflows/pr-reminders.js
+++ b/.github/workflows/pr-reminders.js
@@ -53,6 +53,7 @@ const main = async ({ github, context }) => {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
+ base: 'stage',
});
for await (const pr of openPRs) {
diff --git a/CODEOWNERS b/CODEOWNERS
index 109eed6e17..d72f876b01 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -44,9 +44,10 @@
/libs/blocks/mnemonic-list/ @adobecom/tacocat
/libs/blocks/ost/ @adobecom/tacocat
/libs/blocks/pdf-vewer/ @meganthecoder @JasonHowellSlavin @Brandon32
-/libs/blocks/quiz/ @colloyd @sabyamon @fullcolorcoder @JackySun9 @elaineskpt @echampio-at-adobe
+/libs/blocks/quiz/ @colloyd @sabyamon @fullcolorcoder @JackySun9
+/libs/blocks/quiz-entry/ @colloyd @fullcolorcoder @JackySun9
/libs/blocks/quiz-marquee/ @ryanmparrish
-/libs/blocks/quiz-results/ @colloyd @sabyamon @fullcolorcoder @JackySun9 @elaineskpt @echampio-at-adobe
+/libs/blocks/quiz-results/ @colloyd @sabyamon @fullcolorcoder @JackySun9
/libs/blocks/quote/ @ryanmparrish
/libs/blocks/recommended-articles/ @meganthecoder @JasonHowellSlavin @Brandon32 @rgclayton
/libs/blocks/review/ @nkthakur48 @chrischrischris @auniverseaway
diff --git a/libs/blocks/caas-config/caas-config.js b/libs/blocks/caas-config/caas-config.js
index 2efac32ab6..e1b12c4638 100644
--- a/libs/blocks/caas-config/caas-config.js
+++ b/libs/blocks/caas-config/caas-config.js
@@ -82,6 +82,7 @@ const defaultOptions = {
primary: 'Primary',
'call-to-action': 'Call To Action',
link: 'Link',
+ dark: 'Dark',
hidden: 'Hide CTAs',
},
container: {
@@ -90,6 +91,7 @@ const defaultOptions = {
'83Percent': '83% Container',
'32Margin': '32 Margin Container',
carousel: 'Carousel',
+ categories: 'Product Categories',
},
ctaActions: {
_blank: 'New Tab',
@@ -183,6 +185,7 @@ const defaultOptions = {
source: {
bacom: 'Bacom',
doccloud: 'DocCloud',
+ events: 'Events',
experienceleague: 'Experience League',
hawks: 'Hawks',
magento: 'Magento',
@@ -191,6 +194,7 @@ const defaultOptions = {
northstar: 'Northstar',
workfront: 'Workfront',
'bacom-blog': 'Bacom Blog',
+ news: 'Newsroom',
},
tagsUrl: 'https://www.adobe.com/chimera-api/tags',
titleHeadingLevel: {
@@ -210,6 +214,10 @@ const defaultOptions = {
default: 'Default',
modifiedDate: 'Modified Date',
},
+ cardHoverEffect: {
+ default: 'Default',
+ grow: 'Grow',
+ },
};
const getTagList = (root) => Object.entries(root).reduce((options, [, tag]) => {
@@ -362,6 +370,11 @@ const UiPanel = () => html`
<${Select} label="Grid Gap (Gutter)" prop="gutter" options=${defaultOptions.gutter} />
<${Select} label="Theme" prop="theme" options=${defaultOptions.theme} />
<${Select} label="Details Text" prop="detailsTextOption" options=${defaultOptions.detailsTextOption} />
+ <${Select}
+ label="Card Hover Effect"
+ prop="cardHoverEffect"
+ options=${defaultOptions.cardHoverEffect}
+ />
<${Select}
label="Collection Button Style"
prop="collectionBtnStyle"
diff --git a/libs/blocks/caas/utils.js b/libs/blocks/caas/utils.js
index 6b6a885e84..21e228db2f 100644
--- a/libs/blocks/caas/utils.js
+++ b/libs/blocks/caas/utils.js
@@ -401,6 +401,26 @@ const getCustomFilterObj = ({ group, filtersCustomItems, openedOnLoad }, strs =
return filterObj;
};
+const getCategoryArray = async (state, country, lang) => {
+ const { tags } = await getTags(state.tagsUrl);
+ const categories = Object.values(tags)
+ .filter((tag) => tag.tagID === 'caas:product-categories')
+ .map((tag) => tag.tags);
+
+ const categoryItems = Object.entries(categories[0])
+ .map(([key, value]) => ({
+ group: key,
+ id: value.tagID,
+ title: value.title,
+ icon: value.icon || '',
+ items: Object.entries(value.tags)
+ .map((tag) => getFilterObj({ excludeTags: [], filterTag: [tag[1].tagID], icon: '', openedOnLoad: false }, tags, state, country, lang))
+ .filter((tag) => tag !== null),
+ }));
+
+ return [{ group: 'All Topics', title: 'All Topics', id: '', items: [] }, ...categoryItems];
+};
+
const getFilterArray = async (state, country, lang, strs) => {
if ((!state.showFilters || state.filters.length === 0) && state.filtersCustom?.length === 0) {
return [];
@@ -595,6 +615,7 @@ export const getConfig = async (originalState, strs = {}) => {
pool: state.sortReservoirPool,
},
ctaAction: state.ctaAction,
+ cardHoverEffect: state.cardHoverEffect || 'default',
additionalRequestParams: arrayToObj(state.additionalRequestParams),
},
hideCtaIds: hideCtaIds.split(URL_ENCODED_COMMA),
@@ -606,6 +627,7 @@ export const getConfig = async (originalState, strs = {}) => {
type: state.showFilters ? state.filterLocation : 'left',
showEmptyFilters: state.filtersShowEmpty,
filters: await getFilterArray(state, country, language, strs),
+ categories: await getCategoryArray(state, country, language),
filterLogic: state.filterLogic,
i18n: {
leftPanel: {
diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js
index 0f87a47406..20512894d7 100644
--- a/libs/blocks/global-navigation/global-navigation.js
+++ b/libs/blocks/global-navigation/global-navigation.js
@@ -512,9 +512,10 @@ class Gnav {
return 'linux';
};
+ const unavVersion = new URLSearchParams(window.location.search).get('unavVersion') || '1.1';
await Promise.all([
- loadScript(`https://${environment}.adobeccstatic.com/unav/1.1/UniversalNav.js`),
- loadStyle(`https://${environment}.adobeccstatic.com/unav/1.1/UniversalNav.css`),
+ loadScript(`https://${environment}.adobeccstatic.com/unav/${unavVersion}/UniversalNav.js`),
+ loadStyle(`https://${environment}.adobeccstatic.com/unav/${unavVersion}/UniversalNav.css`),
]);
const getChildren = () => {
diff --git a/libs/blocks/icon-block/icon-block.css b/libs/blocks/icon-block/icon-block.css
index 22d9eae5ef..d0e45323af 100644
--- a/libs/blocks/icon-block/icon-block.css
+++ b/libs/blocks/icon-block/icon-block.css
@@ -177,6 +177,11 @@
margin-top: var(--spacing-s);
}
+.icon-block .foreground .second-column .title-row {
+ display: flex;
+ align-items: center;
+}
+
.icon-block.full-width.small .foreground .icon-area,
.icon-block.vertical.small .foreground .icon-area {
margin-bottom: var(--spacing-s);
diff --git a/libs/blocks/icon-block/icon-block.js b/libs/blocks/icon-block/icon-block.js
index b7163a1f3d..5a4cfad278 100644
--- a/libs/blocks/icon-block/icon-block.js
+++ b/libs/blocks/icon-block/icon-block.js
@@ -70,7 +70,15 @@ function decorateContent(el) {
const textContent = el.querySelectorAll('.text-content > :not(.icon-area)');
const secondColumn = createTag('div', { class: 'second-column' });
textContent.forEach((content) => {
- secondColumn.append(content);
+ let nodeToInsert = content;
+ const firstIcon = content.querySelector('.icon:first-child');
+ if (firstIcon) {
+ const titleRowSpan = createTag('span', { class: 'title-row' });
+ titleRowSpan.append(firstIcon, content);
+ nodeToInsert = titleRowSpan;
+ }
+
+ secondColumn.append(nodeToInsert);
});
if (secondColumn.children.length === 1) el.classList.add('items-center');
el.querySelector('.foreground .text-content').append(secondColumn);
diff --git a/libs/blocks/merch/merch.js b/libs/blocks/merch/merch.js
index 24970d025e..1c17da5b83 100644
--- a/libs/blocks/merch/merch.js
+++ b/libs/blocks/merch/merch.js
@@ -226,7 +226,7 @@ export async function getUpgradeAction(
imsSignedInPromise,
[{ productArrangement: { productFamily: offerFamily } = {} }],
) {
- if (options.entitlement === false) return undefined;
+ if (!options.upgrade) return undefined;
const loggedIn = await imsSignedInPromise;
if (!loggedIn) return undefined;
const entitlements = await fetchEntitlements();
@@ -384,6 +384,7 @@ export async function getCheckoutContext(el, params) {
const checkoutWorkflow = params.get('workflow') ?? settings.checkoutWorkflow;
const checkoutWorkflowStep = params?.get('workflowStep') ?? settings.checkoutWorkflowStep;
const entitlement = params?.get('entitlement');
+ const upgrade = params?.get('upgrade');
const modal = params?.get('modal');
const extraOptions = {};
@@ -400,6 +401,7 @@ export async function getCheckoutContext(el, params) {
checkoutWorkflowStep,
checkoutMarketSegment,
entitlement,
+ upgrade,
modal,
extraOptions: JSON.stringify(extraOptions),
};
diff --git a/libs/blocks/merch/upgrade.js b/libs/blocks/merch/upgrade.js
index d8809e712d..165aad5efb 100644
--- a/libs/blocks/merch/upgrade.js
+++ b/libs/blocks/merch/upgrade.js
@@ -39,6 +39,9 @@ function buildUrl(upgradeOffer, upgradable, env) {
url.searchParams.append('surface', 'ADOBE_COM');
url.searchParams.append('ctx', 'if');
url.searchParams.append('ctxRtUrl', encodeURIComponent(window.location.href));
+ if (upgradeOffer.dataset?.promotionCode) {
+ url.searchParams.append('promoCode', upgradeOffer.dataset.promotionCode);
+ }
return url.toString();
}
diff --git a/libs/blocks/path-finder/img/link-out.svg b/libs/blocks/path-finder/img/link-out.svg
new file mode 100644
index 0000000000..9a410ee831
--- /dev/null
+++ b/libs/blocks/path-finder/img/link-out.svg
@@ -0,0 +1,4 @@
+
diff --git a/libs/blocks/path-finder/path-finder.css b/libs/blocks/path-finder/path-finder.css
new file mode 100644
index 0000000000..20512fd89d
--- /dev/null
+++ b/libs/blocks/path-finder/path-finder.css
@@ -0,0 +1,92 @@
+:root {
+ /* Colors */
+ --hlx-sk-bg: rgb(48 48 48);
+ --hlx-sk-color: #FFF;
+ --hlx-notch-size: 12px;
+ --hlx-path-height: 49px;
+}
+
+body {
+ background: var(--hlx-sk-bg);
+ color: var(--hlx-sk-color);
+}
+
+.path-finder form {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 90px 50px;
+ align-items: center;
+ gap: 8px;
+ height: var(--hlx-path-height);
+ box-sizing: border-box;
+}
+
+.path-finder button,
+.path-finder input {
+ color: #FFF;
+ display: block;
+ font-family: var(--body-font-family);
+ box-sizing: border-box;
+ background: none;
+}
+
+.path-finder input {
+ height: var(--hlx-path-height);
+ font-size: 18px;
+ padding: 4px 12px;
+ border: none;
+ outline: none !important;
+}
+
+.path-finder span {
+ height: var(--hlx-path-height);
+ font-size: 18px;
+ padding: 11px 12px 4px;
+ display: inline-block;
+}
+
+.path-finder form.error {
+ background: rgb(94 11 11);
+}
+
+.path-finder button {
+ display: inline-block;
+ font-style: normal;
+ cursor: pointer;
+ padding: 5px 14px;
+ line-height: 18px;
+ font-size: 15px;
+ border-radius: 44px 0 0 44px;
+ outline-offset: 0;
+ transition: outline-offset .2s;
+ text-decoration: none;
+ font-weight: 700;
+ text-align: center;
+ background: var(--color-accent);
+ border: 2px solid var(--color-accent);
+ outline-color: var(--color-accent);
+ color: #FFF;
+}
+
+.path-finder button.new-tab {
+ border-radius: 0 44px 44px 0;
+ margin-right: 6px;
+ padding: 14px 0;
+ display: block;
+ background: var(--color-accent) url('./img/link-out.svg') center 5px / 18px no-repeat;
+}
+
+.path-finder button.login {
+ cursor: pointer;
+ background: #FF1593;
+ border-radius: 0;
+ font-weight: 700;
+ outline: transparent solid 0;
+ transition: outline 300ms;
+ border: none;
+ height: 36px;
+ font-family: var(--body-font-family);
+ padding: 0 18px;
+ margin-left: 6px;
+ clip-path: polygon(0% 0%, var(--hlx-notch-size) 0%, calc(100% - var(--hlx-notch-size)) 0%, 100% var(--hlx-notch-size), 100% calc(100% - var(--hlx-notch-size)), 100% 100%, 0% 100%, 0% 100%);
+}
diff --git a/libs/blocks/path-finder/path-finder.js b/libs/blocks/path-finder/path-finder.js
new file mode 100644
index 0000000000..39bcaec736
--- /dev/null
+++ b/libs/blocks/path-finder/path-finder.js
@@ -0,0 +1,104 @@
+import login from '../../tools/sharepoint/login.js';
+import { account } from '../../tools/sharepoint/state.js';
+import getServiceConfig from '../../utils/service-config.js';
+import { getReqOptions } from '../../tools/sharepoint/msal.js';
+import { createTag } from '../../utils/utils.js';
+
+const SCOPES = ['files.readwrite', 'sites.readwrite.all'];
+const TELEMETRY = { application: { appName: 'Milo - Where am I' } };
+
+const getSharePointDetails = (() => {
+ let site;
+ let driveId;
+ let reqOpts;
+
+ return async () => {
+ if (site && driveId && reqOpts) return { site, driveId, reqOpts };
+
+ // Fetching SharePoint details
+ const { sharepoint } = await getServiceConfig(origin);
+ ({ site } = sharepoint);
+ driveId = sharepoint.driveId ? `drives/${sharepoint.driveId}` : 'drive';
+ reqOpts = getReqOptions();
+
+ return { site, driveId, reqOpts };
+ };
+})();
+
+function getItemId() {
+ const referrer = new URLSearchParams(window.location.search).get('referrer');
+ const sourceDoc = referrer?.match(/sourcedoc=([^&]+)/)[1];
+ const sourceId = decodeURIComponent(sourceDoc);
+ return sourceId.slice(1, -1);
+}
+
+async function linkToPage(e, form, input, target) {
+ e.preventDefault();
+
+ const { site, driveId, reqOpts } = await getSharePointDetails();
+ const resp = await fetch(`${site}/${driveId}/root:${input.value}`, reqOpts);
+ if (!resp.ok) {
+ form.classList.add('error');
+ return;
+ }
+
+ form.classList.remove('error');
+ const json = await resp.json();
+ window.open(json.webUrl, target || '_blank');
+}
+
+function buildUi(el, path) {
+ const form = createTag('form');
+ const input = createTag('input', { type: 'text', value: path });
+ const btn = createTag('button', { }, 'Go');
+ const jumpBtn = createTag('button', { }, '');
+ jumpBtn.classList.add('new-tab');
+
+ form.addEventListener('submit', (e) => linkToPage(e, form, input, '_parent'));
+ jumpBtn.addEventListener('click', (e) => linkToPage(e, form, input));
+ form.append(input, btn, jumpBtn);
+ el.append(form);
+}
+
+async function setup(el) {
+ await login({ scopes: SCOPES, telemetry: TELEMETRY });
+ if (!account.value.username) {
+ window.lana.log('Could not login to MS Graph', { tags: 'errorType=info,module=path-finder' });
+ return;
+ }
+ el.innerHTML = '';
+ // Get basic SP details
+ const { site, driveId, reqOpts } = await getSharePointDetails();
+
+ // Get basic item details
+ const itemId = getItemId();
+
+ // Ask Graph
+ const resp = await fetch(`${site}/${driveId}/items/${itemId}`, reqOpts);
+ if (!resp.ok) return;
+ const json = await resp.json();
+
+ // Format the data
+ const fileName = json.name;
+ const parentPath = json.parentReference.path.split(':').pop();
+ const fullPath = `${parentPath}/${fileName}`;
+
+ // Build the UI
+ buildUi(el, fullPath);
+}
+
+function buttonSetup(el) {
+ const span = createTag('span');
+ span.innerHTML = 'The login popup was blocked. Please use this login button to try again.';
+ const btn = createTag('button', { class: 'login' }, 'Open login');
+ btn.addEventListener('click', () => setup(el));
+ el.append(span, btn);
+}
+
+export default async function init(el) {
+ try {
+ await setup(el);
+ } catch {
+ buttonSetup(el);
+ }
+}
diff --git a/libs/blocks/quiz-entry/quizoption.js b/libs/blocks/quiz-entry/quizoption.js
index 42db01b59b..d6a63de97e 100644
--- a/libs/blocks/quiz-entry/quizoption.js
+++ b/libs/blocks/quiz-entry/quizoption.js
@@ -18,12 +18,12 @@ export const OptionCard = ({
`;
const imageHtml = image ? html`
` : null;
- const titleHtml = title ? html`${title}
` : null;
+ const titleHtml = title ? html`${title}
` : null;
const textHtml = text ? html`${text}
` : null;
return html`