diff --git a/express/blocks/browse-by-category/browse-by-category.css b/express/blocks/browse-by-category/browse-by-category.css index ec8073ac9..af055f341 100644 --- a/express/blocks/browse-by-category/browse-by-category.css +++ b/express/blocks/browse-by-category/browse-by-category.css @@ -1,26 +1,27 @@ -main .browse-by-category-container { - padding-bottom: 24px; -} - -main .browse-by-category-container .browse-by-category { +main .browse-by-category-wrapper.fullwidth { + margin: 0; + max-width: fit-content; + } + + main .browse-by-category { max-width: max-content; -} - -main .browse-by-category-container .browse-by-category-wrapper { - padding: 0; - max-width: none; -} - -main .browse-by-category-container .browse-by-category .carousel-container .carousel-platform { - align-items: flex-start; -} - -main .browse-by-category-container .browse-by-category .carousel-container .button.carousel-arrow { + } + + main .browse-by-category .carousel-container .carousel-platform { + align-items: start; + } + + main .browse-by-category.card .carousel-container .carousel-platform { + gap: 14px; + margin: 6px 0 0 0; + } + + main .browse-by-category .carousel-container .button.carousel-arrow { position: absolute; top: 46px; -} - -main .browse-by-category-container .browse-by-category-heading-section { + } + + main .browse-by-category .browse-by-category-heading-section { display: flex; justify-content: space-between; flex-direction: column; @@ -29,28 +30,28 @@ main .browse-by-category-container .browse-by-category-heading-section { margin-left: auto; margin-right: auto; margin-bottom: 10px; -} - -main .browse-by-category-container .browse-by-category-heading-section .browse-by-category-heading { + } + + main .browse-by-category .browse-by-category-heading-section .browse-by-category-heading { text-align: left; font-size: 28px; line-height: 30px; -} - -main .browse-by-category-container .browse-by-category-heading-section .browse-by-category-link-wrapper { + } + + main .browse-by-category .browse-by-category-heading-section .browse-by-category-link-wrapper { margin: 8px 0 0; -} - -main .browse-by-category-container .browse-by-category-link { + } + + main .browse-by-category .browse-by-category-link { font-size: 16px; line-height: 22px; display: flex; padding: 0; white-space: nowrap; width: max-content; -} - -main .browse-by-category-container .browse-by-category-link::after { + } + + main .browse-by-category .browse-by-category-link::after { display: flex; width: 6px; height: 6px; @@ -61,14 +62,14 @@ main .browse-by-category-container .browse-by-category-link::after { border-style: solid; border-color: var(--color-info-accent); transform-origin: 75% 75%; - transform: rotate( -45deg ); + transform: rotate(-45deg); content: ""; margin-top: 5px; margin-left: 5px; margin-right: 2.25px; -} - -main .browse-by-category-container .browse-by-category-category { + } + + main .browse-by-category:not(.card) .browse-by-category-card { position: relative; display: flex; gap: 8px; @@ -77,19 +78,29 @@ main .browse-by-category-container .browse-by-category-category { min-width: 123px; margin: 0; padding: 10px 8px 0 8px; -} - -main .browse-by-category-container .browse-by-category-category-anchor { + } + + main .browse-by-category.card .browse-by-category-card { + position: relative; + display: flex; + flex-direction: column; + } + + main .browse-by-category .browse-by-category-card-link { position: absolute; height: 100%; width: 100%; -} - -main .browse-by-category-container .browse-by-category-category-anchor:hover ~ .browse-by-category-category-image-wrapper img { - transform: scale(1.1) matrix(1, -0.07, 0.07, 1, 0, 0); -} - -main .browse-by-category-container .browse-by-category-category-image-wrapper { + } + + main .browse-by-category:not(.card) .browse-by-category-card-link:hover ~ .browse-by-category-image-wrapper img { + transform: scale(1.1) matrix(1, -0.07, 0.05, 1, 0, 0); + } + + main .browse-by-category.card .browse-by-category-card-link:hover ~ .browse-by-category-image-wrapper img { + transform: scale(1.1) matrix(1, -0.01, 0.01, 1, 0, 0); + } + + main .browse-by-category .browse-by-category-image-wrapper { background-color: var(--color-gray-200); min-height: 90px; width: 148px; @@ -98,21 +109,21 @@ main .browse-by-category-container .browse-by-category-category-image-wrapper { justify-content: center; align-items: center; pointer-events: none; -} - -main .browse-by-category-container .browse-by-category-category-image-wrapper img { + } + + main .browse-by-category:not(.card) .browse-by-category-image-wrapper img { display: block; object-fit: cover; width: 80px; height: 80px; transform: matrix(1, -0.07, 0.07, 1, 0, 0); - box-shadow: 0 0 6px #0000001F; + box-shadow: 0 0 6px #0000001f; border-radius: 8px; opacity: 1; transition: transform 0.2s ease-in-out; -} - -main .browse-by-category-container .browse-by-category-category-image-wrapper .browse-by-category-category-image-shadow { + } + + main .browse-by-category .browse-by-category-image-wrapper .browse-by-category-image-shadow { position: absolute; width: 76px; height: 76px; @@ -120,31 +131,78 @@ main .browse-by-category-container .browse-by-category-category-image-wrapper .b background: var(--color-gray-300) 0 0 no-repeat padding-box; border-radius: 10px; opacity: 1; -} - -main .browse-by-category-container .browse-by-category-category-title { + } + + main .browse-by-category.card .browse-by-category-image-shadow-wrapper { + display: block; + object-fit: cover; + transform: matrix(1, -0.07, 0.07, 1, 0, 0); + box-shadow: 0 0 6px #0000001f; + border-radius: 8px; + opacity: 1; + transition: transform 0.2s ease-in-out; + } + + main .browse-by-category.card .browse-by-category-image-wrapper { + background-color: #f8f8f8; + min-height: 90px; + height: 116px; + width: 182px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + pointer-events: none; + } + + main .browse-by-category.card .browse-by-category-image-wrapper img { + display: block; + object-fit: contain; + max-width: 110px; + max-height: 97px; + height: 100%; + transform: matrix(1, -0.01, 0.01, 1, 0, 0); + box-shadow: 0 0 6px #0000001f; + border-radius: 8px; + opacity: 1; + transition: transform 0.2s ease-in-out; + } + + main .browse-by-category.card .browse-by-category-image-wrapper .browse-by-category-image-shadow { + position: absolute; + width: 100%; + height: 100%; + transform: matrix(0.90, -0.18, 0.20, 0.90, -14, 0); + background: #d6d6d6e5; + } + + main .browse-by-category:not(.card) .browse-by-category-card-title { margin: 0; font-size: 18px; line-height: 24px; font-weight: 700; max-width: 148px; -} - -@media (min-width: 900px) { - main .browse-by-category-container .browse-by-category-heading-section { - flex-direction: row; - padding: 0; - } - - main .browse-by-category-container .browse-by-category-wrapper { - padding: 0 28px; - max-width: none; - } -} - -@media (min-width: 1200px) { - main .browse-by-category-container .browse-by-category-wrapper { - padding: 0 28px; - max-width: none; + } + + main .browse-by-category.card .browse-by-category-card-title { + margin: 6px 0; + font-size: 16px; + line-height: 24px; + font-weight: 700; + align-self: start; + } + + @media (min-width: 900px) { + main .section .browse-by-category-wrapper { + padding: 0 28px; + } + + main .section .browse-by-category-wrapper { + max-width: none; + } + + main .browse-by-category .browse-by-category-heading-section { + flex-direction: row; + padding: 0; } -} + } diff --git a/express/blocks/browse-by-category/browse-by-category.js b/express/blocks/browse-by-category/browse-by-category.js index 7ceec92c5..328b268ec 100644 --- a/express/blocks/browse-by-category/browse-by-category.js +++ b/express/blocks/browse-by-category/browse-by-category.js @@ -11,75 +11,94 @@ */ /* eslint-disable import/named, import/extensions */ -import { - createTag, -} from '../../scripts/scripts.js'; +import { createTag } from '../../scripts/scripts.js'; -import { - buildCarousel, -} from '../shared/carousel.js'; +import { buildCarousel } from '../shared/carousel.js'; -export function decorateHeading($block, payload) { - const $headingSection = createTag('div', { class: 'browse-by-category-heading-section' }); - const $heading = createTag('h3', { class: 'browse-by-category-heading' }); - const $viewAllButtonWrapper = createTag('p', { class: 'browse-by-category-link-wrapper' }); +export function decorateHeading(block, payload) { + const headingSection = createTag('div', { class: 'browse-by-category-heading-section' }); + const heading = createTag('h3', { class: 'browse-by-category-heading' }); + const viewAllButtonWrapper = createTag('p', { class: 'browse-by-category-link-wrapper' }); if (payload.viewAllLink.href !== '') { - const $viewAllButton = createTag('a', { class: 'browse-by-category-link', href: payload.viewAllLink.href }); - $viewAllButton.textContent = payload.viewAllLink.text; - $viewAllButtonWrapper.append($viewAllButton); + const viewAllButton = createTag('a', { + class: 'browse-by-category-link', + href: payload.viewAllLink.href, + }); + viewAllButton.textContent = payload.viewAllLink.text; + viewAllButtonWrapper.append(viewAllButton); } - $heading.textContent = payload.heading; - $headingSection.append($heading, $viewAllButtonWrapper); - $block.append($headingSection); + heading.textContent = payload.heading; + headingSection.append(heading, viewAllButtonWrapper); + block.append(headingSection); } -export function decorateCategories($block, payload) { - const $categoriesWrapper = createTag('div', { class: 'browse-by-category-categories-wrapper' }); - - payload.categories.forEach((category) => { - const $category = createTag('div', { class: 'browse-by-category-category' }); - const $categoryImageWrapper = createTag('div', { class: 'browse-by-category-category-image-wrapper' }); - const $categoryImageShadow = createTag('div', { class: 'browse-by-category-category-image-shadow' }); - const $categoryImage = category.$image; - const $categoryTitle = createTag('h4', { class: 'browse-by-category-category-title' }); - const $categoryAnchor = createTag('a', { class: 'browse-by-category-category-anchor' }); - - $categoryTitle.textContent = category.text; - $categoryAnchor.href = category.link; - $categoryImageWrapper.append($categoryImageShadow, $categoryImage); - $category.append($categoryAnchor, $categoryImageWrapper, $categoryTitle); - $categoriesWrapper.append($category); +export function decorateCategories(block, payload) { + const categoriesWrapper = createTag('div', { class: 'browse-by-category-categories-wrapper' }); + + payload.categories.forEach((categoryCard) => { + const category = createTag('div', { class: 'browse-by-category-card' }); + const categoryImageWrapper = createTag('div', { class: 'browse-by-category-image-wrapper' }); + const categoryImageShadowWrapper = createTag('div', { + class: 'browse-by-category-image-shadow-wrapper', + }); + const categoryImageShadow = createTag('div', { class: 'browse-by-category-image-shadow' }); + const categoryImage = categoryCard.image; + const categoryTitle = createTag('h4', { class: 'browse-by-category-card-title' }); + const categoryAnchor = createTag('a', { class: 'browse-by-category-card-link' }); + + categoryTitle.textContent = categoryCard.text; + categoryAnchor.href = categoryCard.link; + categoryImageShadowWrapper.append(categoryImageShadow, categoryImage); + categoryImageWrapper.append(categoryImageShadowWrapper); + category.append(categoryAnchor, categoryImageWrapper, categoryTitle); + categoriesWrapper.append(category); }); - $block.append($categoriesWrapper); + block.append(categoriesWrapper); } -export default async function decorate($block) { - const $rows = Array.from($block.children); - const $headingDiv = $rows.shift(); +export default async function decorate(block) { + const rows = Array.from(block.children); + const headingDiv = rows.shift(); const payload = { - heading: $headingDiv.querySelector('h4') ? $headingDiv.querySelector('h4').textContent.trim() : '', + heading: headingDiv.querySelector('h4') + ? headingDiv.querySelector('h4').textContent.trim() + : '', viewAllLink: { - text: $headingDiv.querySelector('a.button') ? $headingDiv.querySelector('a.button').textContent.trim() : '', - href: $headingDiv.querySelector('a.button') ? $headingDiv.querySelector('a.button').href : '', + text: headingDiv.querySelector('a.button') + ? headingDiv.querySelector('a.button').textContent.trim() + : '', + href: headingDiv.querySelector('a.button') ? headingDiv.querySelector('a.button').href : '', }, categories: [], }; - $rows.forEach(($row) => { + rows.forEach((row) => { payload.categories.push({ - $image: $row.querySelector('picture'), - text: $row.querySelector('a.button') ? $row.querySelector('a.button').textContent.trim() : 'missing category text', - link: $row.querySelector('a.button') ? $row.querySelector('a.button').href : 'missing category link', + image: row.querySelector('picture'), + text: row.querySelector('a.button') + ? row.querySelector('a.button').textContent.trim() + : 'missing category text', + link: row.querySelector('a.button') + ? row.querySelector('a.button').href + : 'missing category link', }); }); - $block.innerHTML = ''; + block.innerHTML = ''; - decorateHeading($block, payload); - decorateCategories($block, payload); - buildCarousel('.browse-by-category-category', $block, false); + decorateHeading(block, payload); + decorateCategories(block, payload); + buildCarousel('.browse-by-category-card', block, false); + + if (block.classList.contains('fullwidth')) { + const blockWrapper = block.parentNode; + + if (blockWrapper && blockWrapper.classList.contains('browse-by-category-wrapper')) { + blockWrapper.classList.add('fullwidth'); + } + } } diff --git a/express/blocks/link-list/link-list.js b/express/blocks/link-list/link-list.js index bc6ad1878..d9b5ad543 100644 --- a/express/blocks/link-list/link-list.js +++ b/express/blocks/link-list/link-list.js @@ -71,4 +71,8 @@ export default async function decorate($block) { buildCarousel('p.button-container', div, false); div.append(platformEl); } + + if (window.location.href.includes('/express/templates/')) { + import('../../scripts/ckg-link-list.js'); + } } diff --git a/express/blocks/long-text/long-text.css b/express/blocks/long-text/long-text.css new file mode 100644 index 000000000..66b19fbb2 --- /dev/null +++ b/express/blocks/long-text/long-text.css @@ -0,0 +1,32 @@ +/* to remove after wrapper deprecation */ +main .section div.long-text-wrapper { + max-width: none; + padding: 0; +} + +main .long-text { + margin: 0 28px; + padding: 28px; + background-color: var(--color-gray-100); + border-radius: 20px; +} + +main .long-text h2, +main .long-text p { + text-align: left; +} + +main .long-text h2 { + font-size: var(--heading-font-size-m); +} + +main .long-text p { + font-size: var(--body-font-size-s); +} + +@media (min-width: 900px) { + main .long-text { + margin: 0 60px; + padding: 28px; + } +} diff --git a/express/blocks/long-text/long-text.js b/express/blocks/long-text/long-text.js new file mode 100644 index 000000000..7033ca8b0 --- /dev/null +++ b/express/blocks/long-text/long-text.js @@ -0,0 +1,21 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export default function decorate(block) { + if (block.textContent.trim() === '') { + if (block.parentElement.classList.contains('long-text-wrapper')) { + block.parentElement.remove(); + } else { + block.remove(); + } + } +} diff --git a/express/blocks/search-marquee/autocomplete-api-v3.js b/express/blocks/search-marquee/autocomplete-api-v3.js new file mode 100644 index 000000000..7e31a9162 --- /dev/null +++ b/express/blocks/search-marquee/autocomplete-api-v3.js @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +const url = 'https://adobesearch-atc.adobe.io/uss/v3/autocomplete'; +const experienceId = 'default-templates-autocomplete-v1'; +const scopeEntities = [ + 'HzTemplate', + // 'HzTextLockup', 'Icon', 'Photo', 'DesignAsset', 'Background' +]; +const emptyRes = { queryResults: [{ items: [] }] }; +export default async function fetchAPI({ limit = 5, textQuery, locale = 'en-US' }) { + if (!textQuery) { + return []; + } + + const res = await fetch(url, { + method: 'POST', + headers: { + 'x-api-key': 'projectx_marketing_web', + 'content-type': 'application/json', + }, + body: JSON.stringify({ + experienceId, + textQuery, + locale, + queries: [ + { + limit, + id: experienceId, + scope: { + entities: scopeEntities, + }, + }, + ], + }), + }) + .then((response) => response.json()) + .then((response) => (response.queryResults?.[0]?.items ? response : emptyRes)) + .catch((err) => { + // eslint-disable-next-line no-console + console.error('Autocomplete API Error: ', err); + return emptyRes; + }); + return res.queryResults[0].items; +} diff --git a/express/blocks/search-marquee/search-marquee.css b/express/blocks/search-marquee/search-marquee.css new file mode 100644 index 000000000..174362156 --- /dev/null +++ b/express/blocks/search-marquee/search-marquee.css @@ -0,0 +1,206 @@ +main .section .search-marquee-wrapper { + max-width: none; +} + +main .search-marquee { + padding: 32px 28px; + margin-bottom: 24px; + background-position: center; + background-size: cover; + display: flex; + flex-direction: column; + align-items: center; +} + +main .search-marquee .search-marquee-bg { + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + object-fit: cover; + z-index: 0; +} + +main .search-marquee .search-dropdown-container { + background: #FFFFFF; + top: calc(100% + 6px); + border-radius: 12px; + box-shadow: 0 0 20px #00000029; + position: absolute; + width: 100%; + box-sizing: border-box; + left: 0; + z-index: 3; +} + +main .search-marquee .search-dropdown-container .dropdown-title { + margin: 0; + font-size: 14px; + color: var(--color-gray-500); +} + +main .search-marquee .search-dropdown-container .trends-container, +main .search-marquee .search-dropdown-container .suggestions-container { + padding: 20px 28px 0; + text-align: left; +} + +main .search-marquee .search-dropdown-container .trends-container .from-scratch-link { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; +} + +main .search-marquee .search-dropdown-container .trends-container ul, +main .search-marquee .search-dropdown-container .suggestions-container ul { + padding-left: 0; + list-style: none; + max-height: 216px; + overflow: auto; +} + +main .search-marquee .search-dropdown-container .trends-container .trends-wrapper li, +main .search-marquee .search-dropdown-container .suggestions-container li { + margin-bottom: 16px; +} + +main .search-marquee .search-dropdown-container .suggestions-container li { + cursor: pointer; +} + +main .search-marquee .search-dropdown-container .suggestions-container li:hover { + color: var(--color-info-accent); +} + +main .search-marquee .search-dropdown-container .trends-container .trends-wrapper li a { + color: var(--color-black); +} + +main .search-marquee .search-dropdown-container .suggestions-container { + min-width: 300px; +} + +main .search-marquee .search-dropdown-container .free-plans-container .free-plan-widget { + width: 100%; + border-radius: 0 0 12px 12px; + flex-direction: column; + align-items: center; + gap: 10px; + margin: 0; + justify-content: center; +} + +main .search-marquee .search-bar-wrapper { + position: relative; + width: 100%; + max-width: 560px; + margin: 0 28px; + height: max-content; + transition: max-width 0.5s; +} + +main .search-marquee .search-bar-wrapper .search-bar { + box-sizing: border-box; + margin: 0; + font-family: var(--body-font-family); + font-weight: 400; + font-size: 16px; + padding: 10px 40px; + border: var(--color-gray-200) 2px solid; + border-radius: 100px; + max-width: 560px; + width: 100%; + height: 41px; + transition: max-width 0.5s, padding-right 0.5s, padding-left 0.5s; +} + +main .search-marquee .search-bar-wrapper .search-bar::placeholder { + transition: color 0.5s; +} + +main .search-marquee .search-bar-wrapper .search-bar:focus-visible, +main .search-marquee .search-bar-wrapper .search-bar:focus { + border: #444444 2px solid; + outline: none; +} + +main .search-marquee .search-bar-wrapper .icon.icon-search { + position: absolute; + z-index: 1; + height: 16px; + width: 16px; + cursor: pointer; + top: 50%; + transform: translateY(-50%); + transition: left 0.5s; + left: 16px; +} + +main .search-marquee .search-bar-wrapper .icon.icon-search-clear { + position: absolute; + z-index: 1; + height: 16px; + width: 16px; + cursor: pointer; + top: 50%; + transform: translateY(-50%); + right: 40px; +} + +main .search-marquee .carousel-container { + margin-top: 32px; + margin-bottom: 0; +} + +main .search-marquee.spreadsheet-powered .carousel-container { + visibility: hidden; +} + +main .search-marquee.spreadsheet-powered .carousel-container.appear { + visibility: unset; +} + +main .search-marquee .carousel-container .carousel-platform { + margin: auto; +} + +main .search-marquee .carousel-container .carousel-platform p.button-container { + margin: 0; + border: none; +} + +main .search-marquee .carousel-container .carousel-platform a { + display: block; + box-sizing: border-box; + min-width: 160px; + min-height: 48px; + padding: 14px 27px; + color: var(--color-black); + font-size: 16px; + line-height: 20px; + background: var(--color-white); + border-radius: 50px; + margin: 0 4px; + border: none; +} + +main .search-marquee .carousel-container .carousel-platform a:hover { + background-color: var(--color-gray-200); +} + +main .search-marquee .hidden { + display: none; +} + +main .search-marquee .carousel-container .carousel-fader-right, +main .search-marquee .carousel-container .carousel-fader-left { + background: unset; +} + +@media (min-width: 600px) { + main .search-marquee .search-dropdown-container .free-plans-container .free-plan-widget { + flex-direction: row; + } +} diff --git a/express/blocks/search-marquee/search-marquee.js b/express/blocks/search-marquee/search-marquee.js new file mode 100644 index 000000000..d5d6acb26 --- /dev/null +++ b/express/blocks/search-marquee/search-marquee.js @@ -0,0 +1,320 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + buildStaticFreePlanWidget, + createTag, + fetchPlaceholders, + getIconElement, + getLocale, + getMetadata, +} from '../../scripts/scripts.js'; + +import { buildCarousel } from '../shared/carousel.js'; +import fetchAllTemplatesMetadata from '../../scripts/all-templates-metadata.js'; + +function handlelize(str) { + return str.normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // Remove accents + .replace(/(\W+|\s+)/g, '-') // Replace space and other characters by hyphen + .replace(/--+/g, '-') // Replaces multiple hyphens by one hyphen + .replace(/(^-+|-+$)/g, '') // Remove extra hyphens from beginning or end of the string + .toLowerCase(); // To lowercase +} + +function logSearch(form, url = 'https://main--express-website--adobe.hlx.page/express/search-terms-log') { + if (form) { + const input = form.querySelector('input'); + fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + data: { + keyword: input.value, + locale: getLocale(window.location), + timestamp: Date.now(), + audience: document.body.dataset.device, + }, + }), + }); + } +} + +function initSearchFunction(block) { + const searchBarWrapper = block.querySelector('.search-bar-wrapper'); + + const searchDropdown = searchBarWrapper.querySelector('.search-dropdown-container'); + const searchForm = searchBarWrapper.querySelector('.search-form'); + const searchBar = searchBarWrapper.querySelector('input.search-bar'); + const clearBtn = searchBarWrapper.querySelector('.icon-search-clear'); + const trendsContainer = searchBarWrapper.querySelector('.trends-container'); + const suggestionsContainer = searchBarWrapper.querySelector('.suggestions-container'); + const suggestionsList = searchBarWrapper.querySelector('.suggestions-list'); + + clearBtn.style.display = 'none'; + + searchBar.addEventListener('click', (e) => { + e.stopPropagation(); + searchDropdown.classList.remove('hidden'); + }, { passive: true }); + + searchBar.addEventListener('keyup', () => { + if (searchBar.value !== '') { + clearBtn.style.display = 'inline-block'; + trendsContainer.classList.add('hidden'); + suggestionsContainer.classList.remove('hidden'); + } else { + clearBtn.style.display = 'none'; + trendsContainer.classList.remove('hidden'); + suggestionsContainer.classList.add('hidden'); + } + }, { passive: true }); + + document.addEventListener('click', (e) => { + const { target } = e; + if (target !== searchBarWrapper && !searchBarWrapper.contains(target)) { + searchDropdown.classList.add('hidden'); + } + }, { passive: true }); + + const redirectSearch = async () => { + const placeholders = await fetchPlaceholders(); + const taskMap = JSON.parse(placeholders['task-name-mapping']); + + const format = getMetadata('placeholder-format'); + let currentTasks = ''; + let searchInput = searchBar.value.toLowerCase() || getMetadata('topics'); + + const tasksFoundInInput = Object.entries(taskMap).filter((task) => task[1].some((word) => { + const searchValue = searchBar.value.toLowerCase(); + return searchValue.indexOf(word.toLowerCase()) >= 0; + })).sort((a, b) => b[0].length - a[0].length); + + if (tasksFoundInInput.length > 0) { + tasksFoundInInput[0][1].sort((a, b) => b.length - a.length).forEach((word) => { + searchInput = searchInput.toLowerCase().replace(word.toLowerCase(), ''); + }); + + searchInput = searchInput.trim(); + [[currentTasks]] = tasksFoundInInput; + } + + const locale = getLocale(window.location); + const urlPrefix = locale === 'us' ? '' : `/${locale}`; + const topicUrl = searchInput ? `/${searchInput}` : ''; + const taskUrl = `/${handlelize(currentTasks.toLowerCase())}`; + const targetPath = `${urlPrefix}/express/templates${taskUrl}${topicUrl}`; + const allTemplatesMetadata = await fetchAllTemplatesMetadata(); + const pathMatch = (e) => e.url === targetPath; + if (allTemplatesMetadata.some(pathMatch)) { + window.location = `${window.location.origin}${targetPath}`; + } else { + const searchUrlTemplate = `/express/templates/search?tasks=${currentTasks}&phformat=${format}&topics=${searchInput || "''"}&q=${searchInput || "''"}`; + window.location = `${window.location.origin}${urlPrefix}${searchUrlTemplate}`; + } + }; + + searchForm.addEventListener('submit', async (e) => { + e.preventDefault(); + searchBar.disabled = true; + logSearch(e.currentTarget); + await redirectSearch(); + }); + + clearBtn.addEventListener('click', () => { + searchBar.value = ''; + suggestionsList.innerHTML = ''; + trendsContainer.classList.remove('hidden'); + suggestionsContainer.classList.add('hidden'); + clearBtn.style.display = 'none'; + }, { passive: true }); + + const suggestionsListUIUpdateCB = (suggestions) => { + suggestionsList.innerHTML = ''; + const searchBarVal = searchBar.value.toLowerCase(); + if (suggestions && !(suggestions.length <= 1 && suggestions[0]?.query === searchBarVal)) { + suggestions.forEach((item) => { + const li = createTag('li', { tabindex: 0 }); + const valRegEx = new RegExp(searchBar.value, 'i'); + li.innerHTML = item.query.replace(valRegEx, `${searchBarVal}`); + li.addEventListener('click', () => { + if (item.query === searchBar.value) return; + searchBar.value = item.query; + searchBar.dispatchEvent(new Event('input')); + }); + + suggestionsList.append(li); + }); + } + }; + + import('./useInputAutocomplete.js').then(({ default: useInputAutocomplete }) => { + const { inputHandler } = useInputAutocomplete( + suggestionsListUIUpdateCB, { throttleDelay: 300, debounceDelay: 500, limit: 7 }, + ); + searchBar.addEventListener('input', inputHandler); + }); +} + +async function decorateSearchFunctions(block) { + const placeholders = await fetchPlaceholders(); + const searchBarWrapper = createTag('div', { class: 'search-bar-wrapper' }); + const searchForm = createTag('form', { class: 'search-form' }); + const searchBar = createTag('input', { + class: 'search-bar', + type: 'text', + placeholder: placeholders['template-search-placeholder'] ?? 'Search for over 50,000 templates', + enterKeyHint: placeholders.search ?? 'Search', + }); + + // Tasks Dropdown + + searchForm.append(searchBar); + const searchIcon = getIconElement('search'); + searchIcon.loading = 'lazy'; + const searchClearIcon = getIconElement('search-clear'); + searchClearIcon.loading = 'lazy'; + searchBarWrapper.append(searchIcon, searchClearIcon); + searchBarWrapper.append(searchForm); + + block.append(searchBarWrapper); +} + +// function downloadBackgroundImg(block) {} + +function decorateBackground(block) { + const supportedImgFormat = ['jpeg', 'jpg', 'webp', 'png', 'svg']; + const supportedVideoFormat = ['mp4']; + const mediaRow = block.querySelector('div:nth-child(2)'); + + if (mediaRow) { + const mediaEl = mediaRow.querySelector('a, :scope > div'); + if (mediaEl) { + const media = mediaEl.href || mediaEl.textContent; + const splitArr = media.split('.'); + + if (supportedImgFormat.includes(splitArr[splitArr.length - 1])) { + const dummyImg = createTag('img'); + dummyImg.src = media; + dummyImg.style.display = 'none'; + block.append(dummyImg); + block.style.backgroundImage = `url(${media})`; + } + + if (supportedVideoFormat.includes(splitArr[splitArr.length - 1])) { + // todo: support video background too + } + } + + mediaRow.remove(); + } +} + +async function buildSearchDropdown(block) { + const placeholders = await fetchPlaceholders(); + + const searchBarWrapper = block.querySelector('.search-bar-wrapper'); + if (searchBarWrapper) { + const dropdownContainer = createTag('div', { class: 'search-dropdown-container hidden' }); + const trendsContainer = createTag('div', { class: 'trends-container' }); + const suggestionsContainer = createTag('div', { class: 'suggestions-container hidden' }); + const suggestionsTitle = createTag('p', { class: 'dropdown-title' }); + const suggestionsList = createTag('ul', { class: 'suggestions-list' }); + const freePlanContainer = createTag('div', { class: 'free-plans-container' }); + + const fromScratchLink = block.querySelector('a'); + const trendsTitle = placeholders['search-trends-title']; + let trends; + if (placeholders['search-trends']) trends = JSON.parse(placeholders['search-trends']); + + if (fromScratchLink) { + const linkDiv = fromScratchLink.parentElement.parentElement; + const templateFreeAccentIcon = getIconElement('template-free-accent'); + templateFreeAccentIcon.loading = 'lazy'; + const arrowRightIcon = getIconElement('arrow-right'); + arrowRightIcon.loading = 'lazy'; + fromScratchLink.prepend(templateFreeAccentIcon); + fromScratchLink.append(arrowRightIcon); + fromScratchLink.classList.remove('button'); + fromScratchLink.classList.add('from-scratch-link'); + fromScratchLink.innerHTML.replaceAll('https://www.adobe.com/express/templates/default-marquee-from-scratch-link', getMetadata('search-marquee-from-scratch-link') || '/'); + trendsContainer.append(fromScratchLink); + linkDiv.remove(); + } + + if (trendsTitle) { + const trendsTitleEl = createTag('p', { class: 'dropdown-title' }); + trendsTitleEl.textContent = trendsTitle; + trendsContainer.append(trendsTitleEl); + } + + if (trends) { + const trendsWrapper = createTag('ul', { class: 'trends-wrapper' }); + for (const [key, value] of Object.entries(trends)) { + const trendLinkWrapper = createTag('li'); + const trendLink = createTag('a', { class: 'trend-link', href: value }); + trendLink.textContent = key; + trendLinkWrapper.append(trendLink); + trendsWrapper.append(trendLinkWrapper); + } + trendsContainer.append(trendsWrapper); + } + + suggestionsTitle.textContent = placeholders['search-suggestions-title']; + suggestionsContainer.append(suggestionsTitle, suggestionsList); + + const freePlanTags = await buildStaticFreePlanWidget(); + + freePlanContainer.append(freePlanTags); + dropdownContainer.append(trendsContainer, suggestionsContainer, freePlanContainer); + searchBarWrapper.append(dropdownContainer); + } +} + +function decorateLinkList(block) { + const carouselItemsWrapper = block.querySelector(':scope > div:nth-of-type(2)'); + if (carouselItemsWrapper) { + const showLinkList = getMetadata('show-search-marquee-link-list'); + if ((showLinkList && !['yes', 'true', 'on', 'Y'].includes(showLinkList)) + // no link list for templates root page + || window.location.pathname.endsWith('/express/templates/') + || window.location.pathname.endsWith('/express/templates')) { + carouselItemsWrapper.remove(); + } else { + buildCarousel(':scope > div > p', carouselItemsWrapper); + const carousel = carouselItemsWrapper.querySelector('.carousel-container'); + block.append(carousel); + } + } +} + +export default async function decorate(block) { + // desktop-only block + if (document.body.dataset?.device !== 'desktop') { + block.remove(); + return; + } + decorateBackground(block); + await decorateSearchFunctions(block); + await buildSearchDropdown(block); + initSearchFunction(block); + decorateLinkList(block); + + const blockLinks = block.querySelectorAll('a'); + if (blockLinks && blockLinks.length > 0) { + const linksPopulated = new CustomEvent('linkspopulated', { detail: blockLinks }); + document.dispatchEvent(linksPopulated); + } + if (window.location.href.includes('/express/templates/')) { + import('../../scripts/ckg-link-list.js'); + } +} diff --git a/express/blocks/search-marquee/useInputAutocomplete.js b/express/blocks/search-marquee/useInputAutocomplete.js new file mode 100644 index 000000000..d49525a05 --- /dev/null +++ b/express/blocks/search-marquee/useInputAutocomplete.js @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { getLocale, getLanguage } from '../../scripts/scripts.js'; +import { memoize, throttle, debounce } from '../../scripts/utils.js'; +import fetchAPI from './autocomplete-api-v3.js'; + +const memoizedFetchAPI = memoize(fetchAPI, { + key: (options) => options.textQuery, + ttl: 30 * 1000, +}); + +export default function useInputAutocomplete( + updateUIWithSuggestions, + { throttleDelay = 300, debounceDelay = 500, limit = 5 } = {}, +) { + const state = { query: '', waitingFor: '' }; + + const fetchAndUpdateUI = async () => { + const currentSearch = state.query; + state.waitingFor = currentSearch; + const suggestions = await memoizedFetchAPI({ + textQuery: currentSearch, + limit, + locale: getLanguage(getLocale(window.location)), + }); + if (state.waitingFor === currentSearch) { + updateUIWithSuggestions(suggestions); + } + }; + + const throttledFetchAndUpdateUI = throttle(fetchAndUpdateUI, throttleDelay, { trailing: true }); + const debouncedFetchAndUpdateUI = debounce(fetchAndUpdateUI, debounceDelay); + + const inputHandler = (e) => { + state.query = e.target.value; + if (state.query.length < 4 || state.query.endsWith(' ')) { + throttledFetchAndUpdateUI(); + } else { + debouncedFetchAndUpdateUI(); + } + }; + return { inputHandler }; +} diff --git a/express/blocks/template-list-ace/feedback-modal.js b/express/blocks/template-list-ace/feedback-modal.js index bb43b46d4..9bb37c551 100644 --- a/express/blocks/template-list-ace/feedback-modal.js +++ b/express/blocks/template-list-ace/feedback-modal.js @@ -141,6 +141,7 @@ export function renderFeedbackModal(feedbackModalContent, result, feedbackState) const { feedbackState: { showSubmittedTooltip } = {} } = BlockMediator.get('ace-state'); if (showSubmittedTooltip) showSubmittedTooltip(); } catch (err) { + // eslint-disable-next-line no-console console.error(err); } feedbackModalContent.parentElement.parentElement.dispatchEvent( diff --git a/express/blocks/template-list-ace/results-modal.js b/express/blocks/template-list-ace/results-modal.js index d236fa2ba..89b9196b9 100644 --- a/express/blocks/template-list-ace/results-modal.js +++ b/express/blocks/template-list-ace/results-modal.js @@ -45,6 +45,7 @@ function getVoteHandler(result, category, feedbackState) { feedbackState.category = category; feedbackState.showThankyou(); } catch (err) { + // eslint-disable-next-line no-console console.error(err); } }; @@ -365,6 +366,7 @@ export async function fetchResults(modalContent) { (searchPositionMap.get(query) + NUM_RESULTS) % (RESULTS_ROTATION * NUM_RESULTS)); } } catch (e) { + // eslint-disable-next-line no-console console.error(e); fetchingState.results = 'error'; } finally { diff --git a/express/blocks/template-list/breadcrumbs.js b/express/blocks/template-list/breadcrumbs.js new file mode 100644 index 000000000..ae9da74de --- /dev/null +++ b/express/blocks/template-list/breadcrumbs.js @@ -0,0 +1,151 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { + fetchPlaceholders, + getMetadata, + titleCase, + createTag, +} from '../../scripts/scripts.js'; +import fetchAllTemplatesMetadata from '../../scripts/all-templates-metadata.js'; + +function sanitize(str) { + return str.replaceAll(/[$@%'"]/g, ''); +} + +function getCrumbsForSearch(templatesUrl, allTemplatesMetadata, taskCategories) { + const { search, origin } = window.location; + let { tasks, topics } = new Proxy(new URLSearchParams(search), { + get: (searchParams, prop) => searchParams.get(prop), + }); + tasks = sanitize(tasks); + topics = sanitize(topics); + const crumbs = []; + if (!tasks && !topics) { + return crumbs; + } + const shortTitle = getMetadata('short-title'); + if (!shortTitle) { + return crumbs; + } + + const lastCrumb = createTag('li'); + lastCrumb.textContent = shortTitle; + crumbs.push(lastCrumb); + if (!tasks || !topics) { + return crumbs; + } + + const taskUrl = `${templatesUrl}${tasks}`; + const foundTaskPage = allTemplatesMetadata + .some((t) => t.url === taskUrl.replace(origin, '')); + + if (foundTaskPage) { + const taskCrumb = createTag('li'); + const taskAnchor = createTag('a', { href: taskUrl }); + taskCrumb.append(taskAnchor); + const translatedTasks = Object.entries(taskCategories) + .find(([_, t]) => t === tasks || t === tasks.replace(/-/g, ' ')) + ?.[0]?.toLowerCase() ?? tasks; + taskAnchor.textContent = titleCase(translatedTasks); + crumbs.unshift(taskCrumb); + } + + return crumbs; +} + +function getCrumbsForSEOPage(templatesUrl, allTemplatesMetadata, taskCategories, segments) { + const { origin, pathname } = window.location; + const tasks = getMetadata('tasks') + // TODO: remove templateTasks and allTemplatesMetadata here after all content are updated + ?? getMetadata('templateTasks') + ?? allTemplatesMetadata[pathname]?.tasks + ?? allTemplatesMetadata[pathname]?.templateTasks; + const translatedTasks = Object.entries(taskCategories) + .find(([_, t]) => t === tasks || t === tasks.replace(/-/g, ' ')) + ?.[0]?.toLowerCase() ?? tasks; + // we might have an inconsistent trailing slash problem + let builtUrl = templatesUrl.replace('templates/', 'templates'); + const crumbs = []; + segments + .slice(0, segments.length - 1) + .forEach((currSeg) => { + const seg = sanitize(currSeg); + if (!seg) return; + builtUrl = `${builtUrl}/${seg}`; + // at least translate tasks seg + const translatedSeg = seg === tasks ? translatedTasks : seg; + const segmentCrumb = createTag('li'); + if (allTemplatesMetadata.some((t) => t.url === builtUrl.replace(origin, ''))) { + const segmentLink = createTag('a', { href: builtUrl }); + segmentLink.textContent = titleCase(translatedSeg); + segmentCrumb.append(segmentLink); + } else { + segmentCrumb.textContent = titleCase(translatedSeg); + } + crumbs.push(segmentCrumb); + }); + const lastCrumb = createTag('li'); + lastCrumb.textContent = getMetadata('short-title'); + crumbs.push(lastCrumb); + return crumbs; +} + +// returns null if no breadcrumbs +// returns breadcrumbs as an li element +export default async function getBreadcrumbs() { + // for backward compatibility + // TODO: remove this check after all content are updated + if (getMetadata('sheet-powered') !== 'Y' || !document.querySelector('.search-marquee')) { + return null; + } + const { origin, pathname } = window.location; + const regex = /(.*?\/express\/)templates(.*)/; + const matches = pathname.match(regex); + if (!matches) { + return null; + } + const placeholders = await fetchPlaceholders(); + const [, homePath, children] = matches; + const breadcrumbs = createTag('ol', { class: 'templates-breadcrumbs' }); + + const homeCrumb = createTag('li'); + const homeUrl = `${origin}${homePath}`; + const homeAnchor = createTag('a', { href: homeUrl }); + homeAnchor.textContent = titleCase(placeholders.express || '') || 'Home'; + homeCrumb.append(homeAnchor); + breadcrumbs.append(homeCrumb); + + const templatesCrumb = createTag('li'); + const templatesUrl = `${homeUrl}templates/`; + const templatesAnchor = createTag('a', { href: templatesUrl }); + templatesAnchor.textContent = titleCase(placeholders.templates || '') || 'Templates'; + templatesCrumb.append(templatesAnchor); + breadcrumbs.append(templatesCrumb); + + const nav = createTag('nav', { 'aria-label': 'Breadcrumb' }); + nav.append(breadcrumbs); + + if (!children || children === '/') { + return nav; + } + const taskCategories = JSON.parse(placeholders['task-categories']); + const allTemplatesMetadata = await fetchAllTemplatesMetadata(); + const isSearchPage = children.startsWith('/search?') || getMetadata('template-search-page') === 'Y'; + const crumbs = isSearchPage + ? getCrumbsForSearch(templatesUrl, allTemplatesMetadata, taskCategories) + : getCrumbsForSEOPage(templatesUrl, allTemplatesMetadata, taskCategories, children.split('/')); + + crumbs.forEach((c) => { + breadcrumbs.append(c); + }); + return nav; +} diff --git a/express/blocks/template-list/template-list.css b/express/blocks/template-list/template-list.css index 771017bb2..27b74b33d 100644 --- a/express/blocks/template-list/template-list.css +++ b/express/blocks/template-list/template-list.css @@ -1,4 +1,5 @@ -main .template-list-horizontal-fullwidth-container { +main .template-list-horizontal-fullwidth-container, +main .template-list-fullwidth-apipowered-container { padding-top: 0; } @@ -76,24 +77,7 @@ main .template-list.fullwidth.lg-view { main .template-list-wrapper.with-categories-list .template-list.fullwidth { width: 100%; -} - -@media (min-width: 600px) { - main .template-list-wrapper.with-categories-list .template-list.fullwidth { - width: calc(100% - 172px); - } - - main .template-list-wrapper.with-categories-list .template-list.fullwidth.sm-view { - max-width: calc(100% - 172px); - } - - main .template-list-wrapper.with-categories-list .template-list.fullwidth.md-view { - max-width: calc(100% - 260px); - } - - main .template-list-wrapper.with-categories-list .template-list.fullwidth.lg-view { - max-width: calc(100% - 337px); - } + min-height: 600px; } main .template-list.sixcols > .masonry-col, @@ -102,7 +86,7 @@ main .template-list.fullwidth > .masonry-col { } main .template-list.fullwidth.sm-view > .masonry-col { - max-width: 165px; + max-width: 170px; text-align: center; } @@ -501,26 +485,6 @@ main .template-list.fullwidth.sm-view .template.placeholder { width: 106.33px; } -@media (min-width: 600px) { - main .template-list.fullwidth.lg-view .template.placeholder { - width: 352px; - } - - main .template-list.fullwidth.md-view .template.placeholder { - width: 227.33px; - } - - main .template-list.fullwidth.sm-view .template.placeholder { - width: 165px; - } -} - -@media (min-width: 900px) { - main .template-list.fullwidth.md-view .template.placeholder { - width: 258.5px; - } -} - main .template-list .template.placeholder div:nth-of-type(2) { margin: 0; position: absolute; @@ -657,139 +621,7 @@ main .template-list.fullwidth.sm-view > .masonry-col { text-align: center; } -@media (min-width: 600px) { - main .template-list.fullwidth.lg-view > .masonry-col { - max-width: 352px; - text-align: center; - } - - main .template-list.fullwidth.md-view > .masonry-col { - max-width: 227.33px; - text-align: center; - } - - main .template-list.fullwidth.sm-view > .masonry-col { - max-width: 165px; - text-align: center; - } - - main .template-list.fullwidth.lg-view .template.placeholder, - main .template-list.fullwidth.md-view .template.placeholder, - main .template-list.fullwidth.sm-view .template.placeholder { - max-height: unset; - } - - main .template-list.sm-view .template.placeholder > div:first-of-type { - height: 95%; - } -} - -@media (min-width: 900px) { - main .template-list-container > div, - main .template-list-fourcols-container > div, - main .template-list-horizontal-container > div { - max-width: 900px; - } - - main .template-list-sixcols-container > div { - max-width: 748px; - } - - main .template-list-fullwidth-container > div, - main .template-list-fullwidth-container > .default-content-wrapper, - main .template-list-fullwidth-apipowered-container > div, - main .template-list-fullwidth-apipowered-container > .default-content-wrapper { - max-width: none; - padding: 0 28px; - } - - main .template-list.sixcols > .masonry-col, - main .template-list.fullwidth > .masonry-col { - max-width: 187px; - } - - main .template-list .template-title > div { - flex-direction: row; - justify-content: space-between; - } - - main .template-list .template-title > div > * { - text-align: left; - } - - main .template-list.sixcols > .masonry-col, - main .template-list.fullwidth > .masonry-col { - max-width: 187px; - } - - main .template-list.fullwidth.md-view > .masonry-col { - max-width: 258.5px; - text-align: center; - } - - main .template-list .template { - width: 240px; - margin-left: 20px; - margin-right: 20px; - } - - main .template-list.sixcols .template:not(.placeholder), - main .template-list.fullwidth .template:not(.placeholder) { - margin: 11px 11px 9px 11px; /* make up for 4px height overlap in first child div */ - } - - main .template-list.sixcols .template.placeholder, - main .template-list.fullwidth .template.placeholder { - width: 145px; - margin: 21px; - } - - /* template-list inside columns */ - - main .columns .template-list { - margin-top: 0; - margin-left: 40px; - } - - main .template-list .template:not(.placeholder) .icon-free-badge { - display: none; - } -} - -@media (min-width: 1200px) { - main .template-list-fourcols-container > div { - max-width: 1200px; - } - - main .template-list-sixcols-container > div { - max-width: 1122px; - } - - main .template-list-fullwidth-container > div, - main .template-list-fullwidth-apipowered-container > div { - max-width: none; - } - - /* template-list inside columns */ - - main .columns .template-list { - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - } - - main .columns .template-list .template { - justify-content: flex-start; - min-width: 132px; - } - - main .columns .template-list .template-link { - font-size: 0.875rem; - font-weight: 400; - } -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper { +main .section .template-list-search-bar-wrapper { position: relative; max-width: 560px; padding: 0 28px; @@ -798,7 +630,11 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper { transition: max-width 0.5s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-bar { +main .template-list-fullwidth-apipowered-container.search-marquee-spreadsheet-powered-container .template-list-search-bar-wrapper { + margin-top: 0; +} + +main .section .template-list-search-bar-wrapper .search-bar { box-sizing: border-box; margin: 0; font-family: var(--body-font-family); @@ -813,17 +649,17 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-b transition: max-width 0.5s, padding-right 0.5s, padding-left 0.5s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-bar::placeholder { +main .section .template-list-search-bar-wrapper .search-bar::placeholder { transition: color 0.5s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-bar:focus-visible, -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-bar:focus { +main .section .template-list-search-bar-wrapper .search-bar:focus-visible, +main .section .template-list-search-bar-wrapper .search-bar:focus { border: #444444 2px solid; outline: none; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .icon.icon-search { +main .section .template-list-search-bar-wrapper .icon.icon-search { position: absolute; z-index: 1; height: 16px; @@ -835,7 +671,7 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .icon.ico left: 160px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .icon.icon-search-clear { +main .section .template-list-search-bar-wrapper .icon.icon-search-clear { position: absolute; z-index: 1; height: 16px; @@ -846,11 +682,11 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .icon.ico right: 40px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list .icon { +main .section .template-list-search-bar-wrapper .task-dropdown-list .icon { margin-right: 4px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown { +main .section .template-list-search-bar-wrapper .search-dropdown { position: absolute; background: var(--color-white); box-sizing: border-box; @@ -863,15 +699,15 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-d box-shadow: 0 0 16px #00000015; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.sticky-search-bar .search-dropdown { +main .section .template-list-search-bar-wrapper.sticky-search-bar .search-dropdown { max-width: unset; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown.hidden { +main .section .template-list-search-bar-wrapper .search-dropdown.hidden { display: none; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .icon { +main .section .template-list-search-bar-wrapper .search-dropdown .icon { position: unset; z-index: unset; height: 22px; @@ -880,19 +716,19 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-d transform: unset; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-heading-wrapper { +main .section .template-list-search-bar-wrapper .search-dropdown .search-dropdown-heading-wrapper { display: flex; justify-content: space-between; font-size: 14px; line-height: 22px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-heading { +main .section .template-list-search-bar-wrapper .search-dropdown .search-dropdown-heading { color: var(--color-gray-500); display: none; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-scratch { +main .section .template-list-search-bar-wrapper .search-dropdown .search-dropdown-scratch { display: flex; align-items: center; gap: 4px; @@ -900,51 +736,7 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-d font-weight: 700; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-scratch .icon.icon-flyer-icon-22 { - filter: invert(21%) sepia(34%) saturate(36) hue-rotate(-136deg) brightness(119%) contrast(85%); -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-scratch .icon.icon-arrow-right { - width: 16px; -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .free-plan-widget { - margin-top: 16px; - position: relative; - background-color: var(--color-gray-100); - border-radius: 20px; - padding: 16px 24px; - display: flex; - gap: 16px; - justify-content: center; - font-size: var(--body-font-size-s); - white-space: nowrap; -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .free-plan-widget > div { - display: flex; - align-items: center; -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .free-plan-widget > div > div:first-child { - position: relative; - margin-right: 6px; - width: 14px; - height: 14px; - background: #33AB84; - border-radius: 50%; -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .free-plan-widget > div > div:first-child img { - position: absolute; - top: 3px; - left: 3px; - fill: var(--color-white); - width: 8px; - max-height: 8px; -} - -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-container { +main .section .template-list-search-bar-wrapper .task-dropdown-container { position: absolute; max-width: 120px; margin: 0; @@ -952,20 +744,20 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro transition: max-width 0.5s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.sticky-search-bar .task-dropdown-container { +main .section .template-list-search-bar-wrapper.sticky-search-bar .task-dropdown-container { z-index: 2; overflow: hidden; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.collapsed .task-dropdown-container { +main .section .template-list-search-bar-wrapper.collapsed .task-dropdown-container { max-width: 0; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.ready .task-dropdown-container { +main .section .template-list-search-bar-wrapper.ready .task-dropdown-container { overflow: unset; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown { +main .section .template-list-search-bar-wrapper .task-dropdown { position: relative; margin: 2px; background-color: var(--color-gray-200); @@ -975,7 +767,7 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro height: 37px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown .icon.icon-drop-down-arrow { +main .section .template-list-search-bar-wrapper .task-dropdown .icon.icon-drop-down-arrow { height: 10px; width: 10px; position: absolute; @@ -985,11 +777,11 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro pointer-events: none; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown.active .icon.icon-drop-down-arrow { +main .section .template-list-search-bar-wrapper .task-dropdown.active .icon.icon-drop-down-arrow { transform: rotate(180deg); } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-toggle { +main .section .template-list-search-bar-wrapper .task-dropdown-toggle { font-family: var(--body-font-family); color: #242424; font-size: 14px; @@ -1008,7 +800,7 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro transition: background-color 0.2s, border 0.2s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-toggle:after { +main .section .template-list-search-bar-wrapper .task-dropdown-toggle:after { content: ''; position: absolute; right: 0; @@ -1018,11 +810,11 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro height: calc(100% - 8px); } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-toggle:hover { +main .section .template-list-search-bar-wrapper .task-dropdown-toggle:hover { background-color: var(--color-gray-200); } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list { +main .section .template-list-search-bar-wrapper .task-dropdown-list { position: absolute; display: flex; width: 240px; @@ -1042,28 +834,28 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro transition: max-height 0.2s, padding 0.2s; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list::-webkit-scrollbar { +main .section .template-list-search-bar-wrapper .task-dropdown-list::-webkit-scrollbar { width: 12px; } /* Handle */ -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list::-webkit-scrollbar-thumb { +main .section .template-list-search-bar-wrapper .task-dropdown-list::-webkit-scrollbar-thumb { background: var(--color-gray-200); border: 2px var(--color-white) solid; border-radius: 12px; } /* Handle on hover */ -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list::-webkit-scrollbar-thumb:hover { +main .section .template-list-search-bar-wrapper .task-dropdown-list::-webkit-scrollbar-thumb:hover { background: var(--color-gray-300); } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list.active { +main .section .template-list-search-bar-wrapper .task-dropdown-list.active { max-height: 400px; padding: 12px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option { +main .section .template-list-search-bar-wrapper .task-dropdown-list li.option { font-size: 14px; font-weight: 400; font-family: var(--body-font-family); @@ -1076,49 +868,12 @@ main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dro align-items: center; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option:hover, -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option.active -{ +main .section .template-list-search-bar-wrapper .task-dropdown-list li.option:hover, +main .section .template-list-search-bar-wrapper .task-dropdown-list li.option.active { background-color: var(--color-gray-200); color: var(--color-info-accent); } -/*main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option .option-radio {*/ -/* display: block;*/ -/* height: 12px;*/ -/* width: 12px;*/ -/* color: var(--color-white);*/ -/* font-size: 4px;*/ -/* border: var(--color-black) 2px solid;*/ -/* border-radius: 50%;*/ -/* margin-right: 10px;*/ -/*}*/ - -/*main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option:hover .option-radio {*/ -/* border: #4646C6 6px solid;*/ -/* max-height: 4px;*/ -/* max-width: 4px;*/ -/* border-radius: 50%;*/ -/*}*/ - -/*main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option:active .option-radio {*/ -/* border: #3D3DB4 6px solid;*/ -/* max-height: 4px;*/ -/* max-width: 4px;*/ -/* border-radius: 50%;*/ -/*}*/ - -/*main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option .option-radio:focus-visible {*/ -/* outline-offset: 4px;*/ -/*}*/ - -/*main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list li.option.active .option-radio {*/ -/* border: var(--color-info-accent) 6px solid;*/ -/* max-height: 4px;*/ -/* max-width: 4px;*/ -/* border-radius: 50%;*/ -/*}*/ - main .template-list-fullwidth-apipowered-container .animated-template-text { margin-top: -28px; background: linear-gradient(320deg, #7C84F3, #FF4DD2, #FF993B, #FF4DD2, #7C84F3, #FF4DD2, #FF993B); @@ -1131,23 +886,6 @@ main .template-list-fullwidth-apipowered-container .animated-template-text { transition: width .3s, margin .3s, min-width .3s, background-color .3s, color .3s, border .3s, background-position 2s ease-out, padding-left .3s; } -@media (min-width: 600px) { - main .template-list-fullwidth-apipowered-container .search-bar-wrapper .task-dropdown-list { - right: 0; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper .task-dropdown-list { - left: 0; - } - - main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking { - padding-left: 40px; - padding-right: 40px; - margin: 0; - border-radius: 0; - } -} - main .template-list-fullwidth-apipowered-container .toolbar-wrapper { padding: 0; background: var(--color-gray-100); @@ -1159,7 +897,7 @@ main .template-list-fullwidth-apipowered-container .toolbar-wrapper { main .template-list-fullwidth-apipowered-container .api-templates-toolbar { position: relative; z-index: 1; - margin: 28px 0px; + margin: 16px 0 28px 0; padding: 16px; display: flex; flex-wrap: wrap; @@ -1200,7 +938,7 @@ main .template-list-fullwidth-apipowered-container .api-templates-toolbar h2 { line-height: 24px; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper { margin: 0; width: 100%; position: absolute; @@ -1210,39 +948,39 @@ main .template-list-fullwidth-apipowered-container .api-templates-toolbar .searc padding: 0; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper.collapsed .icon-search { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper.collapsed .icon-search { left: 12px; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper .icon-search { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper .icon-search { left: 132px; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper .icon-search-clear { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper .icon-search-clear { right: 12px; } -main .template-list-fullwidth-apipowered-container .search-bar-wrapper .icon-search-clear, -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.sticky-search-bar.collapsed .icon-search-clear, -main .template-list-fullwidth-apipowered-container .search-bar-wrapper.sticky-search-bar.collapsed .search-dropdown { +main .section .template-list-search-bar-wrapper .icon-search-clear, +main .section .template-list-search-bar-wrapper.sticky-search-bar.collapsed .icon-search-clear, +main .section .template-list-search-bar-wrapper.sticky-search-bar.collapsed .search-dropdown { display: none; } -main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking .search-bar-wrapper { +main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking .template-list-search-bar-wrapper { display: unset; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper.collapsed { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper.collapsed { max-width: 42px; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper.collapsed .search-bar { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper.collapsed .search-bar { padding: 0; max-width: 42px; } -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper.collapsed .search-bar::placeholder, -main .template-list-fullwidth-apipowered-container .api-templates-toolbar .search-bar-wrapper.collapsed .search-bar { +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper.collapsed .search-bar::placeholder, +main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper.collapsed .search-bar { color: transparent; } @@ -1783,17 +1521,39 @@ main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-c white-space: nowrap; } +main .template-list-wrapper .category-list-wrapper .category-list-heading { + margin-bottom: 4px; + height: 24px; + padding: 4px 7px; + border-radius: 8px; + transition: background-color .2s, color .2s; + overflow: hidden; + display: flex; + font-weight: 700; + font-size: 14px; + align-items: center; + width: max-content; +} + +main .template-list-wrapper .category-list-wrapper .category-list-heading .icon { + height: 18px; + width: 18px; + padding-right: 8px; +} + main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list ul { + position: relative; list-style: none; max-height: 800px; overflow: hidden; transition: max-height 0.2s; + margin: 0; padding-left: 0; } main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list ul > li { margin-bottom: 4px; - padding: 4px 8px; + padding: 4px 7px; border-radius: 8px; transition: background-color .2s, color .2s; } @@ -1812,7 +1572,8 @@ main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-c height: 18px; width: 18px; display: block; - padding-right: 6px; + padding-right: 8px; + /* padding-right: 6px; */ } main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list ul > li > a { @@ -1825,6 +1586,7 @@ main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-c user-select: none; display: flex; align-items: center; + white-space: nowrap; } main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list ul > li > a > .category-list-template-count { @@ -1873,25 +1635,6 @@ main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-c padding-bottom: 4px; } -@media (min-width: 600px) { - main .template-list-fullwidth-apipowered-container .toolbar-wrapper { - margin: 40px; - border-radius: 12px; - } - - main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only { - display: unset; - } -} - -@media (min-width: 900px) { - main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list-wrapper { - text-align: left; - padding-top: 32px; - padding-left: 12px; - } -} - /* Carousel styles (Template-list specific) */ /* hide controls in mobile-breakpoint if they scrolled using drag */ @@ -1936,165 +1679,13 @@ main .template-list.horizontal .carousel-platform { scroll-padding: 12px; } -@media (min-width: 1200px) { - main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-left, - main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-right { - opacity: unset; +@media (max-width: 600px) { + main .section.template-list-horizontal-container > div.template-list-wrapper { + margin-left: 0; + margin-right: 0; + max-width: unset; } - - main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-left a.button.carousel-arrow, - main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-right a.button.carousel-arrow { - pointer-events: auto; - } - - main .template-list.horizontal .carousel-container .carousel-fader-left.arrow-hidden, - main .template-list.horizontal .carousel-container .carousel-fader-right.arrow-hidden { - opacity: 0; - pointer-events: none; - } - - main .template-list.horizontal .carousel-container .carousel-fader-left.arrow-hidden a.button.carousel-arrow, - main .template-list.horizontal .carousel-container .carousel-fader-right.arrow-hidden a.button.carousel-arrow { - pointer-events: none; - } - - main .template-list.horizontal .carousel-container.controls-hidden .carousel-platform { - scroll-snap-type: x mandatory; - } - - main .template-list.horizontal .carousel-container { - margin-left: auto; - margin-right: auto; - max-width: 470px; - display: block; - } - - main .template-list.horizontal.fullwidth .carousel-container { - margin-bottom: 20px; - max-width: none; - display: block; - } - - main .template-list.horizontal .carousel-container .carousel-fader-left { - background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); - } - - main .template-list.horizontal.fullwidth .carousel-container .carousel-fader-left { - width: 150px; - background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); - } - - main .template-list.horizontal .carousel-container .carousel-fader-right { - background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); - } - - main .template-list.horizontal.fullwidth .carousel-container .carousel-fader-left { - background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); - width: 150px; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar { - border-radius: 12px; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-functions .functions-container { - display: flex; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer { - display: none; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-content-search { - flex-basis: unset; - padding-right: 16px; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer .function-sort:hover .options-wrapper { - display: flex; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer .function-sort:hover .button-wrapper .icon.icon-drop-down-arrow { - transform: rotate(180deg); - } -} - -@media (min-width: 900px) { - main .template-list.horizontal .carousel-container { - margin-left: 12px; - margin-right: 12px; - max-width: none; - display: inline-block; - } - - main .section.template-list-horizontal-container { - padding-left: 32px; - padding-right: 32px; - } - - main .section.template-list-horizontal-container > div { - max-width: 836px; - } - - main .section.template-list-horizontal-container > div > *:not(.template-list.horizontal), - main .section.template-list-horizontal-container > div > .template-list.horizontal > *:not(.carousel-container) { - max-width: unset; - } - - main .template-list.horizontal { - max-width: 800px; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar h2 { - font-size: 22px; - line-height: 24px; - width: max-content; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-functions { - flex-grow: 0; - } - - main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking .wrapper-content-search { - width: auto; - } - - main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-content-search .search-bar-wrapper { - width: 480px; - } - - main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .search-dropdown-heading { - display: unset; - } - - main .template-list-fullwidth-apipowered-container .search-bar-wrapper .search-dropdown .free-plan-widget { - gap: 32px; - } -} - -@media (min-width: 1200px) { - main .section.template-list-horizontal-container > div { - max-width: 1024px; - } - - main .template-list.horizontal.fullwidth > div, - main .section.template-list-horizontal-fullwidth-container > div, - main .section.template-list-horizontal-fullwidth-collaboration-container > div { - max-width: none; - } - - main .template-list.horizontal { - max-width: 1090px; - } -} - -@media (max-width: 600px) { - main .section.template-list-horizontal-container > div.template-list-wrapper { - margin-left: 0; - margin-right: 0; - max-width: unset; - } -} +} main .template-list.mini { min-height: unset; @@ -2228,34 +1819,6 @@ main .template-list.collaboration .carousel-container { margin-left: 6px; } -@media (min-width: 900px) { - main .template-list.collaboration .carousel-container { - margin-left: 0; - } -} - -@media (min-width: 1200px) { - main .template-list-horizontal-fullwidth-mini-container .template-list.mini { - display: none; - } - - main .template-list.collaboration .template-title > div > h3 { - gap: 12px; - } - - main .template-list.collaboration .template-title > div > h3 .collaboration-anchor .clipboard-tag { - right: unset; - bottom: unset; - top: 50%; - left: 48px; - transform: translateY(-50%); - } - - main .template-list.collaboration .template-title > div > h3 .collaboration-anchor.copied .clipboard-tag { - left: 28px; - } -} - main .template-list-horizontal-apipowered-holiday-container { padding-bottom: 10px; position: relative; @@ -2421,31 +1984,479 @@ main .template-list-horizontal-apipowered-holiday-container.expanded .animation- transition: opacity 1s; } +main .template-list-fullwidth-apipowered-container .template-list-fallback-msg-wrapper { + font-size: 22px; + text-align: center; + padding: 0 28px; +} + +main .template-list-fullwidth-apipowered-container nav ol.templates-breadcrumbs { + list-style: none; + margin: 0; + padding: 0 0 0 40px; + display: flex; +} + +main .template-list-fullwidth-apipowered-container nav ol.templates-breadcrumbs li { + display: flex; + padding: 5px 10px 5px 0; + color: var(--color-gray-500); +} + +main .template-list-fullwidth-apipowered-container nav ol.templates-breadcrumbs li a { + color: var(--color-black); + font-weight: 400; +} + +main .template-list-fullwidth-apipowered-container nav ol.templates-breadcrumbs li:not(:first-child):before { + content: '/'; + color: var(--color-black); + padding: 0 10px 0 0; +} + @media (min-width: 600px) { - main .template-list.horizontal.holiday .carousel-container .carousel-fader-left, - main .template-list.horizontal.holiday .carousel-container .carousel-fader-right { - background: initial; + main .template-list.fullwidth.lg-view .template.placeholder { + width: 352px; } -} -@media (min-width: 1200px) { - main .template-list-horizontal-apipowered-holiday-container { - padding-bottom: 0; + main .template-list.fullwidth.md-view .template.placeholder { + width: 227.33px; } - main .template-list-horizontal-apipowered-holiday-container .toggle-bar { - max-width: 1024px; + main .template-list.fullwidth.sm-view .template.placeholder { + width: 165px; } - main .template-list-horizontal-apipowered-holiday-container .mobile-only { - display: none; + main .template-list.fullwidth.lg-view > .masonry-col { + max-width: 328px; + text-align: center; } - main .template-list-horizontal-apipowered-holiday-container .toggle-bar-bottom { - display: flex; + main .template-list.fullwidth.md-view > .masonry-col { + max-width: 200px; + text-align: center; } - main .template-list-horizontal-apipowered-holiday-container .template-list-wrapper.expanded { - padding-top: 0; + main .template-list.fullwidth.sm-view > .masonry-col { + max-width: 156px; + text-align: center; + } + + main .template-list.fullwidth.lg-view .template.placeholder, + main .template-list.fullwidth.md-view .template.placeholder, + main .template-list.fullwidth.sm-view .template.placeholder { + max-height: unset; + } + + main .template-list.sm-view .template.placeholder > div:first-of-type { + height: 95%; + } + + main .section .template-list-search-bar-wrapper .task-dropdown-list { + right: 0; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .template-list-search-bar-wrapper .task-dropdown-list { + left: 0; + } + + main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking { + padding-left: 40px; + padding-right: 40px; + margin: 0; + border-radius: 0; + z-index: 2; + } + + main .template-list-fullwidth-apipowered-container .toolbar-wrapper { + margin: 0 40px; + border-radius: 12px; + } + + main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list::before { + content: ''; + z-index: 0; + position: absolute; + height: 100%; + transition: opacity 0.2s; + background: linear-gradient(180deg, #FFFFFF00 0%, #FFFFFF 59%); + } + + main .template-list-wrapper.with-categories-list.with-fallback-msg .category-list-wrapper.desktop-only { + top: -56px; + } + + main .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only:not(:hover) .category-list::before { + width: 100%; + } + + main .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only:hover .category-list::before { + width: 0; + } + + main .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only:hover::before { + top: 0; + content: ''; + opacity: 1; + z-index: -1; + position: absolute; + height: 100%; + width: 100%; + transition: opacity 0.2s; + background: linear-gradient(90deg, #ffffff 50%, #ffffff9B 88%, #ffffff00 100%); + } + + main .template-list-fullwidth-apipowered-container .category-list-wrapper.desktop-only .category-list-toggle-wrapper { + padding: 4px 8px; + } + + main .template-list-fullwidth-apipowered-container .category-list-wrapper.desktop-only .category-list-toggle-wrapper .icon { + transition: transform 0.2s; + } + + main .template-list-fullwidth-apipowered-container .category-list-wrapper.desktop-only:hover .category-list-toggle-wrapper .icon { + transform: rotate(180deg); + } + + main .template-list.horizontal.holiday .carousel-container .carousel-fader-left, + main .template-list.horizontal.holiday .carousel-container .carousel-fader-right { + background: initial; + } +} + +@media (min-width: 900px) { + main .template-list.fullwidth.md-view .template.placeholder { + width: 258.5px; + } + + main .template-list-container > div, + main .template-list-fourcols-container > div, + main .template-list-horizontal-container > div { + max-width: 900px; + } + + main .template-list-sixcols-container > div { + max-width: 748px; + } + + main .template-list-fullwidth-container > div, + main .template-list-fullwidth-container > .default-content-wrapper, + main .template-list-fullwidth-apipowered-container > div, + main .template-list-fullwidth-apipowered-container > .default-content-wrapper { + max-width: none; + } + + main .template-list.sixcols > .masonry-col, + main .template-list.fullwidth > .masonry-col { + max-width: 187px; + } + + main .template-list .template-title > div { + flex-direction: row; + justify-content: space-between; + } + + main .template-list .template-title > div > * { + text-align: left; + } + + main .template-list.sixcols > .masonry-col, + main .template-list.fullwidth > .masonry-col { + max-width: 187px; + } + + main .template-list.fullwidth.md-view > .masonry-col { + max-width: 258.5px; + text-align: center; + } + + main .template-list .template { + width: 240px; + margin-left: 20px; + margin-right: 20px; + } + + main .template-list.sixcols .template:not(.placeholder), + main .template-list.fullwidth .template:not(.placeholder) { + margin: 11px 11px 9px 11px; /* make up for 4px height overlap in first child div */ + } + + main .template-list.sixcols .template.placeholder, + main .template-list.fullwidth .template.placeholder { + width: 145px; + margin: 21px; + } + + /* template-list inside columns */ + + main .columns .template-list { + margin-top: 0; + margin-left: 40px; + } + + main .template-list .template:not(.placeholder) .icon-free-badge { + display: none; + } + + main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list-wrapper { + text-align: left; + padding-top: 32px; + padding-left: 40px; + } + + main .template-list.horizontal .carousel-container { + margin-left: 12px; + margin-right: 12px; + max-width: none; + display: inline-block; + } + + main .section.template-list-horizontal-container { + padding-left: 32px; + padding-right: 32px; + } + + main .section.template-list-horizontal-container > div { + max-width: 836px; + } + + main .section.template-list-horizontal-container > div > *:not(.template-list.horizontal), + main .section.template-list-horizontal-container > div > .template-list.horizontal > *:not(.carousel-container) { + max-width: unset; + } + + main .template-list.horizontal { + max-width: 800px; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar h2 { + font-size: 22px; + line-height: 24px; + width: max-content; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-functions { + flex-grow: 0; + } + + main .template-list-fullwidth-apipowered-container .toolbar-wrapper.sticking .wrapper-content-search { + width: auto; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-content-search .template-list-search-bar-wrapper { + width: 480px; + } + + main .section .template-list-search-bar-wrapper .search-dropdown .search-dropdown-heading { + display: unset; + } + + main .section .template-list-search-bar-wrapper .search-dropdown .free-plan-widget { + gap: 32px; + } + + main .template-list.collaboration .carousel-container { + margin-left: 0; + } +} + +@media (min-width: 1200px) { + main .template-list-fourcols-container > div { + max-width: 1200px; + } + + main .template-list-sixcols-container > div { + max-width: 1122px; + } + + main .template-list-fullwidth-container > div, + main .template-list-fullwidth-apipowered-container > div { + max-width: none; + } + + /* template-list inside columns */ + + main .columns .template-list { + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + } + + main .columns .template-list .template { + justify-content: flex-start; + min-width: 132px; + } + + main .columns .template-list .template-link { + font-size: 0.875rem; + font-weight: 400; + } + + main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only { + display: unset; + background: transparent; + position: absolute; + left: 0; + /* top: 64px; */ + height: calc(100% - 64px); + max-height: unset; + overflow: hidden; + min-width: unset; + max-width: 32px; + z-index: 1; + } + + main .template-list-fullwidth-apipowered-container .template-list-wrapper.with-categories-list .category-list-wrapper.desktop-only:hover { + min-width: max-content; + padding-right: 160px; + overflow: visible; + } + + main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-left, + main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-right { + opacity: unset; + } + + main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-left a.button.carousel-arrow, + main .template-list.horizontal .carousel-container.controls-hidden .carousel-fader-right a.button.carousel-arrow { + pointer-events: auto; + } + + main .template-list.horizontal .carousel-container .carousel-fader-left.arrow-hidden, + main .template-list.horizontal .carousel-container .carousel-fader-right.arrow-hidden { + opacity: 0; + pointer-events: none; + } + + main .template-list.horizontal .carousel-container .carousel-fader-left.arrow-hidden a.button.carousel-arrow, + main .template-list.horizontal .carousel-container .carousel-fader-right.arrow-hidden a.button.carousel-arrow { + pointer-events: none; + } + + main .template-list.horizontal .carousel-container.controls-hidden .carousel-platform { + scroll-snap-type: x mandatory; + } + + main .template-list.horizontal .carousel-container { + margin-left: auto; + margin-right: auto; + max-width: none; + display: block; + } + + main .template-list.horizontal.fullwidth .carousel-container { + margin-bottom: 20px; + max-width: none; + display: block; + } + + main .template-list.fullwidth.apipowered { + padding-left: 56px; + } + + main .template-list.horizontal .carousel-container .carousel-fader-left { + background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + } + + main .template-list.horizontal.fullwidth .carousel-container .carousel-fader-left { + width: 150px; + background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + } + + main .template-list.horizontal .carousel-container .carousel-fader-right { + background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + } + + main .template-list.horizontal.fullwidth .carousel-container .carousel-fader-left { + background: linear-gradient(to left, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + width: 150px; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar { + border-radius: 12px; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-functions .functions-container { + display: flex; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer { + display: none; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .wrapper-content-search { + flex-basis: unset; + padding-right: 16px; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer .function-sort:hover .options-wrapper { + display: flex; + } + + main .template-list-fullwidth-apipowered-container .api-templates-toolbar .functions-drawer .function-sort:hover .button-wrapper .icon.icon-drop-down-arrow { + transform: rotate(180deg); + } + + main .template-list.fullwidth.md-view > .masonry-col { + max-width: 250px; + } + + main .section.template-list-horizontal-container > div { + max-width: 1024px; + } + + main .template-list.horizontal.fullwidth > div, + main .section.template-list-horizontal-fullwidth-container > div, + main .section.template-list-horizontal-fullwidth-collaboration-container > div { + max-width: none; + } + + main .template-list.horizontal { + max-width: 1090px; + } + + main .template-list-horizontal-fullwidth-mini-container .template-list.mini { + display: none; + } + + main .template-list.collaboration .template-title > div > h3 { + gap: 12px; + } + + main .template-list.collaboration .template-title > div > h3 .collaboration-anchor .clipboard-tag { + right: unset; + bottom: unset; + top: 50%; + left: 48px; + transform: translateY(-50%); + } + + main .template-list.collaboration .template-title > div > h3 .collaboration-anchor.copied .clipboard-tag { + left: 28px; + } + + main .template-list-horizontal-apipowered-holiday-container { + padding-bottom: 0; + } + + main .template-list-horizontal-apipowered-holiday-container .toggle-bar { + max-width: 1024px; + } + + main .template-list-horizontal-apipowered-holiday-container .mobile-only { + display: none; + } + + main .template-list-horizontal-apipowered-holiday-container .toggle-bar-bottom { + display: flex; + } + + main .template-list-horizontal-apipowered-holiday-container .template-list-wrapper.expanded { + padding-top: 0; + } + + main .section.template-list-fullwidth-apipowered-container .template-list-fallback-msg-wrapper { + padding-left: 120px; + text-align: left; } } diff --git a/express/blocks/template-list/template-list.js b/express/blocks/template-list/template-list.js index 4b23ab18e..b7076137a 100644 --- a/express/blocks/template-list/template-list.js +++ b/express/blocks/template-list/template-list.js @@ -9,7 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -/* eslint-disable import/named, import/extensions */ +/* eslint-disable import/named, import/extensions, no-underscore-dangle */ import { addAnimationToggle, @@ -33,21 +33,9 @@ import { import { Masonry } from '../shared/masonry.js'; import { buildCarousel } from '../shared/carousel.js'; - -const props = { - templates: [], - filters: { locales: '(en)' }, - tailButton: '', - limit: 70, - total: 0, - start: '', - sort: '-_score,-remixCount', - masonry: undefined, - authoringError: false, - headingTitle: null, - headingSlug: null, - viewAllLink: null, -}; +import fetchAllTemplatesMetadata from '../../scripts/all-templates-metadata.js'; +import { memoize } from '../../scripts/utils.js'; +import getBreadcrumbs from './breadcrumbs.js'; function wordStartsWithVowels(word) { return word.match('^[aieouâêîôûäëïöüàéèùœAIEOUÂÊÎÔÛÄËÏÖÜÀÉÈÙŒ].*'); @@ -68,10 +56,19 @@ function trimFormattedFilterText(attr, capitalize) { return capitalize ? resultString.charAt(0).toUpperCase() + resultString.slice(1) : resultString; } -async function populateHeadingPlaceholder(locale) { +function loadBetterAssetInBackground(img) { + const updateImgRes = () => { + img.src = img.src.replace('width/size/151', 'width/size/400'); + img.removeEventListener('load', updateImgRes); + }; + + img.addEventListener('load', updateImgRes); +} + +async function populateHeadingPlaceholder(locale, props) { const heading = props.heading.replace("''", ''); // special treatment for express/ root url - const camelHeading = heading === 'Adobe Express' ? heading : heading.charAt(0).toLowerCase() + heading.slice(1); + const lowerCaseHeading = heading === 'Adobe Express' ? heading : heading.toLowerCase(); const placeholders = await fetchPlaceholders(); const lang = getLanguage(getLocale(window.location)); const templateCount = lang === 'es-ES' ? props.total.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') : props.total.toLocaleString(lang); @@ -85,9 +82,9 @@ async function populateHeadingPlaceholder(locale) { if (grammarTemplate) { grammarTemplate = grammarTemplate - .replace('{{quantity}}', templateCount) + .replace('{{quantity}}', props.fallbackMsg ? '0' : templateCount) .replace('{{Type}}', heading) - .replace('{{type}}', camelHeading); + .replace('{{type}}', lowerCaseHeading); if (locale === 'fr') { grammarTemplate.split(' ').forEach((word, index, words) => { @@ -117,28 +114,54 @@ function formatSearchQuery(limit, start, sort, filters) { return `https://www.adobe.com/cc-express-search-api?limit=${limit}&start=${start}&orderBy=${sort}&filters=${filterString}`; } -async function fetchTemplates() { - if (!props.authoringError && Object.keys(props.filters).length !== 0) { - props.queryString = formatSearchQuery(props.limit, props.start, props.sort, props.filters); +const memoizedFetchUrl = memoize((url) => fetch(url).then((r) => (r.ok ? r.json() : null)), { + key: (q) => q, + ttl: 1000 * 60 * 60 * 24, +}); - const result = await fetch(props.queryString) - .then((response) => response.json()) - .then((response) => response); +async function getFallbackMsg(tasks = '') { + const placeholders = await fetchPlaceholders(); + const fallBacktextTemplate = tasks && tasks !== "''" ? placeholders['templates-fallback-with-tasks'] : placeholders['templates-fallback-without-tasks']; - // eslint-disable-next-line no-underscore-dangle - if (result._embedded.total > 0) { + if (fallBacktextTemplate) { + return tasks ? fallBacktextTemplate.replaceAll('{{tasks}}', tasks.toString()) : fallBacktextTemplate; + } + + return `Sorry we couldn't find any results for what you searched for, try some of these popular ${ + tasks ? ` ${tasks.toString()} ` : ''}templates instead.`; +} + +async function fetchTemplates(props) { + props.fallbackMsg = null; + if (props.authoringError || Object.keys(props.filters).length === 0) { + props.authoringError = true; + props.heading = 'Authoring error: first row must specify the template “type”'; + return null; + } + const { limit, start, sort } = props; + props.queryString = formatSearchQuery(limit, start, sort, props.filters); + + let result = await memoizedFetchUrl(props.queryString); + + if (result?._embedded?.total) { + return result; + } + const { filters: { tasks, locales } } = props; + const tasksMatch = /\((.+)\)/.exec(tasks); + if (tasksMatch) { + props.queryString = formatSearchQuery(limit, start, sort, { locales, tasks }); + result = await memoizedFetchUrl(props.queryString); + if (result?._embedded?.total) { + props.fallbackMsg = await getFallbackMsg(tasksMatch[1]); return result; - } else { - // save fetch if search query returned 0 templates. "Bad result is better than no result" - return fetch(`https://www.adobe.com/cc-express-search-api?limit=${props.limit}&start=${props.start}&orderBy=${props.sort}&filters=locales:(${props.filters.locales})`) - .then((response) => response.json()) - .then((response) => response); } } - return null; + props.queryString = formatSearchQuery(limit, start, sort, { locales }); + props.fallbackMsg = await getFallbackMsg(); + return memoizedFetchUrl(props.queryString); } -function fetchTemplatesByTasks(tasks) { +function fetchTemplatesByTasks(tasks, props) { const tempFilters = { ...props.filters }; if (tasks) { @@ -146,46 +169,39 @@ function fetchTemplatesByTasks(tasks) { } if (!props.authoringError && Object.keys(tempFilters).length !== 0) { - const tempQ = formatSearchQuery(props.limit, '', props.sort, tempFilters); + const tempQ = formatSearchQuery(0, '', props.sort, tempFilters); - return fetch(tempQ) - .then((response) => response.json()) - .then((response) => response); + return memoizedFetchUrl(tempQ); } return null; } -async function appendCategoryTemplatesCount($section) { +async function appendCategoryTemplatesCount($section, props) { const categories = $section.querySelectorAll('ul.category-list > li'); - const currentTask = props.filters.tasks; const lang = getLanguage(getLocale(window.location)); for (const li of categories) { const anchor = li.querySelector('a'); if (anchor) { // eslint-disable-next-line no-await-in-loop - const json = await fetchTemplatesByTasks(anchor.dataset.tasks); + const json = await fetchTemplatesByTasks(anchor.dataset.tasks, props); const countSpan = createTag('span', { class: 'category-list-template-count' }); - // eslint-disable-next-line no-underscore-dangle - countSpan.textContent = `(${json._embedded.total.toLocaleString(lang)})`; + countSpan.textContent = `(${json?._embedded?.total?.toLocaleString(lang) ?? 0})`; anchor.append(countSpan); } } - - props.filters.tasks = currentTask; } -async function processResponse() { - const placeholders = await fetchPlaceholders(); - const response = await fetchTemplates(); - let templateFetched; - if (response) { - // eslint-disable-next-line no-underscore-dangle - templateFetched = response._embedded.results; +async function processResponse(props) { + const [placeholders, response] = await Promise.all([fetchPlaceholders(), fetchTemplates(props)]); + const { _embedded } = response || {}; + let templateFetched = []; + if (_embedded) { + const { results, total } = _embedded; + templateFetched = results; if ('_links' in response) { - // eslint-disable-next-line no-underscore-dangle const nextQuery = response._links.next.href; const starts = new URLSearchParams(nextQuery).get('start').split(','); starts.pop(); @@ -194,25 +210,18 @@ async function processResponse() { props.start = ''; } - // eslint-disable-next-line no-underscore-dangle - props.total = response._embedded.total; + props.total = total; } - const renditionParams = { - format: 'jpg', - dimension: 'width', - size: 400, - }; - if (templateFetched) { return templateFetched.map((template) => { const $template = createTag('div'); - const $pictureWrapper = createTag('div'); + const imgWrapper = createTag('div'); ['format', 'dimension', 'size'].forEach((param) => { - template.rendition.href = template.rendition.href.replace(`{${param}}`, renditionParams[param]); + template.rendition.href = template.rendition.href.replace(`{${param}}`, props.renditionParams[param]); }); - const $picture = createTag('img', { + const img = createTag('img', { src: template.rendition.href, alt: template.title, }); @@ -224,10 +233,10 @@ async function processResponse() { }); $button.textContent = placeholders['edit-this-template'] ?? 'Edit this template'; - $pictureWrapper.insertAdjacentElement('beforeend', $picture); - $buttonWrapper.insertAdjacentElement('beforeend', $button); - $template.insertAdjacentElement('beforeend', $pictureWrapper); - $template.insertAdjacentElement('beforeend', $buttonWrapper); + imgWrapper.append(img); + $buttonWrapper.append($button); + $template.append(imgWrapper, $buttonWrapper); + loadBetterAssetInBackground(img); return $template; }); } else { @@ -252,7 +261,7 @@ async function fetchBlueprint(pathname) { return ($main); } -function populateTemplates($block, templates) { +function populateTemplates($block, templates, props) { for (let $tmplt of templates) { const isPlaceholder = $tmplt.querySelector(':scope > div:first-of-type > img[src*=".svg"], :scope > div:first-of-type > svg'); const $linkContainer = $tmplt.querySelector(':scope > div:nth-of-type(2)'); @@ -292,11 +301,11 @@ function populateTemplates($block, templates) { if (isPlaceholder) { // add aspect ratio to template const sep = option.includes(':') ? ':' : 'x'; - const ratios = option.split(sep).map((e) => +e); + const ratios = option.split(sep).map((str) => parseInt(str, 10)); props.placeholderFormat = ratios; if ($block.classList.contains('horizontal')) { const height = $block.classList.contains('mini') ? 100 : 200; - if (ratios[1]) { + if (ratios?.length === 2) { const width = (ratios[0] / ratios[1]) * height; $tmplt.style = `width: ${width}px`; if (width / height > 1.3) { @@ -423,7 +432,7 @@ async function attachFreeInAppPills($block) { } } -async function readRowsFromBlock($block) { +async function readRowsFromBlock($block, props) { if ($block.children.length > 0) { Array.from($block.children).forEach((row, index, array) => { const cells = row.querySelectorAll('div'); @@ -456,13 +465,12 @@ async function readRowsFromBlock($block) { } }); - const fetchedTemplates = await processResponse(); + const fetchedTemplates = await processResponse(props); if (fetchedTemplates) { props.templates = props.templates.concat(fetchedTemplates); props.templates.forEach((template) => { - const clone = template.cloneNode(true); - $block.append(clone); + $block.append(template); }); } } else { @@ -471,11 +479,45 @@ async function readRowsFromBlock($block) { } } -async function redirectSearch($searchBar) { - const placeholders = await fetchPlaceholders().then((result) => result); +function getRedirectUrl(tasks, topics, format, allTemplatesMetadata) { + const locale = getLocale(window.location); + const urlPrefix = locale === 'us' ? '' : `/${locale}`; + const topicUrl = topics ? `/${topics}` : ''; + const taskUrl = `/${handlelize(tasks.toLowerCase())}`; + const targetPath = `${urlPrefix}/express/templates${taskUrl}${topicUrl}`; + const pathMatch = (e) => e.path === targetPath; + if (allTemplatesMetadata.some(pathMatch)) { + return `${window.location.origin}${targetPath}`; + } else { + const searchUrlTemplate = `/express/templates/search?tasks=${tasks}&phformat=${format}&topics=${topics || "''"}`; + const searchUrl = `${window.location.origin}${urlPrefix}${searchUrlTemplate}`; + return searchUrl; + } +} + +function logSearch(form, url = 'https://main--express-website--adobe.hlx.page/express/search-terms-log') { + if (form) { + const input = form.querySelector('input'); + fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + data: { + keyword: input.value, + locale: getLocale(window.location), + timestamp: Date.now(), + audience: document.body.dataset.device, + }, + }), + }); + } +} + +async function redirectSearch($searchBar, props) { + const placeholders = await fetchPlaceholders(); const taskMap = JSON.parse(placeholders['task-name-mapping']); if ($searchBar) { - const wrapper = $searchBar.closest('.search-bar-wrapper'); + const wrapper = $searchBar.closest('.template-list-search-bar-wrapper'); const $selectorTask = wrapper.querySelector('.task-dropdown-list > .option.active'); props.filters.tasks = `(${$selectorTask.dataset.tasks})`; } @@ -498,20 +540,9 @@ async function redirectSearch($searchBar) { searchInput = searchInput.trim(); [[currentTasks]] = tasksFoundInInput; } - - const locale = getLocale(window.location); - const urlPrefix = locale === 'us' ? '' : `/${locale}`; - const topicUrl = searchInput ? `/${searchInput}` : ''; - const taskUrl = `/${handlelize(currentTasks.toLowerCase())}`; - const searchUrlTemplate = `/express/templates/search?tasks=${currentTasks}&phformat=${format}&topics=${searchInput || "''"}`; - const targetPath = `${urlPrefix}/express/templates${taskUrl}${topicUrl}`; - const searchUrl = `${window.location.origin}${urlPrefix}${searchUrlTemplate}`; - const pathMatch = (e) => e.path === targetPath; - if (window.templates && window.templates.data.some(pathMatch)) { - window.location = `${window.location.origin}${targetPath}`; - } else { - window.location = searchUrl; - } + const allTemplatesMetadata = await fetchAllTemplatesMetadata(); + const redirectUrl = getRedirectUrl(currentTasks, searchInput, format, allTemplatesMetadata); + window.location = redirectUrl; } function makeTemplateFunctions(placeholders) { @@ -535,25 +566,25 @@ function makeTemplateFunctions(placeholders) { Object.entries(functions).forEach((entry) => { entry[1].elements.wrapper = createTag('div', { - class: `function-wrapper function-${Object.values(entry)[0]}`, - 'data-param': Object.values(entry)[0], + class: `function-wrapper function-${entry[0]}`, + 'data-param': entry[0], }); entry[1].elements.wrapper.subElements = { button: { - wrapper: createTag('div', { class: `button-wrapper button-wrapper-${Object.values(entry)[0]}` }), + wrapper: createTag('div', { class: `button-wrapper button-wrapper-${entry[0]}` }), subElements: { iconHolder: createTag('span', { class: 'icon-holder' }), - textSpan: createTag('span', { class: `current-option current-option-${Object.values(entry)[0]}` }), + textSpan: createTag('span', { class: `current-option current-option-${entry[0]}` }), chevIcon: getIconElement('drop-down-arrow'), }, }, options: { - wrapper: createTag('div', { class: `options-wrapper options-wrapper-${Object.values(entry)[0]}` }), + wrapper: createTag('div', { class: `options-wrapper options-wrapper-${entry[0]}` }), subElements: Object.entries(entry[1].placeholders).map((option, subIndex) => { const icon = getIconElement(entry[1].icons[subIndex]); - const optionButton = createTag('div', { class: 'option-button', 'data-value': Object.values(option)[1] }); - [optionButton.textContent] = Object.values(option); + const optionButton = createTag('div', { class: 'option-button', 'data-value': option[1] }); + [optionButton.textContent] = option; optionButton.prepend(icon); return optionButton; }), @@ -569,6 +600,7 @@ function makeTemplateFunctions(placeholders) { function updateFilterIcon(block) { const section = block.closest('.section.template-list-fullwidth-apipowered-container'); + if (!section) return; const functionWrapper = section.querySelectorAll('.function-wrapper'); const optionsWrapper = section.querySelectorAll('.options-wrapper'); @@ -588,22 +620,22 @@ function decorateFunctionsContainer($block, $section, functions, placeholders) { const $functionsContainer = createTag('div', { class: 'functions-container' }); const $functionContainerMobile = createTag('div', { class: 'functions-drawer' }); - Object.entries(functions).forEach((filter) => { - const $filterWrapper = filter[1].elements.wrapper; + Object.values(functions).forEach((filter) => { + const filterWrapper = filter.elements.wrapper; - Object.entries($filterWrapper.subElements).forEach((part) => { - const $innerWrapper = part[1].wrapper; + Object.values(filterWrapper.subElements).forEach((part) => { + const innerWrapper = part.wrapper; - Object.entries(part[1].subElements).forEach((innerElement) => { - if (Object.values(innerElement)[1]) { - $innerWrapper.append(Object.values(innerElement)[1]); + Object.values(part.subElements).forEach((innerElement) => { + if (innerElement) { + innerWrapper.append(innerElement); } }); - $filterWrapper.append($innerWrapper); + filterWrapper.append(innerWrapper); }); - $functionContainerMobile.append($filterWrapper.cloneNode({ deep: true })); - $functionsContainer.append($filterWrapper); + $functionContainerMobile.append(filterWrapper.cloneNode({ deep: true })); + $functionsContainer.append(filterWrapper); }); // restructure drawer for mobile design @@ -673,6 +705,7 @@ function decorateFunctionsContainer($block, $section, functions, placeholders) { } function resetTaskDropdowns($section) { + if (!$section) return; const $taskDropdowns = $section.querySelectorAll('.task-dropdown'); const $taskDropdownLists = $section.querySelectorAll('.task-dropdown-list'); @@ -687,7 +720,7 @@ function resetTaskDropdowns($section) { function closeTaskDropdown($toolBar) { const $section = $toolBar.closest('.section.template-list-fullwidth-apipowered-container'); - const $searchBarWrappers = $section.querySelectorAll('.search-bar-wrapper'); + const $searchBarWrappers = $section.querySelectorAll('.template-list-search-bar-wrapper'); $searchBarWrappers.forEach(($wrapper) => { const $taskDropdown = $wrapper.querySelector('.task-dropdown'); const $taskDropdownList = $taskDropdown.querySelector('.task-dropdown-list'); @@ -696,10 +729,11 @@ function closeTaskDropdown($toolBar) { }); } -function initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper) { +function initSearchFunction($toolBar, $stickySearchBarWrapper, generatedSearchBar, props) { const $section = $toolBar.closest('.section.template-list-fullwidth-apipowered-container'); const $stickySearchBar = $stickySearchBarWrapper.querySelector('input.search-bar'); - const $searchBarWrappers = $section.querySelectorAll('.search-bar-wrapper'); + const searchMarqueeSearchBar = document.querySelector('.search-marquee .search-bar-wrapper'); + const $searchBarWrappers = document.querySelectorAll('.template-list-search-bar-wrapper'); const $toolbarWrapper = $toolBar.parentElement; const searchBarWatcher = new IntersectionObserver((entries) => { @@ -711,7 +745,10 @@ function initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper } }, { rootMargin: '0px', threshold: 1 }); - searchBarWatcher.observe($searchBarWrapper); + // for backward compatibility + // TODO: consider removing !searchMarqueeSearchBar as it should always be there for desktop pages + const searchBarToWatch = (document.body.dataset.device === 'mobile' || !searchMarqueeSearchBar) ? generatedSearchBar : searchMarqueeSearchBar; + searchBarWatcher.observe(searchBarToWatch); $searchBarWrappers.forEach(($wrapper) => { const $searchForm = $wrapper.querySelector('.search-form'); @@ -737,7 +774,8 @@ function initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper $searchForm.addEventListener('submit', async (e) => { e.preventDefault(); - await redirectSearch($searchBar); + logSearch(e.currentTarget); + await redirectSearch($searchBar, props); }); $clear.addEventListener('click', () => { @@ -808,14 +846,6 @@ function initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper } }, 500); }, { passive: true }); - - $stickySearchBarWrapper.addEventListener('mouseleave', () => { - if (!$stickySearchBar || $stickySearchBar !== document.activeElement) { - $stickySearchBarWrapper.classList.remove('ready'); - $stickySearchBarWrapper.classList.add('collapsed'); - resetTaskDropdowns($section); - } - }, { passive: true }); } function updateLottieStatus($section) { @@ -833,116 +863,117 @@ function updateLottieStatus($section) { } } -function decorateCategoryList($block, $section, placeholders) { - const params = new Proxy(new URLSearchParams(window.location.search), { - get: (searchParams, prop) => searchParams.get(prop), - }); +async function decorateCategoryList(block, section, placeholders, props) { + const $blockWrapper = block.closest('.template-list-wrapper'); + const $mobileDrawerWrapper = section.querySelector('.filter-drawer-mobile'); + const $inWrapper = section.querySelector('.filter-drawer-mobile-inner-wrapper'); + const categories = JSON.parse(placeholders['task-categories']); + const categoryIcons = placeholders['task-category-icons'].replace(/\s/g, '').split(','); + const $categoriesDesktopWrapper = createTag('div', { class: 'category-list-wrapper' }); + const $categoriesToggleWrapper = createTag('div', { class: 'category-list-toggle-wrapper' }); + const $desktopCategoriesToggleWrapper = createTag('div', { class: 'category-list-toggle-wrapper' }); + const $categoriesToggle = createTag('span', { class: 'category-list-toggle' }); + const desktopCategoriesToggle = getIconElement('drop-down-arrow'); + const categoriesListHeading = createTag('div', { class: 'category-list-heading' }); + const $categories = createTag('ul', { class: 'category-list' }); + + categoriesListHeading.append(getIconElement('template-search'), placeholders['jump-to-category']); + $categoriesToggle.textContent = placeholders['jump-to-category']; + const allTemplatesMetadata = await fetchAllTemplatesMetadata(); - if (params.tasks) { - const locale = getLocale(window.location); - const $blockWrapper = $block.closest('.template-list-wrapper'); - const $mobileDrawerWrapper = $section.querySelector('.filter-drawer-mobile'); - const $inWrapper = $section.querySelector('.filter-drawer-mobile-inner-wrapper'); - const categories = JSON.parse(placeholders['task-categories']); - const categoryIcons = placeholders['task-category-icons'].replace(/\s/g, '').split(','); - const $categoriesDesktopWrapper = createTag('div', { class: 'category-list-wrapper' }); - const $categoriesToggleWrapper = createTag('div', { class: 'category-list-toggle-wrapper' }); - const $categoriesToggle = createTag('span', { class: 'category-list-toggle' }); - const $categories = createTag('ul', { class: 'category-list' }); - - $categoriesToggle.textContent = placeholders['jump-to-category']; - - $categoriesToggleWrapper.append($categoriesToggle); - $categoriesDesktopWrapper.append($categoriesToggleWrapper, $categories); - - Object.entries(categories).forEach((category, index) => { - const format = `${props.placeholderFormat[0]}:${props.placeholderFormat[1]}`; - const targetTasks = category[1]; - const currentTasks = trimFormattedFilterText(props.filters.tasks) ? trimFormattedFilterText(props.filters.tasks) : "''"; - const currentTopic = trimFormattedFilterText(props.filters.topics); - - const $listItem = createTag('li'); - if (category[1] === currentTasks) { - $listItem.classList.add('active'); - } + Object.entries(categories).forEach((category, index) => { + const format = `${props.placeholderFormat[0]}:${props.placeholderFormat[1]}`; + const targetTasks = category[1]; + const currentTasks = trimFormattedFilterText(props.filters.tasks) ? trimFormattedFilterText(props.filters.tasks) : "''"; + const currentTopic = trimFormattedFilterText(props.filters.topics); - let icon; - if (categoryIcons[index] && categoryIcons[index] !== '') { - icon = categoryIcons[index]; - } else { - icon = 'template-static'; - } + const $listItem = createTag('li'); + if (category[1] === currentTasks) { + $listItem.classList.add('active'); + } - const iconElement = getIconElement(icon); - const urlPrefix = locale === 'us' ? '' : `/${locale}`; - const $a = createTag('a', { - 'data-tasks': targetTasks, - href: `${urlPrefix}/express/templates/search?tasks=${targetTasks}&phformat=${format}&topics=${currentTopic || "''"}`, - }); - [$a.textContent] = category; + let icon; + if (categoryIcons[index] && categoryIcons[index] !== '') { + icon = categoryIcons[index]; + } else { + icon = 'template-static'; + } - $a.prepend(iconElement); - $listItem.append($a); - $categories.append($listItem); + const iconElement = getIconElement(icon); + const redirectUrl = getRedirectUrl(targetTasks, currentTopic, format, allTemplatesMetadata); + const $a = createTag('a', { + 'data-tasks': targetTasks, + href: redirectUrl, }); + [$a.textContent] = category; - const $categoriesMobileWrapper = $categoriesDesktopWrapper.cloneNode({ deep: true }); + $a.prepend(iconElement); + $listItem.append($a); + $categories.append($listItem); + }); - const $lottieArrows = createTag('a', { class: 'lottie-wrapper' }); - $mobileDrawerWrapper.append($lottieArrows); - $inWrapper.append($categoriesMobileWrapper); - $lottieArrows.innerHTML = getLottie('purple-arrows', '/express/icons/purple-arrows.json'); - lazyLoadLottiePlayer(); + $categoriesToggleWrapper.append($categoriesToggle); + $categoriesDesktopWrapper.append($categories); + const $categoriesMobileWrapper = $categoriesDesktopWrapper.cloneNode({ deep: true }); + $categoriesMobileWrapper.prepend($categoriesToggleWrapper); - $categoriesDesktopWrapper.classList.add('desktop-only'); + $desktopCategoriesToggleWrapper.append(desktopCategoriesToggle); + $categoriesDesktopWrapper.prepend($desktopCategoriesToggleWrapper, categoriesListHeading); - if ($blockWrapper) { - $blockWrapper.prepend($categoriesDesktopWrapper); - $blockWrapper.classList.add('with-categories-list'); - } + const $lottieArrows = createTag('a', { class: 'lottie-wrapper' }); + $mobileDrawerWrapper.append($lottieArrows); + $inWrapper.append($categoriesMobileWrapper); + $lottieArrows.innerHTML = getLottie('purple-arrows', '/express/icons/purple-arrows.json'); + lazyLoadLottiePlayer(); - const $toggleButton = $categoriesMobileWrapper.querySelector('.category-list-toggle-wrapper'); - $toggleButton.append(getIconElement('drop-down-arrow')); - $toggleButton.addEventListener('click', () => { - const $listWrapper = $toggleButton.parentElement; - $toggleButton.classList.toggle('collapsed'); - if ($toggleButton.classList.contains('collapsed')) { - if ($listWrapper.classList.contains('desktop-only')) { - $listWrapper.classList.add('collapsed'); - $listWrapper.style.maxHeight = '40px'; - } else { - $listWrapper.classList.add('collapsed'); - $listWrapper.style.maxHeight = '24px'; - } + $categoriesDesktopWrapper.classList.add('desktop-only'); + + if ($blockWrapper) { + $blockWrapper.prepend($categoriesDesktopWrapper); + $blockWrapper.classList.add('with-categories-list'); + } + + const $toggleButton = $categoriesMobileWrapper.querySelector('.category-list-toggle-wrapper'); + $toggleButton.append(getIconElement('drop-down-arrow')); + $toggleButton.addEventListener('click', () => { + const $listWrapper = $toggleButton.parentElement; + $toggleButton.classList.toggle('collapsed'); + if ($toggleButton.classList.contains('collapsed')) { + if ($listWrapper.classList.contains('desktop-only')) { + $listWrapper.classList.add('collapsed'); + $listWrapper.style.maxHeight = '40px'; } else { - $listWrapper.classList.remove('collapsed'); - $listWrapper.style.maxHeight = '1000px'; + $listWrapper.classList.add('collapsed'); + $listWrapper.style.maxHeight = '24px'; } + } else { + $listWrapper.classList.remove('collapsed'); + $listWrapper.style.maxHeight = '1000px'; + } - setTimeout(() => { - if (!$listWrapper.classList.contains('desktop-only')) { - updateLottieStatus($section); - } - }, 510); - }, { passive: true }); + setTimeout(() => { + if (!$listWrapper.classList.contains('desktop-only')) { + updateLottieStatus(section); + } + }, 510); + }, { passive: true }); - $lottieArrows.addEventListener('click', () => { - $inWrapper.scrollBy({ - top: 300, - behavior: 'smooth', - }); - }, { passive: true }); + $lottieArrows.addEventListener('click', () => { + $inWrapper.scrollBy({ + top: 300, + behavior: 'smooth', + }); + }, { passive: true }); - $inWrapper.addEventListener('scroll', () => { - updateLottieStatus($section); - }, { passive: true }); - } + $inWrapper.addEventListener('scroll', () => { + updateLottieStatus(section); + }, { passive: true }); } -async function decorateSearchFunctions($toolBar, $section, placeholders) { +async function decorateSearchFunctions($toolBar, $section, placeholders, props) { const $inBlockLocation = $toolBar.querySelector('.wrapper-content-search'); - const $inSectionLocation = $section.querySelector('.link-list-wrapper'); - const $searchBarWrapper = createTag('div', { class: 'search-bar-wrapper' }); + const $linkListLocation = document.querySelector('.link-list-fullwidth-container'); + const $searchBarWrapper = createTag('div', { class: 'template-list-search-bar-wrapper' }); const $searchForm = createTag('form', { class: 'search-form' }); const $searchBar = createTag('input', { class: 'search-bar', @@ -994,9 +1025,15 @@ async function decorateSearchFunctions($toolBar, $section, placeholders) { $stickySearchBarWrapper.classList.add('sticky-search-bar'); $stickySearchBarWrapper.classList.add('collapsed'); $inBlockLocation.append($stickySearchBarWrapper); - $inSectionLocation.insertAdjacentElement('beforebegin', $searchBarWrapper); - - initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper); + if ($linkListLocation) { + const linkListWrapper = $linkListLocation.querySelector('.link-list-wrapper'); + if (linkListWrapper) { + linkListWrapper.before($searchBarWrapper); + } else { + $linkListLocation.prepend($searchBarWrapper); + } + } + initSearchFunction($toolBar, $stickySearchBarWrapper, $searchBarWrapper, props); } function closeDrawer($toolBar) { @@ -1015,7 +1052,7 @@ function closeDrawer($toolBar) { }, 500); } -function updateOptionsStatus($block, $toolBar) { +function updateOptionsStatus($block, $toolBar, props) { const $wrappers = $toolBar.querySelectorAll('.function-wrapper'); $wrappers.forEach(($wrapper) => { @@ -1054,7 +1091,7 @@ function updateOptionsStatus($block, $toolBar) { }); } -function initDrawer($block, $section, $toolBar) { +function initDrawer($block, $section, $toolBar, props) { const $filterButton = $toolBar.querySelector('.filter-button-mobile-wrapper'); const $drawerBackground = $toolBar.querySelector('.drawer-background'); const $drawer = $toolBar.querySelector('.filter-drawer-mobile'); @@ -1090,7 +1127,7 @@ function initDrawer($block, $section, $toolBar) { $element.addEventListener('click', () => { props.filters = { ...currentFilters }; closeDrawer($toolBar); - updateOptionsStatus($block, $toolBar); + updateOptionsStatus($block, $toolBar, props); }, { passive: true }); }); @@ -1122,7 +1159,7 @@ function initDrawer($block, $section, $toolBar) { $drawer.classList.add('hidden'); } -function updateQueryURL(functionWrapper, option) { +function updateQueryURL(functionWrapper, option, props) { const paramType = functionWrapper.dataset.param; const paramValue = option.dataset.value; @@ -1145,7 +1182,7 @@ function updateQueryURL(functionWrapper, option) { } } -function updateLoadMoreButton($block, $loadMore) { +function updateLoadMoreButton($block, $loadMore, props) { if (props.start === '') { $loadMore.style.display = 'none'; } else { @@ -1153,12 +1190,12 @@ function updateLoadMoreButton($block, $loadMore) { } } -async function decorateNewTemplates($block, options = { reDrawMasonry: false }) { - const newTemplates = await processResponse(); +async function decorateNewTemplates($block, props, options = { reDrawMasonry: false }) { + const newTemplates = await processResponse(props); const $loadMore = $block.parentElement.querySelector('.load-more'); props.templates = props.templates.concat(newTemplates); - populateTemplates($block, newTemplates); + populateTemplates($block, newTemplates, props); const newCells = Array.from($block.querySelectorAll('.template:not(.appear)')); @@ -1170,11 +1207,11 @@ async function decorateNewTemplates($block, options = { reDrawMasonry: false }) props.masonry.draw(newCells); if ($loadMore) { - updateLoadMoreButton($block, $loadMore); + updateLoadMoreButton($block, $loadMore, props); } } -async function redrawTemplates($block, $toolBar) { +async function redrawTemplates($block, $toolBar, props) { const $heading = $toolBar.querySelector('h2'); const lang = getLanguage(getLocale(window.location)); const currentTotal = props.total.toLocaleString(lang); @@ -1184,9 +1221,9 @@ async function redrawTemplates($block, $toolBar) { $card.remove(); }); - await decorateNewTemplates($block, { reDrawMasonry: true }).then(() => { + await decorateNewTemplates($block, props, { reDrawMasonry: true }).then(() => { $heading.textContent = $heading.textContent.replace(`${currentTotal}`, props.total.toLocaleString(lang)); - updateOptionsStatus($block, $toolBar); + updateOptionsStatus($block, $toolBar, props); if ($block.querySelectorAll('.template').length <= 0) { const $viewButtons = $toolBar.querySelectorAll('.view-toggle-button'); $viewButtons.forEach(($button) => { @@ -1199,7 +1236,7 @@ async function redrawTemplates($block, $toolBar) { }); } -async function toggleAnimatedText($block, $toolBar) { +async function toggleAnimatedText($block, $toolBar, props) { const section = $block.closest('.section.template-list-fullwidth-apipowered-container'); const $toolbarWrapper = $toolBar.parentElement; @@ -1219,7 +1256,7 @@ async function toggleAnimatedText($block, $toolBar) { } } -function initFilterSort($block, $toolBar) { +function initFilterSort($block, $toolBar, props) { const $buttons = $toolBar.querySelectorAll('.button-wrapper'); const $applyFilterButton = $toolBar.querySelector('.apply-filter-button'); @@ -1261,15 +1298,15 @@ function initFilterSort($block, $toolBar) { }); $option.classList.add('active'); - updateQueryURL($wrapper, $option); + updateQueryURL($wrapper, $option, props); updateFilterIcon($block); if (!$optionsList.classList.contains('in-drawer')) { - await toggleAnimatedText($block, $toolBar); + await toggleAnimatedText($block, $toolBar, props); } if (!$button.classList.contains('in-drawer')) { - await redrawTemplates($block, $toolBar); + await redrawTemplates($block, $toolBar, props); } }; @@ -1300,14 +1337,14 @@ function initFilterSort($block, $toolBar) { if ($applyFilterButton) { $applyFilterButton.addEventListener('click', async (e) => { e.preventDefault(); - await redrawTemplates($block, $toolBar); + await redrawTemplates($block, $toolBar, props); closeDrawer($toolBar); - await toggleAnimatedText($block, $toolBar); + await toggleAnimatedText($block, $toolBar, props); }); } // sync current filter & sorting method with toolbar current options - updateOptionsStatus($block, $toolBar); + updateOptionsStatus($block, $toolBar, props); updateFilterIcon($block); } } @@ -1355,7 +1392,7 @@ function getPlaceholderWidth($block) { return width; } -function toggleMasonryView($block, $button, $toggleButtons) { +function toggleMasonryView($block, $button, $toggleButtons, props) { const $templatesToView = $block.querySelectorAll('.template:not(.placeholder)'); const $blockWrapper = $block.closest('.template-list-wrapper'); if (!$button.classList.contains('active') && $templatesToView.length > 0) { @@ -1375,7 +1412,7 @@ function toggleMasonryView($block, $button, $toggleButtons) { $block.classList.add(`${$button.dataset.view}-view`); $blockWrapper.classList.add(`${$button.dataset.view}-view`); - props.masonry.draw(); + props.masonry?.draw(); } else { $button.classList.remove('active'); ['sm-view', 'md-view', 'lg-view'].forEach((className) => { @@ -1383,14 +1420,14 @@ function toggleMasonryView($block, $button, $toggleButtons) { $blockWrapper.classList.remove(className); }); - props.masonry.draw(); + props.masonry?.draw(); } const placeholder = $block.querySelector('.template.placeholder'); const ratios = props.placeholderFormat; const width = getPlaceholderWidth($block); - if (ratios[1]) { + if (ratios?.length === 2) { const height = (ratios[1] / ratios[0]) * width; placeholder.style = `height: ${height - 21}px`; if (width / height > 1.3) { @@ -1399,20 +1436,28 @@ function toggleMasonryView($block, $button, $toggleButtons) { } } -function initViewToggle($block, $toolBar) { +function initViewToggle($block, $toolBar, props) { const $toggleButtons = $toolBar.querySelectorAll('.view-toggle-button '); $toggleButtons.forEach(($button, index) => { if (index === 0) { - toggleMasonryView($block, $button, $toggleButtons); + toggleMasonryView($block, $button, $toggleButtons, props); } $button.addEventListener('click', () => { - toggleMasonryView($block, $button, $toggleButtons); + toggleMasonryView($block, $button, $toggleButtons, props); }, { passive: true }); }); } +async function decorateBreadcrumbs(block) { + const parent = block.closest('.section'); + // breadcrumbs are desktop-only + if (document.body.dataset.device !== 'desktop') return; + const breadcrumbs = await getBreadcrumbs(); + if (breadcrumbs) parent.prepend(breadcrumbs); +} + function initToolbarShadow($block, $toolbar) { const $toolbarWrapper = $toolbar.parentElement; document.addEventListener('scroll', () => { @@ -1424,7 +1469,7 @@ function initToolbarShadow($block, $toolbar) { }); } -function decorateToolbar($block, $section, placeholders) { +function decorateToolbar($block, $section, placeholders, props) { const $toolBar = $section.querySelector('.api-templates-toolbar'); if ($toolBar) { @@ -1448,19 +1493,19 @@ function decorateToolbar($block, $section, placeholders) { $toolBar.append(toolBarFirstWrapper, functionsWrapper, $functions.mobile); - decorateSearchFunctions($toolBar, $section, placeholders); - initDrawer($block, $section, $toolBar); - initFilterSort($block, $toolBar); - initViewToggle($block, $toolBar); + decorateSearchFunctions($toolBar, $section, placeholders, props); + initDrawer($block, $section, $toolBar, props); + initFilterSort($block, $toolBar, props); + initViewToggle($block, $toolBar, props); initToolbarShadow($block, $toolBar); } } -export async function decorateTemplateList($block) { +export async function decorateTemplateList($block, props) { const locale = getLocale(window.location); - const placeholders = await fetchPlaceholders().then((result) => result); + const placeholders = await fetchPlaceholders(); if ($block.classList.contains('apipowered')) { - await readRowsFromBlock($block); + await readRowsFromBlock($block, props); const $parent = $block.closest('.section'); if ($parent) { @@ -1538,7 +1583,7 @@ export async function decorateTemplateList($block) { } else if (props.authoringError) { $sectionHeading.textContent = props.heading; } else { - $sectionHeading.textContent = await populateHeadingPlaceholder(locale) || ''; + $sectionHeading.textContent = await populateHeadingPlaceholder(locale, props) || ''; } } @@ -1546,6 +1591,12 @@ export async function decorateTemplateList($block) { $toolBar.classList.remove('default-content-wrapper'); $templateListWrapper.before($toolBarWrapper); + if (props.fallbackMsg) { + $templateListWrapper.classList.add('with-fallback-msg'); + const fallbackMsgWrapper = createTag('div', { class: 'template-list-fallback-msg-wrapper' }); + fallbackMsgWrapper.textContent = props.fallbackMsg; + $templateListWrapper.before(fallbackMsgWrapper); + } $toolBarWrapper.append($toolBar); $toolBar.append($contentWrapper, $functionsWrapper); $contentWrapper.append($sectionHeading); @@ -1555,15 +1606,13 @@ export async function decorateTemplateList($block) { } } - const $linkList = $parent.querySelector('.link-list-wrapper'); - if ($linkList - && $linkList.previousElementSibling.classList.contains('hero-animation-wrapper') - && placeholders['template-filter-premium']) { - document.addEventListener('linkspopulated', (e) => { - if (e.detail.length > 0 && e.detail[0].parentElement.classList.contains('template-list')) { - decorateToolbar($block, $parent, placeholders); - decorateCategoryList($block, $parent, placeholders); - appendCategoryTemplatesCount($parent); + if (placeholders['template-filter-premium']) { + document.addEventListener('linkspopulated', async (e) => { + // desktop/mobile fires the same event + if ($parent.contains(e.detail[0])) { + decorateToolbar($block, $parent, placeholders, props); + await decorateCategoryList($block, $parent, placeholders, props); + appendCategoryTemplatesCount($parent, props); } }); } @@ -1711,7 +1760,7 @@ export async function decorateTemplateList($block) { // // make copy of children to avoid modifying list while looping - populateTemplates($block, templates); + populateTemplates($block, templates, props); if ($block.classList.contains('spreadsheet-powered') && !$block.classList.contains('apipowered') @@ -1753,9 +1802,8 @@ export async function decorateTemplateList($block) { document.dispatchEvent(linksPopulated); } -async function decorateLoadMoreButton($block) { - const placeholders = await fetchPlaceholders() - .then((result) => result); +async function decorateLoadMoreButton($block, props) { + const placeholders = await fetchPlaceholders(); const $loadMoreDiv = createTag('div', { class: 'load-more' }); const $loadMoreButton = createTag('button', { class: 'load-more-button' }); const $loadMoreText = createTag('p', { class: 'load-more-text' }); @@ -1767,7 +1815,7 @@ async function decorateLoadMoreButton($block) { $loadMoreButton.addEventListener('click', async () => { $loadMoreButton.classList.add('disabled'); const scrollPosition = window.scrollY; - await decorateNewTemplates($block); + await decorateNewTemplates($block, props); window.scrollTo({ top: scrollPosition, left: 0, @@ -1779,11 +1827,11 @@ async function decorateLoadMoreButton($block) { return $loadMoreDiv; } -async function decorateTailButton($block) { +async function decorateTailButton($block, props) { const $carouselPlatform = $block.querySelector('.carousel-platform'); if ($block.classList.contains('spreadsheet-powered')) { - const placeholders = await fetchPlaceholders().then((result) => result); + const placeholders = await fetchPlaceholders(); if (placeholders['relevant-rows-view-all'] && (props.viewAllLink || placeholders['relevant-rows-view-all-link'])) { props.tailButton = createTag('a', { class: 'button accent tail-cta' }); @@ -1798,7 +1846,7 @@ async function decorateTailButton($block) { } } -function cacheCreatedTemplate($block) { +function cacheCreatedTemplate($block, props) { const lastRow = $block.children[$block.children.length - 1]; if (lastRow && lastRow.querySelector(':scope > div:first-of-type > img[src*=".svg"], :scope > div:first-of-type > svg')) { props.templates.push(lastRow.cloneNode(true)); @@ -1824,7 +1872,7 @@ function addBackgroundAnimation($block, animationUrl) { } } -async function replaceRRTemplateList($block) { +async function replaceRRTemplateList($block, props) { const placeholders = await fetchPlaceholders(); const relevantRowsData = await fetchRelevantRows(window.location.pathname); props.limit = parseInt(placeholders['relevant-rows-templates-limit'], 10) || 10; @@ -1879,16 +1927,57 @@ async function replaceRRTemplateList($block) { } } +function constructProps() { + const smScreen = window.matchMedia('(max-width: 900px)'); + const mdScreen = window.matchMedia('(min-width: 901px) and (max-width: 1200px)'); + const bgScreen = window.matchMedia('(max-width: 1440px)'); + const ratioSeparator = getMetadata('placeholder-format').includes(':') ? ':' : 'x'; + const ratioFromMetadata = getMetadata('placeholder-format') + .split(ratioSeparator) + .map((str) => parseInt(str, 10)); + + return { + templates: [], + filters: { + locales: getMetadata('locales') || '(en)', + tasks: getMetadata('tasks') || '', + topics: getMetadata('topics') || '', + premium: getMetadata('premium') || '', + animated: getMetadata('animated') || '', + }, + tailButton: '', + // eslint-disable-next-line no-nested-ternary + limit: smScreen.matches ? 20 : mdScreen.matches ? 30 : bgScreen.matches ? 40 : 70, + total: 0, + start: '', + sort: '-_score,-remixCount', + masonry: undefined, + authoringError: false, + headingTitle: null, + headingSlug: null, + viewAllLink: null, + placeholderFormat: ratioFromMetadata, + renditionParams: { + format: 'jpg', + dimension: 'width', + size: 151, + }, + }; +} + export default async function decorate($block) { + const props = constructProps(); if ($block.classList.contains('spreadsheet-powered')) { - await replaceRRTemplateList($block); + await replaceRRTemplateList($block, props); } if ($block.classList.contains('apipowered') && !$block.classList.contains('holiday')) { - cacheCreatedTemplate($block); + cacheCreatedTemplate($block, props); } - await decorateTemplateList($block); + await decorateBreadcrumbs($block); + + await decorateTemplateList($block, props); if ($block.classList.contains('horizontal')) { const requireInfiniteScroll = !$block.classList.contains('mini') && !$block.classList.contains('collaboration'); @@ -1898,15 +1987,15 @@ export default async function decorate($block) { } if ($block.classList.contains('apipowered') && !$block.classList.contains('holiday') && !$block.classList.contains('mini')) { - const $loadMore = await decorateLoadMoreButton($block); + const $loadMore = await decorateLoadMoreButton($block, props); if ($loadMore) { - updateLoadMoreButton($block, $loadMore); + updateLoadMoreButton($block, $loadMore, props); } } if ($block.classList.contains('mini') || $block.classList.contains('apipowered')) { - await decorateTailButton($block); + await decorateTailButton($block, props); } if ($block.classList.contains('holiday') && props.backgroundAnimation) { diff --git a/express/icons/template-free-accent.svg b/express/icons/template-free-accent.svg new file mode 100644 index 000000000..86b10c709 --- /dev/null +++ b/express/icons/template-free-accent.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/express/icons/template-search.svg b/express/icons/template-search.svg new file mode 100644 index 000000000..aab15ce2f --- /dev/null +++ b/express/icons/template-search.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/express/scripts/all-templates-metadata.js b/express/scripts/all-templates-metadata.js new file mode 100644 index 000000000..dc5186f24 --- /dev/null +++ b/express/scripts/all-templates-metadata.js @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { getHelixEnv, getLocale } from './scripts.js'; +import { memoize } from './utils.js'; + +const memoizedFetchUrl = memoize((url) => fetch(url).then((r) => (r.ok ? r.json() : null)), { + key: (q) => q, + ttl: 1000 * 60 * 60 * 24, +}); + +let allTemplatesMetadata; + +export default async function fetchAllTemplatesMetadata() { + const locale = getLocale(window.location); + const urlPrefix = locale === 'us' ? '' : `/${locale}`; + + if (!allTemplatesMetadata) { + try { + const env = getHelixEnv(); + const dev = new URLSearchParams(window.location.search).get('dev'); + let sheet; + + if (['yes', 'true', 'on'].includes(dev) && env?.name === 'stage') { + sheet = '/templates-dev.json?sheet=seo-templates&limit=100000'; + } else { + sheet = `${urlPrefix}/express/templates/default/metadata.json?limit=100000`; + } + + let resp = await memoizedFetchUrl(sheet); + allTemplatesMetadata = resp?.data; + + // TODO: remove the > 1 logic after publishing of the split metadata sheet + if (!(allTemplatesMetadata && allTemplatesMetadata.length > 1)) { + resp = await memoizedFetchUrl('/express/templates/content.json?sheet=seo-templates&limit=100000'); + allTemplatesMetadata = resp?.data?.map((p) => ({ + ...p, + // TODO: backward compatibility. Remove when we move away from helix-seo-templates + url: p.path, + title: p.metadataTitle, + description: p.metadataDescription, + 'short-title': p.shortTitle, + ckgid: p.ckgID, + 'hero-title': p.heroAnimationTitle, + 'hero-text': p.heroAnimationText, + locales: p.templateLocale, + premium: p.templatePremium, + animated: p.templateAnimated, + tasks: p.templateTasks, + topics: p.templateTopics, + 'placeholder-format': p.placeholderFormat, + 'create-link': p.createLink, + 'create-text': p.createText, + 'top-templates': p.topTemplates, + 'top-templates-text': p.topTemplatesText, + })) || []; + } + } catch (err) { + allTemplatesMetadata = []; + } + } + return allTemplatesMetadata; +} diff --git a/express/scripts/api-v3-controller.js b/express/scripts/api-v3-controller.js index 60a7c6afe..6eca0a3e0 100644 --- a/express/scripts/api-v3-controller.js +++ b/express/scripts/api-v3-controller.js @@ -10,11 +10,11 @@ * governing permissions and limitations under the License. */ -import { getHelixEnv, getLocale } from './scripts.js'; +import { getHelixEnv, getLocale, getMetadata } from './scripts.js'; const endpoints = { dev: { - cdn: '', + cdn: 'https://uss-templates-dev.adobe.io/uss/v3/query', url: 'https://uss-templates-dev.adobe.io/uss/v3/query', token: 'cd1823ed-0104-492f-ba91-25f4195d5f6c', }, @@ -69,8 +69,8 @@ export default async function getData(env = '', data = {}) { } } -export async function fetchLinkListFromCKGApi(pageData) { - if (pageData.ckgID) { +export async function fetchLinkListFromCKGApi() { + if (getMetadata('ckgid')) { const dataRaw = { experienceId: 'templates-browse-v1', locale: 'en_US', @@ -87,7 +87,7 @@ export async function fetchLinkListFromCKGApi(pageData) { filters: [ { categories: [ - pageData.ckgID, + getMetadata('ckgid'), ], }, ], @@ -102,7 +102,7 @@ export async function fetchLinkListFromCKGApi(pageData) { }; const env = getHelixEnv(); - const result = await getData(env.name, dataRaw).then((data) => data); + const result = await getData(env.name, dataRaw); if (result.status.httpCode === 200) { return result; } diff --git a/express/scripts/ckg-link-list.js b/express/scripts/ckg-link-list.js new file mode 100644 index 000000000..0fdfe4209 --- /dev/null +++ b/express/scripts/ckg-link-list.js @@ -0,0 +1,303 @@ +/* + * Copyright 2022 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { + titleCase, + getLocale, + getMetadata, +} from './scripts.js'; + +import { + fetchLinkListFromCKGApi, + getPillWordsMapping, +} from './api-v3-controller.js'; + +import { memoize } from './utils.js'; +import fetchAllTemplatesMetadata from './all-templates-metadata.js'; + +async function fetchLinkList() { + if (!window.linkLists) { + window.linkLists = {}; + if (!window.linkLists.ckgData) { + const response = await fetchLinkListFromCKGApi(); + // catch data from CKG API, if empty, use top priority categories sheet + if (response && response.queryResults[0].facets) { + window.linkLists.ckgData = response.queryResults[0].facets[0].buckets.map((ckgItem) => { + let formattedTasks; + if (getMetadata('template-search-page') === 'Y') { + const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), + }); + formattedTasks = titleCase(params.tasks).replace(/[$@%"]/g, ''); + } else { + formattedTasks = titleCase(getMetadata('tasks')).replace(/[$@%"]/g, ''); + } + + return { + parent: formattedTasks, + 'child-siblings': `${titleCase(ckgItem.displayValue)} ${formattedTasks}`, + ckgID: ckgItem.canonicalName, + displayValue: ckgItem.displayValue, + }; + }); + } + } + + if (!window.linkLists.sheetData) { + const resp = await fetch('/express/templates/top-priority-categories.json'); + window.linkLists.sheetData = resp.ok ? (await resp.json()).data : []; + } + } +} + +function matchCKGResult(ckgData, pageData) { + const ckgMatch = pageData.ckgID === ckgData.ckgID; + const pageDataTasks = pageData.tasks ?? pageData.templateTasks; + const taskMatch = ckgData.tasks?.toLowerCase() === pageDataTasks?.toLowerCase(); + const currentLocale = getLocale(window.location); + const pageLocale = pageData.url.split('/')[1] === 'express' ? 'us' : pageData.url.split('/')[1]; + const sameLocale = currentLocale === pageLocale; + + return sameLocale && ckgMatch && taskMatch; +} + +function replaceLinkPill(linkPill, data) { + const clone = linkPill.cloneNode(true); + if (data) { + clone.innerHTML = clone.innerHTML.replace('/express/templates/default', data.url); + clone.innerHTML = clone.innerHTML.replaceAll('Default', data.altShortTitle || data['short-title']); + } + return clone; +} + +async function updateSEOLinkList(container, linkPill, list) { + const templatePages = await fetchAllTemplatesMetadata(); + container.innerHTML = ''; + + if (list && templatePages) { + list.forEach((d) => { + const currentLocale = getLocale(window.location); + const templatePageData = templatePages.find((p) => { + const targetLocale = /^[a-z]{2}$/.test(p.url.split('/')[1]) ? p.url.split('/')[1] : 'us'; + const isLive = p.live === 'Y'; + const titleMatch = p['short-title'].toLowerCase() === d.childSibling.toLowerCase(); + const localeMatch = currentLocale === targetLocale; + + return isLive && titleMatch && localeMatch; + }); + + if (templatePageData) { + const clone = replaceLinkPill(linkPill, templatePageData); + container.append(clone); + } + }); + } +} + +function formatLinkPillText(linkPillData) { + const digestedDisplayValue = titleCase(linkPillData.displayValue.replace(/-/g, ' ')); + const digestedChildSibling = titleCase(linkPillData.childSibling.replace(/-/g, ' ')); + const topics = getMetadata('topics') !== '" "' ? `${getMetadata('topics').replace(/[$@%"]/g, '').replace(/-/g, ' ')}` : ''; + + const displayTopics = topics && linkPillData.childSibling.indexOf(titleCase(topics)) < 0 ? titleCase(topics) : ''; + let displayText; + + if (getMetadata('tasks')) { + displayText = `${displayTopics} ${digestedDisplayValue} ${digestedChildSibling}` + .split(' ') + .filter((item, i, allItems) => i === allItems.indexOf(item)) + .join(' ').trim(); + } else { + displayText = `${digestedDisplayValue} ${digestedChildSibling} ${displayTopics}` + .split(' ') + .filter((item, i, allItems) => i === allItems.indexOf(item)) + .join(' ').trim(); + } + + return displayText; +} + +const memoizedGetPillWordsMapping = memoize(getPillWordsMapping, { ttl: 1000 * 60 * 60 * 24 }); + +async function updateLinkList(container, linkPill, list) { + const templatePages = await fetchAllTemplatesMetadata(); + const pillsMapping = await memoizedGetPillWordsMapping(); + const pageLinks = []; + const searchLinks = []; + container.innerHTML = ''; + + if (list && templatePages) { + list.forEach((d) => { + const topics = getMetadata('topics') !== '" "' ? `${getMetadata('topics').replace(/[$@%"]/g, '')}` : ''; + const templatePageData = templatePages.find((p) => p.live === 'Y' && matchCKGResult(d, p)); + const topicsQuery = `${topics ?? topics} ${d.displayValue}`.split(' ') + .filter((item, i, allItems) => i === allItems.indexOf(item)) + .join(' ').trim(); + let displayText = formatLinkPillText(d); + + const locale = getLocale(window.location); + const urlPrefix = locale === 'us' ? '' : `/${locale}`; + const localeColumnString = locale === 'us' ? 'EN' : locale.toUpperCase(); + let hideUntranslatedPill = false; + + if (pillsMapping) { + const alternateText = pillsMapping.find((row) => getMetadata('url') === `${urlPrefix}${row['Express SEO URL']}` && d.ckgID === row['CKG Pill ID']); + + if (alternateText && alternateText[`${localeColumnString}`]) { + displayText = alternateText[`${localeColumnString}`]; + if (templatePageData) { + templatePageData.altShortTitle = displayText; + } + } + + hideUntranslatedPill = displayText && locale !== 'us'; + } + + if (templatePageData) { + const clone = replaceLinkPill(linkPill, templatePageData); + pageLinks.push(clone); + } else if (d.ckgID && !hideUntranslatedPill) { + const currentTasks = getMetadata('tasks') ? getMetadata('tasks').replace(/[$@%"]/g, '') : ' '; + const searchParams = `tasks=${currentTasks}&phformat=${getMetadata('placeholder-format')}&topics=${topicsQuery}&ckgid=${d.ckgID}`; + const clone = linkPill.cloneNode(true); + + clone.innerHTML = clone.innerHTML.replace('/express/templates/default', `${urlPrefix}/express/templates/search?${searchParams}`); + clone.innerHTML = clone.innerHTML.replaceAll('Default', displayText); + searchLinks.push(clone); + } + }); + + pageLinks.concat(searchLinks).forEach((clone) => { + container.append(clone); + }); + + if (container.children.length === 0) { + const linkListData = []; + + window.linkLists.sheetData.forEach((row) => { + if (row.parent === getMetadata('short-title')) { + linkListData.push({ + childSibling: row['child-siblings'], + shortTitle: getMetadata('short-title'), + tasks: getMetadata('tasks'), + }); + } + }); + + linkListData.forEach((d) => { + const templatePageData = templatePages.find((p) => p.live === 'Y' && p.shortTitle === d.childSibling); + const clone = replaceLinkPill(linkPill, templatePageData); + container.append(clone); + }); + } + } +} + +async function lazyLoadLinklist() { + await fetchLinkList(); + const linkList = document.querySelector('.link-list.fullwidth'); + + if (linkList) { + const linkListContainer = linkList.querySelector('p').parentElement; + const linkListTemplate = linkList.querySelector('p').cloneNode(true); + const linkListData = []; + + if (window.linkLists && window.linkLists.ckgData && getMetadata('short-title')) { + window.linkLists.ckgData.forEach((row) => { + linkListData.push({ + childSibling: row['child-siblings'], + ckgID: row.ckgID, + shortTitle: getMetadata('short-title'), + tasks: row.parent, + displayValue: row.displayValue, + }); + }); + } + + await updateLinkList(linkListContainer, linkListTemplate, linkListData); + linkList.style.visibility = 'visible'; + } else { + linkList?.remove(); + } +} + +async function lazyLoadSEOLinkList() { + await fetchLinkList(); + const seoNav = document.querySelector('.seo-nav'); + + if (seoNav) { + const topTemplatesContainer = seoNav.querySelector('p').parentElement; + const topTemplates = getMetadata('top-templates'); + if (topTemplates) { + const topTemplatesTemplate = seoNav.querySelector('p').cloneNode(true); + const topTemplatesData = topTemplates.split(', ').map((cs) => ({ childSibling: cs })); + + await updateSEOLinkList(topTemplatesContainer, topTemplatesTemplate, topTemplatesData); + topTemplatesContainer.style.visibility = 'visible'; + } else { + topTemplatesContainer.innerHTML = ''; + } + } +} + +async function lazyLoadSearchMarqueeLinklist() { + await fetchLinkList(); + const searchMarquee = document.querySelector('.search-marquee'); + + if (searchMarquee) { + const linkListContainer = searchMarquee.querySelector('.carousel-container > .carousel-platform'); + if (linkListContainer) { + const linkListTemplate = linkListContainer.querySelector('p').cloneNode(true); + + const linkListData = []; + + if (window.linkLists && window.linkLists.ckgData && getMetadata('short-title')) { + window.linkLists.ckgData.forEach((row) => { + linkListData.push({ + childSibling: row['child-siblings'], + ckgID: row.ckgID, + shortTitle: getMetadata('short-title'), + tasks: row.parent, // task on the page + displayValue: row.displayValue, + }); + }); + } + + await updateLinkList(linkListContainer, linkListTemplate, linkListData); + linkListContainer.parentElement.classList.add('appear'); + } + } +} + +function hideAsyncBlocks() { + const linkList = document.querySelector('.link-list.fullwidth'); + const seoNav = document.querySelector('.seo-nav'); + + if (linkList) { + linkList.style.visibility = 'hidden'; + } + + if (seoNav) { + const topTemplatesContainer = seoNav.querySelector('p').parentElement; + topTemplatesContainer.style.visibility = 'hidden'; + } +} + +(async function updateAsyncBlocks() { + hideAsyncBlocks(); + // TODO: integrate memoization + const showSearchMarqueeLinkList = getMetadata('show-search-marquee-link-list'); + if (document.body.dataset.device === 'desktop' && (!showSearchMarqueeLinkList || ['yes', 'true', 'on', 'Y'].includes(showSearchMarqueeLinkList))) { + await lazyLoadSearchMarqueeLinklist(); + } + await lazyLoadLinklist(); + await lazyLoadSEOLinkList(); +}()); diff --git a/express/scripts/content-replace.js b/express/scripts/content-replace.js new file mode 100644 index 000000000..8eb5210dc --- /dev/null +++ b/express/scripts/content-replace.js @@ -0,0 +1,217 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + fetchPlaceholders, + getMetadata, + titleCase, + createTag, +} from './scripts.js'; +import fetchAllTemplatesMetadata from './all-templates-metadata.js'; + +async function replaceDefaultPlaceholders(template) { + template.innerHTML = template.innerHTML.replaceAll('https://www.adobe.com/express/templates/default-create-link', getMetadata('create-link') || '/'); + + if (getMetadata('tasks') === '') { + const placeholders = await fetchPlaceholders(); + template.innerHTML = template.innerHTML.replaceAll('default-create-link-text', placeholders['start-from-scratch'] || ''); + } else { + template.innerHTML = template.innerHTML.replaceAll('default-create-link-text', getMetadata('create-text') || ''); + } +} + +async function getReplacementsFromSearch() { + const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), + }); + const { + tasks, + phformat, + topics, + q, + } = params; + if (!tasks && !phformat) { + return null; + } + const placeholders = await fetchPlaceholders(); + const categories = JSON.parse(placeholders['task-categories']); + if (!categories) { + return null; + } + const tasksPair = Object.entries(categories).find((cat) => cat[1] === tasks); + const sanitizedTasks = tasks === "''" ? '' : tasks; + const sanitizedTopics = topics === "''" ? '' : topics; + const sanitizedQuery = q === "''" ? '' : q; + const translatedTasks = tasksPair ? tasksPair[0].toLowerCase() : tasks; + return { + '{{queryTasks}}': sanitizedTasks || '', + '{{QueryTasks}}': titleCase(sanitizedTasks || ''), + '{{translatedTasks}}': translatedTasks || '', + '{{TranslatedTasks}}': titleCase(translatedTasks || ''), + '{{placeholderRatio}}': phformat || '', + '{{QueryTopics}}': titleCase(sanitizedTopics || ''), + '{{queryTopics}}': sanitizedTopics || '', + '{{query}}': sanitizedQuery || '', + }; +} + +const bladeRegex = /\{\{[a-zA-Z_-]+\}\}/g; +function replaceBladesInStr(str, replacements) { + if (!replacements) return str; + return str.replaceAll(bladeRegex, (match) => { + if (match in replacements) { + return replacements[match]; + } + return match; + }); +} + +// for backwards compatibility +// TODO: remove this func after all content is updated +// legacy json -> metadata & dom blades +await (async function updateLegacyContent() { + const searchMarquee = document.querySelector('.search-marquee'); + if (searchMarquee) { + // not legacy + return; + } + const legacyAllTemplatesMetadata = await fetchAllTemplatesMetadata(); + const data = legacyAllTemplatesMetadata.find((p) => p.url === window.location.pathname); + if (!data) return; + if (['yes', 'true', 'on', 'Y'].includes(getMetadata('template-search-page'))) { + const replacements = await getReplacementsFromSearch(); + if (!replacements) return; + for (const key of Object.keys(data)) { + data[key] = replaceBladesInStr(data[key], replacements); + } + } + + const heroAnimation = document.querySelector('.hero-animation.wide'); + const templateList = document.querySelector('.template-list.fullwidth.apipowered'); + + const head = document.querySelector('head'); + Object.keys(data).forEach((metadataKey) => { + const existingMetadataTag = head.querySelector(`meta[name=${metadataKey}]`); + if (existingMetadataTag) { + existingMetadataTag.setAttribute('content', data[metadataKey]); + } else { + head.append(createTag('meta', { name: `${metadataKey}`, content: data[metadataKey] })); + } + }); + + if (heroAnimation) { + if (data.heroAnimationTitle) { + heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template title', data.heroAnimationTitle); + } + + if (data.heroAnimationText) { + heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template text', data.heroAnimationText); + } + } + + if (templateList) { + const regex = /default-[a-zA-Z_-]+/g; + const replacements = { + 'default-title': data.shortTitle || '', + 'default-tasks': data.templateTasks || '', + 'default-topics': data.templateTopics || '', + 'default-locale': data.templateLocale || 'en', + 'default-premium': data.templatePremium || '', + 'default-animated': data.templateAnimated || '', + 'default-format': data.placeholderFormat || '', + }; + templateList.innerHTML = templateList.innerHTML.replaceAll(regex, (match) => { + if (match in replacements) { + return replacements[match]; + } + return match; + }).replaceAll('https://www.adobe.com/express/templates/default-create-link', data.createLink || '/'); + + if (data.templateTasks === '') { + const placeholders = await fetchPlaceholders(); + templateList.innerHTML = templateList.innerHTML.replaceAll('default-create-link-text', placeholders['start-from-scratch'] || ''); + } else { + templateList.innerHTML = templateList.innerHTML.replaceAll('default-create-link-text', data.createText || ''); + } + } +}()); + +// searchbar -> metadata blades +await (async function updateMetadataForTemplates() { + if (!['yes', 'true', 'on', 'Y'].includes(getMetadata('template-search-page'))) { + return; + } + const head = document.querySelector('head'); + if (head) { + const replacements = await getReplacementsFromSearch(); + if (!replacements) return; + head.innerHTML = replaceBladesInStr(head.innerHTML, replacements); + } +}()); + +// metadata -> dom blades +(function autoUpdatePage() { + const wl = ['{{heading_placeholder}}', '{{type}}', '{{quantity}}']; + // FIXME: deprecate wl + const main = document.querySelector('main'); + if (!main) return; + const regex = /\{\{([a-zA-Z_-]+)\}\}/g; + main.innerHTML = main.innerHTML.replaceAll(regex, (match, p1) => { + if (!wl.includes(match.toLowerCase())) { + return getMetadata(p1); + } + return match; + }); +}()); + +// cleanup remaining dom blades +(async function updateNonBladeContent() { + const heroAnimation = document.querySelector('.hero-animation.wide'); + const templateList = document.querySelector('.template-list.fullwidth.apipowered'); + const templateX = document.querySelector('.template-x'); + const browseByCat = document.querySelector('.browse-by-category'); + const seoNav = document.querySelector('.seo-nav'); + + if (heroAnimation) { + if (getMetadata('hero-title')) { + heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template title', getMetadata('hero-title')); + } + + if (getMetadata('hero-text')) { + heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template text', getMetadata('hero-text')); + } + } + + if (templateList) { + await replaceDefaultPlaceholders(templateList); + } + + if (templateX) { + await replaceDefaultPlaceholders(templateX); + } + + if (seoNav) { + if (getMetadata('top-templates-title')) { + seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates title', getMetadata('top-templates-title')); + } + + if (getMetadata('top-templates-text')) { + seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates text', getMetadata('top-templates-text')); + } else { + seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates text', ''); + } + } + + if (browseByCat && !['yes', 'true', 'on', 'Y'].includes(getMetadata('show-browse-by-category'))) { + browseByCat.remove(); + } +}()); diff --git a/express/scripts/gnav.js b/express/scripts/gnav.js index 5dc9a26cd..a84059a12 100644 --- a/express/scripts/gnav.js +++ b/express/scripts/gnav.js @@ -152,7 +152,7 @@ async function loadFEDS() { async function buildBreadCrumbArray() { if (isHomepage || getMetadata('hide-breadcrumbs') === 'true') { - return undefined; + return null; } const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1); const buildBreadCrumb = (path, name, parentPath = '') => ( @@ -160,31 +160,27 @@ async function loadFEDS() { ); const placeholders = await fetchPlaceholders(); - const validCategories = ['create', 'feature', 'templates']; - const pathSegments = window.location.pathname.split('/') - .filter((element) => element !== '') - .filter((element) => element !== locale); + const validSecondPathSegments = ['create', 'feature']; + const pathSegments = window.location.pathname + .split('/') + .filter((e) => e !== locale); const localePath = locale === 'us' ? '' : `${locale}/`; - let category = pathSegments[1]; - const secondPathSegment = category.toLowerCase(); - const pagesShortNameElement = document.querySelector('meta[name="short-title"]'); - const pagesShortName = pagesShortNameElement ? pagesShortNameElement.getAttribute('content') : null; + const secondPathSegment = pathSegments[1].toLowerCase(); + const pagesShortNameElement = document.head.querySelector('meta[name="short-title"]'); + const pagesShortName = pagesShortNameElement?.getAttribute('content') ?? null; + const replacedCategory = placeholders[`breadcrumbs-${secondPathSegment}`]; - if ((!pagesShortName && pathSegments.length > 2) - || !placeholders[`breadcrumbs-${category}`] + if (!pagesShortName + || pathSegments.length <= 2 + || !replacedCategory + || !validSecondPathSegments.includes(replacedCategory) || locale !== 'us') { // Remove this line once locale translations are complete - return undefined; + return null; } - category = capitalize(placeholders[`breadcrumbs-${category}`]); - validCategories.push(category); - const secondBreadCrumb = buildBreadCrumb(secondPathSegment, category, `${localePath}/express`); + const secondBreadCrumb = buildBreadCrumb(secondPathSegment, capitalize(replacedCategory), `${localePath}/express`); const breadCrumbList = [secondBreadCrumb]; - if (!validCategories.includes(category)) { - return undefined; - } - if (pathSegments.length >= 3) { const thirdBreadCrumb = buildBreadCrumb(pagesShortName, pagesShortName, secondBreadCrumb.url); breadCrumbList.push(thirdBreadCrumb); diff --git a/express/scripts/scripts.js b/express/scripts/scripts.js index 82a613022..9a60e6174 100644 --- a/express/scripts/scripts.js +++ b/express/scripts/scripts.js @@ -1773,13 +1773,13 @@ export async function fetchFloatingCta(path) { } if (['yes', 'true', 'on'].includes(dev) && env && env.name === 'stage') { - spreadsheet = '/express/floating-cta-dev.json?limit=10000'; + spreadsheet = '/express/floating-cta-dev.json?limit=100000'; } else { - spreadsheet = '/express/floating-cta.json?limit=10000'; + spreadsheet = '/express/floating-cta.json?limit=100000'; } if (experimentStatus === 'active') { - const expSheet = '/express/experiments/floating-cta-experiments.json?limit=10000'; + const expSheet = '/express/experiments/floating-cta-experiments.json?limit=100000'; floatingBtnData = await fetchFloatingBtnData(expSheet); } @@ -2328,8 +2328,14 @@ async function loadEager() { } if (!window.hlx.lighthouse) await decorateTesting(); - if (window.location.href.includes('/express/templates/')) { - await import('./templates.js'); + // for backward compatibility + // TODO: remove the href check after we tag content with sheet-powered + if (getMetadata('sheet-powered') === 'Y' || window.location.href.includes('/express/templates/')) { + await import('./content-replace.js'); + } + + if (getMetadata('template-search-page') === 'Y') { + await import('./template-redirect.js'); } if (main) { @@ -2342,10 +2348,14 @@ async function loadEager() { displayOldLinkWarning(); wordBreakJapanese(); - const lcpBlocks = ['columns', 'hero-animation', 'hero-3d', 'template-list', 'floating-button', 'fullscreen-marquee', 'collapsible-card']; - const block = document.querySelector('.block'); - const hasLCPBlock = (block && lcpBlocks.includes(block.getAttribute('data-block-name'))); - if (hasLCPBlock) await loadBlock(block, true); + const lcpBlocks = ['columns', 'hero-animation', 'hero-3d', 'template-list', 'floating-button', 'fullscreen-marquee', 'collapsible-card', 'search-marquee']; + const blocks = document.querySelectorAll('.block'); + const firstVisualBlock = Array.from(blocks).find((b) => { + const { audience } = b.closest('.section')?.dataset || {}; + return audience === document.body.dataset.device; + }); + const hasLCPBlock = (firstVisualBlock && lcpBlocks.includes(firstVisualBlock.getAttribute('data-block-name'))); + if (hasLCPBlock) await loadBlock(firstVisualBlock, true); document.querySelector('body').classList.add('appear'); @@ -2367,7 +2377,9 @@ async function loadEager() { await new Promise((resolve) => { if (lcpCandidate && !lcpCandidate.complete) { lcpCandidate.setAttribute('loading', 'eager'); - lcpCandidate.addEventListener('load', () => resolve()); + lcpCandidate.addEventListener('load', () => { + resolve(); + }); lcpCandidate.addEventListener('error', () => resolve()); } else { resolve(); diff --git a/express/scripts/template-redirect.js b/express/scripts/template-redirect.js new file mode 100644 index 000000000..1e07e1f3f --- /dev/null +++ b/express/scripts/template-redirect.js @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { getHelixEnv, getLocale, getMetadata } from './scripts.js'; +import fetchAllTemplatesMetadata from './all-templates-metadata.js'; + +async function existsTemplatePage(url) { + const allTemplatesMetadata = await fetchAllTemplatesMetadata(); + return allTemplatesMetadata.some((e) => e.url === url); +} + +(function validatePage() { + const env = getHelixEnv(); + const title = document.querySelector('title'); + if ((env && env.name !== 'stage') && getMetadata('live') === 'N') { + window.location.replace('/express/templates/'); + } + + if ((env && env.name !== 'stage') || (title && title.innerText.match(/{{(.*?)}}/))) { + window.location.replace('/404'); + } +}()); + +(async function redirectToExistingPage() { + // TODO: check if the search query points to an existing page. If so, redirect. + const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), + }); + if (params.topics) { + const targetPath = `/express/templates/${params.tasks}`.concat(params.topics ? `/${params.topics}` : ''); + const locale = getLocale(window.location); + const pathToMatch = locale === 'us' ? targetPath : `/${locale}${targetPath}`; + if (await existsTemplatePage(pathToMatch)) { + window.location.replace(`${window.location.origin}${pathToMatch}`); + } + } +}()); diff --git a/express/scripts/templates.js b/express/scripts/templates.js deleted file mode 100644 index 94ce6f272..000000000 --- a/express/scripts/templates.js +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright 2022 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -import { - getHelixEnv, - arrayToObject, - titleCase, - createTag, - fetchPlaceholders, - getLocale, - getMetadata, -} from './scripts.js'; - -import { - fetchLinkListFromCKGApi, - getPillWordsMapping, -} from './api-v3-controller.js'; - -export function findMatchExistingSEOPage(path) { - const pathMatch = (e) => e.path === path; - return (window.templates && window.templates.data.some(pathMatch)); -} - -export async function fetchPageContent(path) { - const env = getHelixEnv(); - const dev = new URLSearchParams(window.location.search).get('dev'); - let sheet; - - if (['yes', 'true', 'on'].includes(dev) && env && env.name === 'stage') { - sheet = '/templates-dev.json?sheet=seo-templates&limit=10000'; - } else { - sheet = '/express/templates/content.json?sheet=seo-templates&limit=10000'; - } - - if (!(window.templates && window.templates.data)) { - window.templates = {}; - const resp = await fetch(sheet); - window.templates.data = resp.ok ? (await resp.json()).data : []; - } - - const page = window.templates.data.find((p) => p.path === path); - - if (env && env.name === 'stage') { - return page || null; - } - - return page && page.live !== 'N' ? page : null; -} - -async function formatSearchQuery(data) { - // todo check if the search query points to an existing page. If so, redirect. - const params = new Proxy(new URLSearchParams(window.location.search), { - get: (searchParams, prop) => searchParams.get(prop), - }); - - const locale = getLocale(window.location); - const targetPath = `/express/templates/${params.tasks}`.concat(params.topics ? `/${params.topics}` : ''); - const pathToMatch = locale === 'us' ? targetPath : `/${locale}${targetPath}`; - - if (findMatchExistingSEOPage(pathToMatch)) { - window.location.replace(`${window.location.origin}${pathToMatch}`); - } - - const dataArray = Object.entries(data); - - if (params.tasks && params.phformat) { - const placeholders = await fetchPlaceholders(); - const categories = JSON.parse(placeholders['task-categories']); - if (categories) { - const TasksPair = Object.entries(categories).find((cat) => cat[1] === params.tasks); - const translatedTasks = TasksPair ? TasksPair[0].toLowerCase() : params.tasks; - dataArray.forEach((col) => { - col[1] = col[1].replace('{{queryTasks}}', params.tasks || ''); - col[1] = col[1].replace('{{QueryTasks}}', titleCase(params.tasks || '')); - col[1] = col[1].replace('{{translatedTasks}}', translatedTasks || ''); - col[1] = col[1].replace('{{TranslatedTasks}}', titleCase(translatedTasks || '')); - col[1] = col[1].replace('{{placeholderRatio}}', params.phformat || ''); - col[1] = col[1].replace('{{QueryTopics}}', titleCase(params.topics || '')); - col[1] = col[1].replace('{{queryTopics}}', params.topics || ''); - }); - } - } else { - return false; - } - - return arrayToObject(dataArray); -} - -async function fetchLinkList(data) { - if (!window.linkLists) { - window.linkLists = {}; - if (!window.linkLists.ckgData) { - const response = await fetchLinkListFromCKGApi(data); - // catch data from CKG API, if empty, use top priority categories sheet - if (response && response.queryResults[0].facets) { - window.linkLists.ckgData = response.queryResults[0].facets[0].buckets.map((ckgItem) => { - let formattedTasks; - if (getMetadata('template-search-page') === 'Y') { - const params = new Proxy(new URLSearchParams(window.location.search), { - get: (searchParams, prop) => searchParams.get(prop), - }); - formattedTasks = titleCase(params.tasks).replace(/[$@%"]/g, ''); - } else { - formattedTasks = titleCase(data.templateTasks).replace(/[$@%"]/g, ''); - } - - return { - parent: formattedTasks, - 'child-siblings': `${titleCase(ckgItem.displayValue)} ${formattedTasks}`, - ckgID: ckgItem.canonicalName, - displayValue: ckgItem.displayValue, - }; - }); - } - } - - if (!window.linkLists.sheetData) { - const resp = await fetch('/express/templates/top-priority-categories.json'); - window.linkLists.sheetData = resp.ok ? (await resp.json()).data : []; - } - } -} - -function matchCKGResult(ckgData, pageData) { - const ckgMatch = pageData.ckgID === ckgData.ckgID; - const taskMatch = ckgData.tasks.toLowerCase() === pageData.templateTasks.toLowerCase(); - const currentLocale = getLocale(window.location); - const pageLocale = pageData.path.split('/')[1] === 'express' ? 'us' : pageData.path.split('/')[1]; - const sameLocale = currentLocale === pageLocale; - - return sameLocale && ckgMatch && taskMatch; -} - -function replaceLinkPill(linkPill, data) { - const clone = linkPill.cloneNode(true); - if (data) { - clone.innerHTML = clone.innerHTML.replace('/express/templates/default', data.path); - clone.innerHTML = clone.innerHTML.replaceAll('Default', data.altShortTitle || data.shortTitle); - } - return clone; -} - -function updateSEOLinkList(container, linkPill, list) { - const templatePages = window.templates.data ?? []; - container.innerHTML = ''; - - if (list && templatePages) { - list.forEach((d) => { - const currentLocale = getLocale(window.location); - const templatePageData = templatePages.find((p) => { - const targetLocale = /^[a-z]{2}$/.test(p.path.split('/')[1]) ? p.path.split('/')[1] : 'us'; - const isLive = p.live === 'Y'; - const titleMatch = p.shortTitle.toLowerCase() === d.childSibling.toLowerCase(); - const localeMatch = currentLocale === targetLocale; - - return isLive && titleMatch && localeMatch; - }); - const clone = replaceLinkPill(linkPill, templatePageData); - container.append(clone); - }); - } -} - -function formatLinkPillText(pageData, linkPillData) { - const digestedDisplayValue = titleCase(linkPillData.displayValue.replace(/-/g, ' ')); - const digestedChildSibling = titleCase(linkPillData.childSibling.replace(/-/g, ' ')); - const topics = pageData.templateTopics !== '" "' ? `${pageData.templateTopics.replace(/[$@%"]/g, '').replace(/-/g, ' ')}` : ''; - - const displayTopics = topics && linkPillData.childSibling.indexOf(titleCase(topics)) < 0 ? titleCase(topics) : ''; - let displayText; - - if (pageData.templateTasks) { - displayText = `${displayTopics} ${digestedDisplayValue} ${digestedChildSibling}` - .split(' ') - .filter((item, i, allItems) => i === allItems.indexOf(item)) - .join(' ').trim(); - } else { - displayText = `${digestedDisplayValue} ${digestedChildSibling} ${displayTopics}` - .split(' ') - .filter((item, i, allItems) => i === allItems.indexOf(item)) - .join(' ').trim(); - } - - return displayText; -} - -async function updateLinkList(container, linkPill, list, pageData) { - const templatePages = window.templates.data ?? []; - const pillsMapping = await getPillWordsMapping(); - const pageLinks = []; - const searchLinks = []; - container.innerHTML = ''; - - if (list && templatePages) { - list.forEach((d) => { - const topics = pageData.templateTopics !== '" "' ? `${pageData.templateTopics.replace(/[$@%"]/g, '')}` : ''; - const templatePageData = templatePages.find((p) => p.live === 'Y' && matchCKGResult(d, p)); - const topicsQuery = `${topics ?? topics} ${d.displayValue}`.split(' ') - .filter((item, i, allItems) => i === allItems.indexOf(item)) - .join(' ').trim(); - let displayText = formatLinkPillText(pageData, d); - - const locale = getLocale(window.location); - const urlPrefix = locale === 'us' ? '' : `/${locale}`; - const localeColumnString = locale === 'us' ? 'EN' : locale.toUpperCase(); - let hideUntranslatedPill = false; - - if (pillsMapping) { - const alternateText = pillsMapping.find((row) => pageData.path === `${urlPrefix}${row['Express SEO URL']}` && d.ckgID === row['CKG Pill ID']); - - if (alternateText && alternateText[`${localeColumnString}`]) { - displayText = alternateText[`${localeColumnString}`]; - if (templatePageData) { - templatePageData.altShortTitle = displayText; - } - } - - hideUntranslatedPill = displayText && locale !== 'us'; - } - - if (templatePageData) { - const clone = replaceLinkPill(linkPill, templatePageData); - pageLinks.push(clone); - } else if (d.ckgID && !hideUntranslatedPill) { - const currentTasks = pageData.templateTasks ? pageData.templateTasks.replace(/[$@%"]/g, '') : ' '; - - const searchParams = `tasks=${currentTasks}&phformat=${pageData.placeholderFormat}&topics=${topicsQuery}&ckgid=${d.ckgID}`; - const clone = linkPill.cloneNode(true); - - clone.innerHTML = clone.innerHTML.replace('/express/templates/default', `${urlPrefix}/express/templates/search?${searchParams}`); - clone.innerHTML = clone.innerHTML.replaceAll('Default', displayText); - searchLinks.push(clone); - } - - pageLinks.concat(searchLinks).forEach((clone) => { - container.append(clone); - }); - }); - - if (container.children.length === 0) { - const linkListData = []; - - window.linkLists.sheetData.forEach((row) => { - if (row.parent === pageData.shortTitle) { - linkListData.push({ - childSibling: row['child-siblings'], - shortTitle: pageData.shortTitle, - tasks: pageData.templateTasks, - }); - } - }); - - linkListData.forEach((d) => { - const templatePageData = templatePages.find((p) => p.live === 'Y' && p.shortTitle === d.childSibling); - replaceLinkPill(linkPill, templatePageData, container); - }); - } - } -} - -function updateMetadata(data) { - const $head = document.querySelector('head'); - const $title = $head.querySelector('title'); - let $metaTitle = document.querySelector('meta[property="og:title"]'); - let $twitterTitle = document.querySelector('meta[name="twitter:title"]'); - let $description = document.querySelector('meta[property="og:description"]'); - - if ($title) { - $title.textContent = data.metadataTitle; - - if ($metaTitle) { - $metaTitle.setAttribute('content', data.metadataTitle); - } else { - $metaTitle = createTag('meta', { property: 'og:title', content: data.metadataTitle }); - $head.append($metaTitle); - } - - if ($description) { - $description.setAttribute('content', data.metadataDescription); - } else { - $description = createTag('meta', { property: 'og:description', content: data.metadataDescription }); - $head.append($description); - } - - if ($twitterTitle) { - $twitterTitle.setAttribute('content', data.metadataTitle); - } else { - $twitterTitle = createTag('meta', { property: 'twitter:title', content: data.metadataTitle }); - $head.append($twitterTitle); - } - } -} - -function formatAllTaskText(data) { - const formattedData = data; - - if (formattedData.templateTasks === "''" || formattedData.templateTopics === "''") { - Object.entries(formattedData).forEach((entry) => { - formattedData[entry[0]] = entry[1].replace("''", ''); - }); - } - - return formattedData; -} - -async function updateBlocks(data) { - const heroAnimation = document.querySelector('.hero-animation.wide'); - const linkList = document.querySelector('.link-list.fullwidth'); - const templateList = document.querySelector('.template-list.fullwidth.apipowered'); - const seoNav = document.querySelector('.seo-nav'); - - if (data.shortTitle) { - const shortTitle = createTag('meta', { name: 'short-title', content: data.shortTitle }); - const $head = document.querySelector('head'); - $head.append(shortTitle); - } - - if (heroAnimation) { - if (data.heroAnimationTitle) { - heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template title', data.heroAnimationTitle); - } - - if (data.heroAnimationText) { - heroAnimation.innerHTML = heroAnimation.innerHTML.replace('Default template text', data.heroAnimationText); - } - } - - const linkListContainer = linkList.querySelector('p').parentElement; - - if (linkList && window.templates.data) { - const linkListTemplate = linkList.querySelector('p').cloneNode(true); - const linkListData = []; - - if (window.linkLists && window.linkLists.ckgData && data.shortTitle) { - window.linkLists.ckgData.forEach((row) => { - linkListData.push({ - childSibling: row['child-siblings'], - ckgID: row.ckgID, - shortTitle: data.shortTitle, - tasks: row.parent, - displayValue: row.displayValue, - }); - }); - } - - await updateLinkList(linkListContainer, linkListTemplate, linkListData, data); - } else { - linkListContainer.remove(); - } - - if (templateList) { - templateList.innerHTML = templateList.innerHTML.replaceAll('default-title', data.shortTitle || ''); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-tasks', data.templateTasks || ''); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-topics', data.templateTopics || ''); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-locale', data.templateLocale || 'en'); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-premium', data.templatePremium || ''); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-animated', data.templateAnimated || ''); - templateList.innerHTML = templateList.innerHTML.replaceAll('https://www.adobe.com/express/templates/default-create-link', data.createLink || '/'); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-format', data.placeholderFormat || ''); - - if (data.templateTasks === '') { - const placeholders = await fetchPlaceholders().then((result) => result); - templateList.innerHTML = templateList.innerHTML.replaceAll('default-create-link-text', placeholders['start-from-scratch'] || ''); - } else { - templateList.innerHTML = templateList.innerHTML.replaceAll('default-create-link-text', data.createText || ''); - } - } - - if (seoNav) { - const topTemplatesContainer = seoNav.querySelector('p').parentElement; - - if (window.templates.data && data.topTemplates) { - const topTemplatesTemplate = seoNav.querySelector('p').cloneNode(true); - const topTemplatesData = data.topTemplates.split(', ').map((cs) => ({ childSibling: cs })); - - updateSEOLinkList(topTemplatesContainer, topTemplatesTemplate, topTemplatesData); - } else { - topTemplatesContainer.innerHTML = ''; - } - - if (data.topTemplatesTitle) { - seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates title', data.topTemplatesTitle); - } - - if (data.topTemplatesText) { - seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates text', data.topTemplatesText); - } else { - seoNav.innerHTML = seoNav.innerHTML.replace('Default top templates text', ''); - } - } -} - -const page = await fetchPageContent(window.location.pathname); - -if (page) { - await fetchLinkList(page); - if (getMetadata('template-search-page') === 'Y') { - const data = await formatSearchQuery(page); - if (!data) { - window.location.replace('/express/templates/'); - } else { - const purgedData = formatAllTaskText(data); - updateMetadata(purgedData); - await updateBlocks(purgedData); - } - } else { - await updateBlocks(page); - } -} else { - const env = getHelixEnv(); - - if ((env && env.name !== 'stage') || window.location.pathname !== '/express/templates/default') { - window.location.replace('/404'); - } -} diff --git a/express/scripts/utils.js b/express/scripts/utils.js index 69a3e2223..3fa7ca605 100644 --- a/express/scripts/utils.js +++ b/express/scripts/utils.js @@ -92,6 +92,7 @@ export function memoize(cb, { key = (...args) => args.join(','), ttl } = {}) { } return result; } catch (e) { + // eslint-disable-next-line no-console console.error('Memoized Callback Error: ', e); throw e; }