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 = ({ ${iconDesktop && html``} ${iconTablet && html``} - ${`Icon - ${title || text}`} + `; 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`