diff --git a/404.html b/404.html index c21fc249..291959fe 100644 --- a/404.html +++ b/404.html @@ -11,7 +11,7 @@ + + - + diff --git a/helix-sitemap.yaml b/helix-sitemap.yaml new file mode 100644 index 00000000..159159c6 --- /dev/null +++ b/helix-sitemap.yaml @@ -0,0 +1,5 @@ +sitemaps: + example: + source: /query-index.json + destination: /sitemap.xml + lastmod: YYYY-MM-DD \ No newline at end of file diff --git a/icons/Navigating Population Health Management-Takeda Overview-1.png b/icons/Navigating Population Health Management-Takeda Overview-1.png new file mode 100644 index 00000000..291cfa04 Binary files /dev/null and b/icons/Navigating Population Health Management-Takeda Overview-1.png differ diff --git a/icons/PDF.svg b/icons/PDF.svg new file mode 100644 index 00000000..ca6a84dd --- /dev/null +++ b/icons/PDF.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/Takeda White Paper - Rare Disease US Policy-1.png b/icons/Takeda White Paper - Rare Disease US Policy-1.png new file mode 100644 index 00000000..7e914096 Binary files /dev/null and b/icons/Takeda White Paper - Rare Disease US Policy-1.png differ diff --git a/icons/YouTube.svg b/icons/YouTube.svg new file mode 100644 index 00000000..6feaa5f1 --- /dev/null +++ b/icons/YouTube.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/arrow-right-white.svg b/icons/arrow-right-white.svg new file mode 100644 index 00000000..3e42c718 --- /dev/null +++ b/icons/arrow-right-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/arrow-right.svg b/icons/arrow-right.svg new file mode 100644 index 00000000..12fca75f --- /dev/null +++ b/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/blood.svg b/icons/blood.svg new file mode 100644 index 00000000..09dbc2da --- /dev/null +++ b/icons/blood.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/button-icon.png b/icons/button-icon.png new file mode 100644 index 00000000..226ca7b3 Binary files /dev/null and b/icons/button-icon.png differ diff --git a/icons/button-icon.svg b/icons/button-icon.svg new file mode 100644 index 00000000..70512bc1 --- /dev/null +++ b/icons/button-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/button-window.svg b/icons/button-window.svg new file mode 100644 index 00000000..5d4794b2 --- /dev/null +++ b/icons/button-window.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/check-circle.svg b/icons/check-circle.svg deleted file mode 100644 index d6db5307..00000000 --- a/icons/check-circle.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - Group 3 - - - - - - - - - \ No newline at end of file diff --git a/icons/checkmark.svg b/icons/checkmark.svg index 630143c0..7f68495f 100644 --- a/icons/checkmark.svg +++ b/icons/checkmark.svg @@ -1,12 +1,3 @@ - - - Group 2 - - - - - - - - + + diff --git a/icons/cheveron-down.svg b/icons/cheveron-down.svg new file mode 100644 index 00000000..aa2c54da --- /dev/null +++ b/icons/cheveron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/cheveron-up.svg b/icons/cheveron-up.svg new file mode 100644 index 00000000..e1708550 --- /dev/null +++ b/icons/cheveron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/chevron-down.svg b/icons/chevron-down.svg deleted file mode 100644 index 125b32b1..00000000 --- a/icons/chevron-down.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - Group 12 Copy - - - - - - - - \ No newline at end of file diff --git a/icons/close.svg b/icons/close.svg index 3f06ffff..65c3ec99 100644 --- a/icons/close.svg +++ b/icons/close.svg @@ -1,14 +1,10 @@ - - - Group 5 - - - - - - - - - - + + + + + + + + + diff --git a/icons/document.svg b/icons/document.svg index 232ba346..d7dae70a 100644 --- a/icons/document.svg +++ b/icons/document.svg @@ -1 +1,16 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + diff --git a/icons/download-button.svg b/icons/download-button.svg new file mode 100644 index 00000000..a010a1fd --- /dev/null +++ b/icons/download-button.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/droplet.svg b/icons/droplet.svg new file mode 100644 index 00000000..aaac60a1 --- /dev/null +++ b/icons/droplet.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/email_2.svg b/icons/email_2.svg new file mode 100644 index 00000000..277f52f8 --- /dev/null +++ b/icons/email_2.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/icons/external_link.svg b/icons/external_link.svg new file mode 100644 index 00000000..35c287a7 --- /dev/null +++ b/icons/external_link.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/facebook.svg b/icons/facebook.svg new file mode 100644 index 00000000..70d0a54a --- /dev/null +++ b/icons/facebook.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/globe.svg b/icons/globe.svg new file mode 100644 index 00000000..d9314e8e --- /dev/null +++ b/icons/globe.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/health.svg b/icons/health.svg new file mode 100644 index 00000000..98abd5e9 --- /dev/null +++ b/icons/health.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/icons/instagram.svg b/icons/instagram.svg new file mode 100644 index 00000000..388128f1 --- /dev/null +++ b/icons/instagram.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/jump_link.svg b/icons/jump_link.svg new file mode 100644 index 00000000..71d2a265 --- /dev/null +++ b/icons/jump_link.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/laptop.png b/icons/laptop.png new file mode 100644 index 00000000..c9040c66 Binary files /dev/null and b/icons/laptop.png differ diff --git a/icons/menu.svg b/icons/menu.svg new file mode 100644 index 00000000..3ad9e8d7 --- /dev/null +++ b/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/open-window.svg b/icons/open-window.svg new file mode 100644 index 00000000..3849694c --- /dev/null +++ b/icons/open-window.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/patient.svg b/icons/patient.svg new file mode 100644 index 00000000..5872c5f6 --- /dev/null +++ b/icons/patient.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/person-1.svg b/icons/person-1.svg new file mode 100644 index 00000000..10b10fc8 --- /dev/null +++ b/icons/person-1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/person.svg b/icons/person.svg new file mode 100644 index 00000000..878c0674 --- /dev/null +++ b/icons/person.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/plant.svg b/icons/plant.svg new file mode 100644 index 00000000..26d748b8 --- /dev/null +++ b/icons/plant.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/icons/right-arrow.svg b/icons/right-arrow.svg index 6ecce36d..09e4a3a3 100644 --- a/icons/right-arrow.svg +++ b/icons/right-arrow.svg @@ -1,7 +1,3 @@ - - - arrow_icon - - - - \ No newline at end of file + + + diff --git a/icons/shield.svg b/icons/shield.svg new file mode 100644 index 00000000..e9b153e0 --- /dev/null +++ b/icons/shield.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/solid-down-arrow.svg b/icons/solid-down-arrow.svg new file mode 100644 index 00000000..01634ebe --- /dev/null +++ b/icons/solid-down-arrow.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/icons/sustainability.svg b/icons/sustainability.svg new file mode 100644 index 00000000..3bf5179f --- /dev/null +++ b/icons/sustainability.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/icons/tree.svg b/icons/tree.svg new file mode 100644 index 00000000..68f1def7 --- /dev/null +++ b/icons/tree.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/icons/twitter.svg b/icons/twitter.svg new file mode 100644 index 00000000..39481690 --- /dev/null +++ b/icons/twitter.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000..e69de29b diff --git a/scripts/lib-franklin.js b/scripts/aem.js similarity index 69% rename from scripts/lib-franklin.js rename to scripts/aem.js index 54f69727..99f56f54 100644 --- a/scripts/lib-franklin.js +++ b/scripts/aem.js @@ -1,5 +1,5 @@ /* - * Copyright 2022 Adobe. All rights reserved. + * 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 @@ -10,16 +10,21 @@ * governing permissions and limitations under the License. */ +/* eslint-env browser */ + /** * log RUM if part of the sample. * @param {string} checkpoint identifies the checkpoint in funnel * @param {Object} data additional data for RUM sample + * @param {string} data.source DOM node that is the source of a checkpoint event, + * identified by #id or .classname + * @param {string} data.target subject of the checkpoint event, + * for instance the href of a link, or a search term */ -export function sampleRUM(checkpoint, data = {}) { +function sampleRUM(checkpoint, data = {}) { sampleRUM.defer = sampleRUM.defer || []; const defer = (fnname) => { - sampleRUM[fnname] = sampleRUM[fnname] - || ((...args) => sampleRUM.defer.push({ fnname, args })); + sampleRUM[fnname] = sampleRUM[fnname] || ((...args) => sampleRUM.defer.push({ fnname, args })); }; sampleRUM.drain = sampleRUM.drain || ((dfnname, fn) => { @@ -28,32 +33,72 @@ export function sampleRUM(checkpoint, data = {}) { .filter(({ fnname }) => dfnname === fnname) .forEach(({ fnname, args }) => sampleRUM[fnname](...args)); }); - sampleRUM.on = (chkpnt, fn) => { sampleRUM.cases[chkpnt] = fn; }; + sampleRUM.always = sampleRUM.always || []; + sampleRUM.always.on = (chkpnt, fn) => { + sampleRUM.always[chkpnt] = fn; + }; + sampleRUM.on = (chkpnt, fn) => { + sampleRUM.cases[chkpnt] = fn; + }; defer('observe'); defer('cwv'); try { window.hlx = window.hlx || {}; if (!window.hlx.rum) { const usp = new URLSearchParams(window.location.search); - const weight = (usp.get('rum') === 'on') ? 1 : 100; // with parameter, weight is 1. Defaults to 100. - // eslint-disable-next-line no-bitwise - const hashCode = (s) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0); - const id = `${hashCode(window.location.href)}-${new Date().getTime()}-${Math.random().toString(16).substr(2, 14)}`; + const weight = usp.get('rum') === 'on' ? 1 : 100; // with parameter, weight is 1. Defaults to 100. + const id = Array.from({ length: 75 }, (_, i) => String.fromCharCode(48 + i)) + .filter((a) => /\d|[A-Z]/i.test(a)) + .filter(() => Math.random() * 75 > 70) + .join(''); const random = Math.random(); - const isSelected = (random * weight < 1); + const isSelected = random * weight < 1; + const firstReadTime = Date.now(); const urlSanitizers = { full: () => window.location.href, origin: () => window.location.origin, path: () => window.location.href.replace(/\?.*$/, ''), }; // eslint-disable-next-line object-curly-newline, max-len - window.hlx.rum = { weight, id, random, isSelected, sampleRUM, sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'] }; + window.hlx.rum = { + weight, + id, + random, + isSelected, + firstReadTime, + sampleRUM, + sanitizeURL: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'], + }; } - const { weight, id } = window.hlx.rum; + const { weight, id, firstReadTime } = window.hlx.rum; if (window.hlx && window.hlx.rum && window.hlx.rum.isSelected) { + const knownProperties = [ + 'weight', + 'id', + 'referer', + 'checkpoint', + 't', + 'source', + 'target', + 'cwv', + 'CLS', + 'FID', + 'LCP', + 'INP', + ]; const sendPing = (pdata = data) => { // eslint-disable-next-line object-curly-newline, max-len, no-use-before-define - const body = JSON.stringify({ weight, id, referer: window.hlx.rum.sanitizeURL(), checkpoint, ...data }); + const body = JSON.stringify( + { + weight, + id, + referer: window.hlx.rum.sanitizeURL(), + checkpoint, + t: Date.now() - firstReadTime, + ...data, + }, + knownProperties, + ); const url = `https://rum.hlx.page/.rum/${weight}`; // eslint-disable-next-line no-unused-expressions navigator.sendBeacon(url, body); @@ -71,18 +116,130 @@ export function sampleRUM(checkpoint, data = {}) { }, }; sendPing(data); - if (sampleRUM.cases[checkpoint]) { sampleRUM.cases[checkpoint](); } + if (sampleRUM.cases[checkpoint]) { + sampleRUM.cases[checkpoint](); + } + } + if (sampleRUM.always[checkpoint]) { + sampleRUM.always[checkpoint](data); } } catch (error) { // something went wrong } } +/** + * Setup block utils. + */ +function setup() { + window.hlx = window.hlx || {}; + window.hlx.RUM_MASK_URL = 'full'; + window.hlx.codeBasePath = ''; + window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') === 'on'; + + const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]'); + if (scriptEl) { + try { + [window.hlx.codeBasePath] = new URL(scriptEl.src).pathname.split('/scripts/scripts.js'); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } + } +} + +/** + * Auto initializiation. + */ + +function init() { + setup(); + sampleRUM('top'); + + window.addEventListener('load', () => sampleRUM('load')); + + window.addEventListener('unhandledrejection', (event) => { + sampleRUM('error', { source: event.reason.sourceURL, target: event.reason.line }); + }); + + window.addEventListener('error', (event) => { + sampleRUM('error', { source: event.filename, target: event.lineno }); + }); +} + +/** + * Sanitizes a string for use as class name. + * @param {string} name The unsanitized string + * @returns {string} The class name + */ +function toClassName(name) { + return typeof name === 'string' + ? name + .toLowerCase() + .replace(/[^0-9a-z]/gi, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + : ''; +} + +/** + * Sanitizes a string for use as a js property name. + * @param {string} name The unsanitized string + * @returns {string} The camelCased name + */ +function toCamelCase(name) { + return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +} + +/** + * Extracts the config from a block. + * @param {Element} block The block element + * @returns {object} The block config + */ +// eslint-disable-next-line import/prefer-default-export +function readBlockConfig(block) { + const config = {}; + block.querySelectorAll(':scope > div').forEach((row) => { + if (row.children) { + const cols = [...row.children]; + if (cols[1]) { + const col = cols[1]; + const name = toClassName(cols[0].textContent); + let value = ''; + if (col.querySelector('a')) { + const as = [...col.querySelectorAll('a')]; + if (as.length === 1) { + value = as[0].href; + } else { + value = as.map((a) => a.href); + } + } else if (col.querySelector('img')) { + const imgs = [...col.querySelectorAll('img')]; + if (imgs.length === 1) { + value = imgs[0].src; + } else { + value = imgs.map((img) => img.src); + } + } else if (col.querySelector('p')) { + const ps = [...col.querySelectorAll('p')]; + if (ps.length === 1) { + value = ps[0].textContent; + } else { + value = ps.map((p) => p.textContent); + } + } else value = row.children[1].textContent; + config[name] = value; + } + } + }); + return config; +} + /** * Loads a CSS file. * @param {string} href URL to the CSS file */ -export async function loadCSS(href) { +async function loadCSS(href) { return new Promise((resolve, reject) => { if (!document.querySelector(`head > link[href="${href}"]`)) { const link = document.createElement('link'); @@ -102,14 +259,13 @@ export async function loadCSS(href) { * @param {string} src URL to the JS file * @param {Object} attrs additional optional attributes */ - -export async function loadScript(src, attrs) { +async function loadScript(src, attrs) { return new Promise((resolve, reject) => { if (!document.querySelector(`head > script[src="${src}"]`)) { const script = document.createElement('script'); script.src = src; if (attrs) { - // eslint-disable-next-line no-restricted-syntax, guard-for-in + // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const attr in attrs) { script.setAttribute(attr, attrs[attr]); } @@ -126,198 +282,152 @@ export async function loadScript(src, attrs) { /** * Retrieves the content of metadata tags. * @param {string} name The metadata name (or property) + * @param {Document} doc Document object to query for metadata. Defaults to the window's document * @returns {string} The metadata value(s) */ -export function getMetadata(name) { +function getMetadata(name, doc = document) { const attr = name && name.includes(':') ? 'property' : 'name'; - const meta = [...document.head.querySelectorAll(`meta[${attr}="${name}"]`)].map((m) => m.content).join(', '); + const meta = [...doc.head.querySelectorAll(`meta[${attr}="${name}"]`)] + .map((m) => m.content) + .join(', '); return meta || ''; } /** - * Sanitizes a string for use as class name. - * @param {string} name The unsanitized string - * @returns {string} The class name + * Returns a picture element with webp and fallbacks + * @param {string} src The image URL + * @param {string} [alt] The image alternative text + * @param {boolean} [eager] Set loading attribute to eager + * @param {Array} [breakpoints] Breakpoints and corresponding params (eg. width) + * @returns {Element} The picture element */ -export function toClassName(name) { - return typeof name === 'string' - ? name.toLowerCase().replace(/[^0-9a-z]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') - : ''; +function createOptimizedPicture( + src, + alt = '', + eager = false, + breakpoints = [{ media: '(min-width: 600px)', width: '2000' }, { width: '750' }], +) { + const url = new URL(src, window.location.href); + const picture = document.createElement('picture'); + const { pathname } = url; + const ext = pathname.substring(pathname.lastIndexOf('.') + 1); + + // webp + breakpoints.forEach((br) => { + const source = document.createElement('source'); + if (br.media) source.setAttribute('media', br.media); + source.setAttribute('type', 'image/webp'); + source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`); + picture.appendChild(source); + }); + + // fallback + breakpoints.forEach((br, i) => { + if (i < breakpoints.length - 1) { + const source = document.createElement('source'); + if (br.media) source.setAttribute('media', br.media); + source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); + picture.appendChild(source); + } else { + const img = document.createElement('img'); + img.setAttribute('loading', eager ? 'eager' : 'lazy'); + img.setAttribute('alt', alt); + picture.appendChild(img); + img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); + } + }); + + return picture; } /** - * Sanitizes a string for use as a js property name. - * @param {string} name The unsanitized string - * @returns {string} The camelCased name + * Set template (page structure) and theme (page styles). */ -export function toCamelCase(name) { - return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +function decorateTemplateAndTheme() { + const addClasses = (element, classes) => { + classes.split(',').forEach((c) => { + element.classList.add(toClassName(c.trim())); + }); + }; + const template = getMetadata('template'); + if (template) addClasses(document.body, template); + const theme = getMetadata('theme'); + if (theme) addClasses(document.body, theme); } -const ICONS_CACHE = {}; /** - * Replace icons with inline SVG and prefix with codeBasePath. - * @param {Element} [element] Element containing icons + * Decorates paragraphs containing a single link as buttons. + * @param {Element} element container element */ -export async function decorateIcons(element) { - // Prepare the inline sprite - let svgSprite = document.getElementById('franklin-svg-sprite'); - if (!svgSprite) { - const div = document.createElement('div'); - div.innerHTML = ''; - svgSprite = div.firstElementChild; - document.body.append(div.firstElementChild); - } - - // Download all new icons - const icons = [...element.querySelectorAll('span.icon')]; - await Promise.all(icons.map(async (span) => { - const iconName = Array.from(span.classList).find((c) => c.startsWith('icon-')).substring(5); - if (!ICONS_CACHE[iconName]) { - ICONS_CACHE[iconName] = true; - try { - const response = await fetch(`${window.hlx.codeBasePath}/icons/${iconName}.svg`); - if (!response.ok) { - ICONS_CACHE[iconName] = false; - return; +function decorateButtons(element) { + element.querySelectorAll('a').forEach((a) => { + a.title = a.title || a.textContent; + if (a.href !== a.textContent) { + const up = a.parentElement; + const twoup = a.parentElement.parentElement; + if (!a.querySelector('img')) { + if (up.childNodes.length === 1 && (up.tagName === 'P' || up.tagName === 'DIV')) { + a.className = 'button'; // default + up.classList.add('button-container'); } - // Styled icons don't play nice with the sprite approach because of shadow dom isolation - const svg = await response.text(); - if (svg.match(/( for icon, prefixed with codeBasePath and optional prefix. + * @param {Element} [span] span element with icon classes + * @param {string} [prefix] prefix to be added to icon src + * @param {string} [alt] alt text to be added to icon */ -export function decorateBlock(block) { - const shortBlockName = block.classList[0]; - if (shortBlockName) { - block.classList.add('block'); - block.dataset.blockName = shortBlockName; - block.dataset.blockStatus = 'initialized'; - const blockWrapper = block.parentElement; - blockWrapper.classList.add(`${shortBlockName}-wrapper`); - const section = block.closest('.section'); - if (section) section.classList.add(`${shortBlockName}-container`); - } +function decorateIcon(span, prefix = '', alt = '') { + const iconName = Array.from(span.classList) + .find((c) => c.startsWith('icon-')) + .substring(5); + const img = document.createElement('img'); + img.dataset.iconName = iconName; + img.src = `${window.hlx.codeBasePath}${prefix}/icons/${iconName}.svg`; + img.alt = alt; + img.loading = 'lazy'; + span.append(img); } /** - * Extracts the config from a block. - * @param {Element} block The block element - * @returns {object} The block config + * Add for icons, prefixed with codeBasePath and optional prefix. + * @param {Element} [element] Element containing icons + * @param {string} [prefix] prefix to be added to icon the src */ -export function readBlockConfig(block) { - const config = {}; - block.querySelectorAll(':scope > div').forEach((row) => { - if (row.children) { - const cols = [...row.children]; - if (cols[1]) { - const col = cols[1]; - const name = toClassName(cols[0].textContent); - let value = ''; - if (col.querySelector('a')) { - const as = [...col.querySelectorAll('a')]; - if (as.length === 1) { - value = as[0].href; - } else { - value = as.map((a) => a.href); - } - } else if (col.querySelector('img')) { - const imgs = [...col.querySelectorAll('img')]; - if (imgs.length === 1) { - value = imgs[0].src; - } else { - value = imgs.map((img) => img.src); - } - } else if (col.querySelector('p')) { - const ps = [...col.querySelectorAll('p')]; - if (ps.length === 1) { - value = ps[0].textContent; - } else { - value = ps.map((p) => p.textContent); - } - } else value = row.children[1].textContent; - config[name] = value; - } - } +function decorateIcons(element, prefix = '') { + const icons = [...element.querySelectorAll('span.icon')]; + icons.forEach((span) => { + decorateIcon(span, prefix); }); - return config; } /** * Decorates all sections in a container element. * @param {Element} main The container element */ -export function decorateSections(main) { +function decorateSections(main) { main.querySelectorAll(':scope > div').forEach((section) => { const wrappers = []; let defaultContent = false; @@ -335,14 +445,17 @@ export function decorateSections(main) { section.dataset.sectionStatus = 'initialized'; section.style.display = 'none'; - /* process section metadata */ + // Process section metadata const sectionMeta = section.querySelector('div.section-metadata'); if (sectionMeta) { const meta = readBlockConfig(sectionMeta); Object.keys(meta).forEach((key) => { if (key === 'style') { - const styles = meta.style.split(',').map((style) => toClassName(style.trim())); - section.classList.add(...styles.filter((style) => style)); + const styles = meta.style + .split(',') + .filter((style) => style) + .map((style) => toClassName(style.trim())); + styles.forEach((style) => section.classList.add(style)); } else { section.dataset[toCamelCase(key)] = meta[key]; } @@ -352,17 +465,56 @@ export function decorateSections(main) { }); } +/** + * Gets placeholders object. + * @param {string} [prefix] Location of placeholders + * @returns {object} Window placeholders object + */ +// eslint-disable-next-line import/prefer-default-export +async function fetchPlaceholders(prefix = 'default') { + window.placeholders = window.placeholders || {}; + if (!window.placeholders[prefix]) { + window.placeholders[prefix] = new Promise((resolve) => { + fetch(`${prefix === 'default' ? '' : prefix}/placeholders.json`) + .then((resp) => { + if (resp.ok) { + return resp.json(); + } + return {}; + }) + .then((json) => { + const placeholders = {}; + json.data + .filter((placeholder) => placeholder.Key) + .forEach((placeholder) => { + placeholders[toCamelCase(placeholder.Key)] = placeholder.Text; + }); + window.placeholders[prefix] = placeholders; + resolve(window.placeholders[prefix]); + }) + .catch(() => { + // error loading placeholders + window.placeholders[prefix] = {}; + resolve(window.placeholders[prefix]); + }); + }); + } + return window.placeholders[`${prefix}`]; +} + /** * Updates all section status in a container element. * @param {Element} main The container element */ -export function updateSectionsStatus(main) { +function updateSectionsStatus(main) { const sections = [...main.querySelectorAll(':scope > div.section')]; for (let i = 0; i < sections.length; i += 1) { const section = sections[i]; const status = section.dataset.sectionStatus; if (status !== 'loaded') { - const loadingBlock = section.querySelector('.block[data-block-status="initialized"], .block[data-block-status="loading"]'); + const loadingBlock = section.querySelector( + '.block[data-block-status="initialized"], .block[data-block-status="loading"]', + ); if (loadingBlock) { section.dataset.sectionStatus = 'loading'; break; @@ -374,22 +526,12 @@ export function updateSectionsStatus(main) { } } -/** - * Decorates all blocks in a container element. - * @param {Element} main The container element - */ -export function decorateBlocks(main) { - main - .querySelectorAll('div.section > div > div') - .forEach(decorateBlock); -} - /** * Builds a block DOM Element from a two dimensional array, string, or object * @param {string} blockName name of the block * @param {*} content two dimensional array or string or object of content */ -export function buildBlock(blockName, content) { +function buildBlock(blockName, content) { const table = Array.isArray(content) ? content : [[content]]; const blockEl = document.createElement('div'); // build image block nested div structure @@ -412,14 +554,14 @@ export function buildBlock(blockName, content) { }); blockEl.appendChild(rowEl); }); - return (blockEl); + return blockEl; } /** * Loads JS and CSS for a block. * @param {Element} block The block element */ -export async function loadBlock(block) { +async function loadBlock(block) { const status = block.dataset.blockStatus; if (status !== 'loading' && status !== 'loaded') { block.dataset.blockStatus = 'loading'; @@ -429,7 +571,9 @@ export async function loadBlock(block) { const decorationComplete = new Promise((resolve) => { (async () => { try { - const mod = await import(`../blocks/${blockName}/${blockName}.js`); + const mod = await import( + `${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.js` + ); if (mod.default) { await mod.default(block); } @@ -447,13 +591,14 @@ export async function loadBlock(block) { } block.dataset.blockStatus = 'loaded'; } + return block; } /** * Loads JS and CSS for all blocks in a container element. * @param {Element} main The container element */ -export async function loadBlocks(main) { +async function loadBlocks(main) { updateSectionsStatus(main); const blocks = [...main.querySelectorAll('div.block')]; for (let i = 0; i < blocks.length; i += 1) { @@ -464,139 +609,28 @@ export async function loadBlocks(main) { } /** - * Returns a picture element with webp and fallbacks - * @param {string} src The image URL - * @param {string} [alt] The image alternative text - * @param {boolean} [eager] Set loading attribute to eager - * @param {Array} [breakpoints] Breakpoints and corresponding params (eg. width) - * @returns {Element} The picture element - */ -export function createOptimizedPicture(src, alt = '', eager = false, breakpoints = [{ media: '(min-width: 600px)', width: '2000' }, { width: '750' }]) { - const url = new URL(src, window.location.href); - const picture = document.createElement('picture'); - const { pathname } = url; - const ext = pathname.substring(pathname.lastIndexOf('.') + 1); - - // webp - breakpoints.forEach((br) => { - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('type', 'image/webp'); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`); - picture.appendChild(source); - }); - - // fallback - breakpoints.forEach((br, i) => { - if (i < breakpoints.length - 1) { - const source = document.createElement('source'); - if (br.media) source.setAttribute('media', br.media); - source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - picture.appendChild(source); - } else { - const img = document.createElement('img'); - img.setAttribute('loading', eager ? 'eager' : 'lazy'); - img.setAttribute('alt', alt); - picture.appendChild(img); - img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`); - } - }); - - return picture; -} - -/** - * Normalizes all headings within a container element. - * @param {Element} el The container element - * @param {string} allowedHeadings The list of allowed headings (h1 ... h6) - */ -export function normalizeHeadings(el, allowedHeadings) { - const allowed = allowedHeadings.map((h) => h.toLowerCase()); - el.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((tag) => { - const h = tag.tagName.toLowerCase(); - if (allowed.indexOf(h) === -1) { - // current heading is not in the allowed list -> try first to "promote" the heading - let level = parseInt(h.charAt(1), 10) - 1; - while (allowed.indexOf(`h${level}`) === -1 && level > 0) { - level -= 1; - } - if (level === 0) { - // did not find a match -> try to "downgrade" the heading - while (allowed.indexOf(`h${level}`) === -1 && level < 7) { - level += 1; - } - } - if (level !== 7) { - tag.outerHTML = `${tag.textContent}`; - } - } - }); -} - -/** - * Set template (page structure) and theme (page styles). + * Decorates a block. + * @param {Element} block The block element */ -export function decorateTemplateAndTheme() { - const addClasses = (element, classes) => { - classes.split(',').forEach((c) => { - element.classList.add(toClassName(c.trim())); - }); - }; - const template = getMetadata('template'); - if (template) addClasses(document.body, template); - const theme = getMetadata('theme'); - if (theme) addClasses(document.body, theme); +function decorateBlock(block) { + const shortBlockName = block.classList[0]; + if (shortBlockName) { + block.classList.add('block'); + block.dataset.blockName = shortBlockName; + block.dataset.blockStatus = 'initialized'; + const blockWrapper = block.parentElement; + blockWrapper.classList.add(`${shortBlockName}-wrapper`); + const section = block.closest('.section'); + if (section) section.classList.add(`${shortBlockName}-container`); + } } /** - * Decorates paragraphs containing a single link as buttons. - * @param {Element} element container element + * Decorates all blocks in a container element. + * @param {Element} main The container element */ -export function decorateButtons(element) { - element.querySelectorAll('a').forEach((a) => { - a.title = a.title || a.textContent; - if (a.href !== a.textContent) { - const up = a.parentElement; - const twoup = a.parentElement.parentElement; - if (!a.querySelector('img')) { - if (up.childNodes.length === 1 && (up.tagName === 'P' || up.tagName === 'DIV')) { - a.className = 'button primary'; // default - up.classList.add('button-container'); - } - if (up.childNodes.length === 1 && up.tagName === 'STRONG' - && twoup.childNodes.length === 1 && twoup.tagName === 'P') { - a.className = 'button primary'; - twoup.classList.add('button-container'); - } - if (up.childNodes.length === 1 && up.tagName === 'EM' - && twoup.childNodes.length === 1 && twoup.tagName === 'P') { - a.className = 'button secondary'; - twoup.classList.add('button-container'); - } - } - } - }); -} - -/** - * Load LCP block and/or wait for LCP in default content. - */ -export async function waitForLCP(lcpBlocks) { - const block = document.querySelector('.block'); - const hasLCPBlock = (block && lcpBlocks.includes(block.dataset.blockName)); - if (hasLCPBlock) await loadBlock(block); - - document.body.style.display = null; - const lcpCandidate = document.querySelector('main img'); - await new Promise((resolve) => { - if (lcpCandidate && !lcpCandidate.complete) { - lcpCandidate.setAttribute('loading', 'eager'); - lcpCandidate.addEventListener('load', resolve); - lcpCandidate.addEventListener('error', resolve); - } else { - resolve(); - } - }); +function decorateBlocks(main) { + main.querySelectorAll('div.section > div > div').forEach(decorateBlock); } /** @@ -604,7 +638,7 @@ export async function waitForLCP(lcpBlocks) { * @param {Element} header header element * @returns {Promise} */ -export function loadHeader(header) { +async function loadHeader(header) { const headerBlock = buildBlock('header', ''); header.append(headerBlock); decorateBlock(headerBlock); @@ -616,7 +650,7 @@ export function loadHeader(header) { * @param footer footer element * @returns {Promise} */ -export function loadFooter(footer) { +async function loadFooter(footer) { const footerBlock = buildBlock('footer', ''); footer.append(footerBlock); decorateBlock(footerBlock); @@ -624,41 +658,52 @@ export function loadFooter(footer) { } /** - * Setup block utils. - */ -export function setup() { - window.hlx = window.hlx || {}; - window.hlx.RUM_MASK_URL = 'full'; - window.hlx.codeBasePath = ''; - window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') === 'on'; - - const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]'); - if (scriptEl) { - try { - [window.hlx.codeBasePath] = new URL(scriptEl.src).pathname.split('/scripts/scripts.js'); - } catch (error) { - // eslint-disable-next-line no-console - console.log(error); - } - } -} - -/** - * Auto initializiation. + * Load LCP block and/or wait for LCP in default content. + * @param {Array} lcpBlocks Array of blocks */ -function init() { - setup(); - sampleRUM('top'); - - window.addEventListener('load', () => sampleRUM('load')); +async function waitForLCP(lcpBlocks) { + const block = document.querySelector('.block'); + const hasLCPBlock = block && lcpBlocks.includes(block.dataset.blockName); + if (hasLCPBlock) await loadBlock(block); - window.addEventListener('unhandledrejection', (event) => { - sampleRUM('error', { source: event.reason.sourceURL, target: event.reason.line }); - }); + document.body.style.display = null; + const lcpCandidate = document.querySelector('main img'); - window.addEventListener('error', (event) => { - sampleRUM('error', { source: event.filename, target: event.lineno }); + await new Promise((resolve) => { + if (lcpCandidate && !lcpCandidate.complete) { + lcpCandidate.setAttribute('loading', 'eager'); + lcpCandidate.addEventListener('load', resolve); + lcpCandidate.addEventListener('error', resolve); + } else { + resolve(); + } }); } init(); + +export { + buildBlock, + createOptimizedPicture, + decorateBlock, + decorateBlocks, + decorateButtons, + decorateIcons, + decorateSections, + decorateTemplateAndTheme, + fetchPlaceholders, + getMetadata, + loadBlock, + loadBlocks, + loadCSS, + loadFooter, + loadHeader, + loadScript, + readBlockConfig, + sampleRUM, + setup, + toCamelCase, + toClassName, + updateSectionsStatus, + waitForLCP, +}; diff --git a/scripts/delayed.js b/scripts/delayed.js index 920b4ad8..a5cc99bd 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -1,7 +1,13 @@ // eslint-disable-next-line import/no-cycle -import { sampleRUM } from './lib-franklin.js'; +import { sampleRUM, loadScript } from './aem.js'; // Core Web Vitals RUM collection sampleRUM('cwv'); // add more delayed functionality here + +const loadAdobeDTM = async () => { + // await loadScript('https://assets.adobedtm.com/8fee56b0a165/17899e6850f3/launch-1c377528b01b-development.min.js'); +}; + +await loadAdobeDTM(); diff --git a/scripts/scripts.js b/scripts/scripts.js index 4381f87d..9ce419e5 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -12,7 +12,7 @@ import { buildBlock, loadCSS, readBlockConfig, -} from './lib-franklin.js'; +} from './aem.js'; import { integrateMartech, @@ -24,12 +24,12 @@ export const BREAKPOINTS = { large: window.matchMedia('(min-width: 1200px)'), }; +const PRODUCTION_DOMAINS = ['www.takeda.com']; const LCP_BLOCKS = ['hero']; // add your LCP blocks to the list function decorateSectionGradientTopper(main) { const section = main.querySelector('.section.inverted-gradient-background'); const hasInvertedGradient = section !== null; - if (!hasInvertedGradient) return; const hero = main.querySelector('& > .section.hero-container'); @@ -52,6 +52,9 @@ async function decorateDisclaimerModal() { const modal = tmp.querySelector('.disclaimer-modal'); const config = readBlockConfig(modal); modal.innerHTML = ` +
+ +

${config.title}

${config.content}

@@ -60,7 +63,7 @@ async function decorateDisclaimerModal() {
`; const disclaimerContainer = document.createElement('div'); - disclaimerContainer.className = 'disclaimer-modal-container'; + disclaimerContainer.className = 'section disclaimer-modal-container'; const disclaimerWrapper = document.createElement('div'); disclaimerWrapper.className = 'disclaimer-modal-wrapper'; disclaimerWrapper.appendChild(modal); @@ -75,6 +78,9 @@ async function decorateDisclaimerModal() { disclaimerContainer.remove(); }); main.append(disclaimerContainer); + modal.querySelector('.close').addEventListener('click', () => { + document.querySelector('.disclaimer-modal-container').style.display = 'none'; + }); } } } @@ -134,7 +140,7 @@ function buildSectionBackgroundImage(main) { if (bgIdx >= 0) { const picture = metadata.children[bgIdx].children[1]; picture.querySelector('picture').classList.add('section-bg-image'); - metadata.parentElement.append(picture.cloneNode(true)); + metadata.parentElement.prepend(picture.cloneNode(true)); } }); } @@ -164,6 +170,68 @@ function fixDefaultImage(main) { }); } +export function checkDomain(url) { + const urlToCheck = typeof url === 'string' ? new URL(url) : url; + + const isProd = PRODUCTION_DOMAINS.some((host) => urlToCheck.hostname.includes(host)); + const isHlx = ['hlx.page', 'hlx.live', 'aem.page', 'aem.live'].some((host) => urlToCheck.hostname.includes(host)); + const isLocal = urlToCheck.hostname.includes('localhost'); + const isPreview = isLocal || urlToCheck.hostname.includes('hlx.page'); + const isKnown = isProd || isHlx || isLocal; + const isExternal = !isKnown && !urlToCheck.hostname.includes('.takeda.com'); + return { + isProd, + isHlx, + isLocal, + isKnown, + isExternal, + isPreview, + }; +} + +/** + * Returns the true origin of the current page in the browser. + * If the page is running in a iframe with srcdoc, the ancestor origin is returned. + * @returns {String} The true origin + */ +export function getOrigin() { + return window.location.href === 'about:srcdoc' ? window.parent.location.origin : window.location.origin; +} + +let browserDomainCheck; +export function checkBrowserDomain() { + if (!browserDomainCheck) { + browserDomainCheck = checkDomain(window.location); + } + return browserDomainCheck; +} + +export function rewriteLinkUrl(a) { + const url = new URL(a.href); + const domainCheck = checkDomain(url); + // protect against maito: links or other weirdness + const isHttp = url.protocol === 'https:' || url.protocol === 'http:'; + + if (isHttp && domainCheck.isKnown) { + if (url.pathname.startsWith('/content/dam/')) { + a.target = '_blank'; + if (checkBrowserDomain().isProd) { + a.href = `${url.pathname}${url.search}${url.hash}`; + } + } else { + // local links are rewritten to be relative + a.href = `${url.pathname.replace('.html', '')}${url.search}${url.hash}`; + } + } else if (isHttp && domainCheck.isExternal) { + // non local open in a new tab + // but if a different takeda.com sub-domain, leave absolute, don't open in a new tab + a.target = '_blank'; + a.rel = 'noopener noreferrer'; + } + + return a; +} + /** * Builds layout containers after all sections & blocks have been decorated. * @param {HTMLElement} main @@ -176,7 +244,6 @@ export function buildLayoutContainers(main) { container.append(...section.children); if (title) section.prepend(title); section.append(container); - section.querySelectorAll('.separator-wrapper').forEach((sep) => { sep.innerHTML = '
'; }); @@ -194,6 +261,21 @@ function decorateSectionBackgroundImage(main) { wrapper.parentElement.replaceWith(wrapper); }); } +function decorateSectionButtonRow(main) { + main.querySelectorAll(':scope div > .default-content-wrapper > p.button-container').forEach((buttonContainer) => { + const wrapper = buttonContainer.parentElement; + wrapper.classList.add('button-wrapper'); + }); +} + +function decorateSectionIDs(main) { + main.querySelectorAll(':scope .section').forEach((section) => { + const id = section.getAttribute('data-id'); + if (id) { + section.id = id.toLowerCase().replaceAll(' ', '-'); + } + }); +} /** * Decorates the main element. @@ -209,8 +291,10 @@ export function decorateMain(main) { fixDefaultImage(main); decorateBlocks(main); buildLayoutContainers(main); + decorateSectionButtonRow(main); decorateSectionBackgroundImage(main); decorateSectionGradientTopper(main); + decorateSectionIDs(main); } /** diff --git a/scripts/third-party.js b/scripts/third-party.js index fe3c838c..1b21dd65 100644 --- a/scripts/third-party.js +++ b/scripts/third-party.js @@ -14,6 +14,6 @@ function createInlineScript(innerHTML, parent) { // eslint-disable-next-line import/prefer-default-export export function integrateMartech() { if (window.location.href.match(/^https?:\/\/(localhost|127.0.0.1)/) === null) { - createInlineScript(GTM_SCRIPT, document.body); + createInlineScript(document.body); } } diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 00000000..7adee000 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,17 @@ +export function createElemWithClass(type, ...classNames) { + const elem = document.createElement(type); + elem.classList.add(...classNames); + return elem; +} + +export function setAttributes(element, attributes) { + Object.keys(attributes).forEach((attributeKey) => { + element.setAttribute(attributeKey, attributes[attributeKey]); + }); +} + +export function createElementWithAttributes(tag, attributes) { + const element = document.createElement(tag); + setAttributes(element, attributes); + return element; +} diff --git a/sitemap-en.xml b/sitemap-en.xml new file mode 100644 index 00000000..e69de29b diff --git a/styles/fonts/GothamSSm-Bold_Web.woff2 b/styles/fonts/GothamSSm-Bold_Web.woff2 new file mode 100644 index 00000000..4664fdc6 Binary files /dev/null and b/styles/fonts/GothamSSm-Bold_Web.woff2 differ diff --git a/styles/fonts/GothamSSm-Book_Web.woff2 b/styles/fonts/GothamSSm-Book_Web.woff2 new file mode 100644 index 00000000..1d9ade9e Binary files /dev/null and b/styles/fonts/GothamSSm-Book_Web.woff2 differ diff --git a/styles/fonts/GothamSSm-Light_Web.woff2 b/styles/fonts/GothamSSm-Light_Web.woff2 new file mode 100644 index 00000000..c128636c Binary files /dev/null and b/styles/fonts/GothamSSm-Light_Web.woff2 differ diff --git a/styles/fonts/GothamSSm-Medium_Web.woff2 b/styles/fonts/GothamSSm-Medium_Web.woff2 new file mode 100644 index 00000000..a06e4376 Binary files /dev/null and b/styles/fonts/GothamSSm-Medium_Web.woff2 differ diff --git a/styles/lazy-styles.css b/styles/lazy-styles.css index f82796c6..5da9a0d3 100644 --- a/styles/lazy-styles.css +++ b/styles/lazy-styles.css @@ -1,4 +1,29 @@ /* below the fold CSS goes here */ -@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600;1,700&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap'); + +@font-face { + font-family: Gotham; + font-style: normal; + font-weight: 300; + src: url(fonts/GothamSSm-Light_Web.woff2) format("woff2") +} + +@font-face { + font-family: Gotham; + font-style: normal; + font-weight: 400; + src: url(fonts/GothamSSm-Book_Web.woff2) format("woff2") +} + +@font-face { + font-family: Gotham; + font-style: normal; + font-weight: 500; + src: url(fonts/GothamSSm-Medium_Web.woff2) format("woff2") +} + +@font-face { + font-family: Gotham; + font-style: normal; + font-weight: 700; + src: url(fonts/GothamSSm-Bold_Web.woff2) format("woff2") +} diff --git a/styles/styles.css b/styles/styles.css index c0060efc..9415ba5a 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -12,60 +12,47 @@ @import url('reset.css'); +/* fallback font for Gotham (normal - 400) */ @font-face { - font-family: 'Open Sans Fallback'; + font-family: "Gothum Fallback"; font-style: normal; font-weight: 400; - src: local('Arial'); - size-adjust: 105.43%; - ascent-override: 105%; -} - -@font-face { - font-family: 'Open Sans Fallback'; - font-style: italic; - font-weight: 400; - src: local('Arial Italic'); - size-adjust: 105.43%; - ascent-override: 105%; -} - -@font-face { - font-family: 'Open Sans Fallback'; - font-style: normal; - font-weight: 600; - src: local('Arial Bold'); - size-adjust: 105.43%; - ascent-override: 105%; -} - -@font-face { - font-family: 'Open Sans Fallback'; - font-style: normal; - font-weight: 700; - src: local('Arial Bold'); - size-adjust: 105.43%; - ascent-override: 105%; + size-adjust: 117.99%; + src: local("Arial"); } :root { - /* Colors */ --bright-gray: #efefef; --medium-gray: #ddd; --gray: #979797; + --brand-border: #cdd1d6; + --gray-neutral-70: #333; + --brand--secondary-subtle: #edf2f4; + --gray-neutral-80: #333; + --gray-neutral-90: #1e1e23; --gray-box-shadow: rgb(0 0 0 / 20%); + --gray-gradient-light: #edeff0; + --gray-gradient-dark: #9da8af; + + /* general colors */ --silver: #d8d8d8; --quartz: #4c4948; - --red: #e10; + --red: #e1242a; + --dark-red: #891515; + --brand-primary: #bd120a; + /* for button and link hover states #891515 */ + --brand-primary-strong: #891515; + --brand-primary-strongest: #450a0a; + --brand-critical: #eb0000; + --brand-critical-strong: #c10000; --white: #fff; --blue: #003087; --black-olive: #3a3a3a; --black: black; - --cadet: #5B6770; /* gray-blue */ - --gray-gradient-light: #edeff0; - --gray-gradient-dark: #9da8af; /* Gradient breakpoint is defined below */ + --cadet: #5B6770; /* Brand Colors */ + --takeda-red-50: #e1242a; --gammagard-liq: #003087; --gammagard-sd: #35a4d8; --hyqvia: #500778; @@ -78,12 +65,12 @@ --hello-programs: #808080; /* Font Colors */ - --default-text-color: var(--black-olive); + --default-text-color: var(--gray-neutral-80); /* Fonts */ - --font-family-opensans: 'Open Sans', 'Open Sans Fallback', 'Arial', sans-serif; - --body-font-family: var(--font-family-opensans); - --heading-font-family: var(--font-family-opensans); + --font-family-gotham: 'Gotham', 'Gotham Fallback', 'Arial', sans-serif; + --body-font-family: var(--font-family-gotham); + --heading-font-family: var(--font-family-gotham); /* Font Weights */ --font-weight-bold: 700; @@ -99,22 +86,30 @@ --body-font-size-m: 16px; --body-font-size-l: 18px; --body-font-size-xl: 20px; - --body-font-size-xxl: 24px; + --body-font-size-xxl: 22px; /* Heading */ --heading-font-size-xxs: 18px; --heading-font-size-xs: 20px; --heading-font-size-s: 24px; - --heading-font-size-m: 26px; - --heading-font-size-l: 30px; - --heading-font-size-xl: 42px; + --heading-font-size-m: 30px; + --heading-font-size-l: 36px; + --heading-font-size-xl: 40px; + --heading-font-size-xxl: 54px; + + /* hover state colors buttons links */ + + --hover-state-default: var(--brand-primary); + --hover--state-pressed: var(--brand-primary-strongest); + + /* Nav Height */ - --nav-height: 100px; + --nav-height: 65px; /* Line heights */ - --line-height-xs: 1.15em; - --line-height-s: 1.25em; + --line-height-xs: 1.1em; + --line-height-s: 1.5em; --line-height-m: 1.5em; --line-height-l: 1.75em; --line-height-xl: 2em; @@ -122,7 +117,7 @@ --line-height-xxxl: 2.9em; /* Page Widths */ - --normal-page-width: 1200px; + --normal-page-width: 1158px; --wide-page-width: 1600px; --full-page-width: 100vw; @@ -133,7 +128,9 @@ --gray-gradient-angle: -180deg; } -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; } @@ -156,22 +153,23 @@ sub { line-height: var(--line-height-xs); } -h1, h2, h3 { +h1, +h2, +h3 { font-family: var(--heading-font-family); font-weight: var(--font-weight-bold); - color: var(--black); + color: var(--gray-neutral-80); } h1 { - margin-bottom: 8px; font-size: var(--heading-font-size-m); - line-height: var(--line-height-s); + line-height: var(--line-height-xs); } h2 { margin-bottom: 12px; font-size: var(--heading-font-size-s); - line-height: var(--line-height-s); + line-height: var(--line-height-xs); } h2 sub { @@ -187,30 +185,38 @@ h3 { line-height: var(--line-height-m); } -p, li { +p, +li { font-family: var(--body-font-family); font-weight: var(--font-weight-normal); font-size: var(--body-font-size-m); - line-height: var(--line-height-s); + line-height: var(--line-height-m); color: var(--default-text-color); - text-align: inherit; + gap:40px; + } -ul, li { +ul, +li { font-family: var(--body-font-family); } p { - margin-bottom: 1.5em; + margin-bottom: 1.2em; } p.reference { font-size: var(--body-font-size-xxs); } -a, a:visited{ +a, +a:visited { + font-family: var(--body-font-family); + color: var(--brand-primary); +} +a:hover { font-family: var(--body-font-family); - color: var(--blue); + color: var(--brand-primary-strong); } span { @@ -221,22 +227,33 @@ span { color: var(--default-text-color); } +main button, p.button-container a { display: inline-block; - padding: 8px 20px; - font-weight: bold; + padding: 12px 24px; + font-weight: normal; line-height: var(--line-height-l); text-decoration: none; - color: var(--black); - background-color: var(--white); - border-radius: 4px; - box-shadow: 3px 3px 7px 1px rgba(0 0 0 / 20%); + color: var(--white); + font-size:var(--body-font-size-m); + border-bottom-left-radius: 20px; + transition-duration: .2s; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(.4,0,.2,1); + transition-timing-function: linear; } p.button-container a:visited { - color: var(--black); text-decoration: none; } +p.button-container:focus { + background-color: var(--brand-primary-strong); + +} +p.button-container a:hover { + + background-color: var(--brand-primary-strong); +} body { display: none; @@ -248,24 +265,91 @@ body.appear { header { height: var(--nav-height); + position: fixed; + top: 0; + left: 0; + width: 100vw; + z-index: 1000; } -body.gray-gradient > main { - background: linear-gradient( - var(--gray-gradient-angle), - var(--gray-gradient-light) 0%, - var(--gray-gradient-light) var(--gray-gradient-breakpoint), - var(--gray-gradient-dark) 100% - ); +main { + padding-top: var(--nav-height); + overflow: hidden; +} + +body.gray-gradient>main { + background: linear-gradient(var(--gray-gradient-angle), + var(--gray-gradient-light) 0%, + var(--gray-gradient-light) var(--gray-gradient-breakpoint), + var(--gray-gradient-dark) 100%); } main .section { - padding: 32px 0; - background-color: var(--white); + padding: 20px 0; +} + +main>div.section:first-child { + padding-bottom: 24px; +} + +main .section.cards-container { + padding-bottom: 20px; +} +main .section.cards-container .default-content-wrapper:first-child p { + color: var(--brand-critical-strong); + text-align:left; +} + +main .content-center .default-content-wrapper { + text-align: center; +} + +main .section.content-bump-out .default-content-wrapper { + border: 0; + max-width: 1158px; + margin: -81px auto 0; + display: table; + width: 100%; + text-align: left; + background-color: #fff; + position: relative; + padding-left: 58px; +} + +main .section.content-bump-out .default-content-wrapper p { + text-align: left; +} + +main .section.content-bump-out .default-content-wrapper .button-container { + text-align: right; +} +main .section .default-content-wrapper .button-container { + text-align: center; +} + + +main .section.content-bump-out.content-center .default-content-wrapper { + text-align:center; +} + +main .section .default-content-wrapper.button-container { + margin: 0 auto; + max-width: 1200px; + text-align: center; +} + +main .section.default-content-wrapper .button-container { + text-align: right; + max-width: 1200px; +} + +main .section.default-content-wrapper { + text-align: right; + max-width: 1200px; } main .section.gray-background { - background-color: var(--bright-gray); + background-color: var(--brand--secondary-subtle); } main .section.silver-background { @@ -279,7 +363,7 @@ main .section.quartz-background { main .section.quartz-background h1, main .section.quartz-background h2, main .section.quartz-background p, -main .section.quartz-background li{ +main .section.quartz-background li { color: var(--white); } @@ -288,32 +372,38 @@ main .section.red-title h2 { color: var(--red); } -main .section[data-background-image] { - position: relative; - padding-bottom: 200px; +main .section.hero h1 { + font-size:36px; +} +main .section.red-headline.plain-section .default-content-wrapper p:first-of-type { + color: var(--dark-red); } -main .section.center .default-content-wrapper { - text-align: center; +main .section[data-background-image] { + position: relative; } -main .section .default-content-wrapper > p > picture { +main .section .default-content-wrapper>p>picture { display: block; position: relative; height: 100%; width: 100%; - margin: 0 auto; + margin: 64px auto; } -main .section.image-boxshadow .default-content-wrapper > p.image { +main .section.image-boxshadow .default-content-wrapper>p.image { padding: 32px; background-color: #fff; border-radius: 10px; - box-shadow: 0 4px 10px 0 rgba(0 0 0 / 20%) } +main .section.content-bump-out .default-content-wrapper p:first-of-type { + margin: 40px 0 16px; + text-align: center; + color: var(--dark-red); +} -main .section .default-content-wrapper > p > picture > img { +main .section .default-content-wrapper>p>picture>img { position: absolute; top: 0; left: 0; @@ -323,8 +413,11 @@ main .section .default-content-wrapper > p > picture > img { object-position: center center; } -main > .section[data-background-image] > .section-bg-image-wrapper { - position: absolute; +main .section .bg-image-wrapper { + text-align: center; +} + +main>.section[data-background-image]>.section-bg-image-wrapper { margin: 0; padding: 0; bottom: 0; @@ -334,28 +427,42 @@ main > .section[data-background-image] > .section-bg-image-wrapper { max-width: unset; } -main > .section[data-background-image] > .section-bg-image-wrapper picture { +main>.section[data-background-image]>.section-bg-image-wrapper picture { display: block; position: relative; height: 100%; width: 100%; } -main > .section[data-background-image] > .section-bg-image-wrapper picture img { - position: absolute; +main>.section[data-background-image]>.section-bg-image-wrapper picture img { bottom: 0; left: 0; width: 100%; object-fit: cover; object-position: center; + + /* temporary until there are right sized images */ + height: 600px; } main .section .default-content-wrapper span.icon { + position: relative; + top: 6px; + left: 12px; display: inline-block; - height: 30px; - width: 30px; + height: 23px; + width: 23px; } +main .section .default-content-wrapper span.icon img { + height:100%; + width:100%; +} + +main .section .default-content-wrapper span.icon { + height:25px; + width:25px; +} main .section .default-content-wrapper span.icon svg { height: 100%; width: 100%; @@ -381,70 +488,61 @@ main .section[data-layout]:not(.center) .layout-content-wrapper .default-content border-left: 3px solid var(--red); } -main .section[data-layout="50/50"] .layout-content-wrapper > div.separator-wrapper { +main .section[data-layout="50/50"] .layout-content-wrapper>div.separator-wrapper { flex-basis: 100%; padding: 2em 0; } -main .section[data-layout="50/50"] .layout-content-wrapper > div.separator-wrapper hr { +main .section[data-layout="50/50"] .layout-content-wrapper>div.separator-wrapper hr { border: none; border-top: 1px solid var(--gray); margin: 0; } -main > .section > div { +main>.section>div { padding: 0 16px; margin: 0 auto; - max-width: var(--normal-page-width); } -main > .section.full-width > div { +main>.section.full-width>div { margin: 0; + text-align: center; max-width: var(--full-page-width); } - main .section.cadet-gradient-background { - background: linear-gradient( - -180deg, + background: linear-gradient(-180deg, #5b6770 0%, #edeff0 86%, rgba(242 244 245 / 55%) 88%, rgba(247 248 248 / 0%) 90%, - rgba(255 255 255 / 0%) 100% - ); + rgba(255 255 255 / 0%) 100%); } main .section.cadet-gradient-background[data-background-image] { - background: linear-gradient( - -180deg, + background: linear-gradient(-180deg, #5b6770 0%, #edeff0 86%, rgba(242 244 245 / 55%) 88%, rgba(247 248 248 / 0%) 90%, - rgba(255 255 255 / 0%) 100% - ); + rgba(255 255 255 / 0%) 100%); } main .section.gray-gradient-background { - background: linear-gradient( - var(--gray-gradient-angle), - var(--gray-gradient-light) 0%, - var(--gray-gradient-light) var(--gray-gradient-breakpoint), - var(--gray-gradient-dark) 100% - ); + background: linear-gradient(var(--gray-gradient-angle), + var(--gray-gradient-light) 0%, + var(--gray-gradient-light) var(--gray-gradient-breakpoint), + var(--gray-gradient-dark) 100%); } main .section.gray-gradient-background[data-background-image] { - background: linear-gradient( - var(--gray-gradient-angle), + background: linear-gradient(var(--gray-gradient-angle), var(--gray-gradient-light) 0%, var(--gray-gradient-light) var(--gray-gradient-breakpoint), - var(--gray-gradient-dark) 80% - ); + var(--gray-gradient-dark) 80%); } -main .angled-inverted-background.section.hero-container > .hero-wrapper { +main .angled-inverted-background.section.hero-container>.hero-wrapper { background-color: var(--gray-gradient-light); } @@ -519,12 +617,10 @@ main .section.inverted-gradient-background { } .angle-background { - background: linear-gradient( - var(--gray-gradient-angle), - var(--gray-gradient-light) 0%, - var(--gray-gradient-light) var(--gray-gradient-breakpoint), - var(--gray-gradient-dark) 100% - ); + background: linear-gradient(var(--gray-gradient-angle), + var(--gray-gradient-light) 0%, + var(--gray-gradient-light) var(--gray-gradient-breakpoint), + var(--gray-gradient-dark) 100%); } .sr-only { @@ -536,6 +632,10 @@ main .section.inverted-gradient-background { overflow: hidden; } +main .disclaimer-modal-container { + background-color: rgb(0 0 0 / 50%); +} + @media screen and (max-width: 299px) { :root { --nav-height: 75px; @@ -548,49 +648,65 @@ main .section.inverted-gradient-background { @media screen and (min-width: 600px) { :root { - --nav-height: 125px; + --nav-height: 65px; + } + + h1 { + font-size: var(--heading-font-size-xl); + } + + h2 { + font-size: var(--heading-font-size-m); } - main > .section > div { + main>.section>div { padding: 0 24px; } - main > .section[data-background-image] > .section-bg-image-wrapper { + main>.section>.default-content-wrapper>picture>img { + object-position: unset; + width: 100%; + height: 100%; + object-fit: cover; + } + + main>.section[data-background-image]>.section-bg-image-wrapper { height: 20%; } - main > .section[data-background-image] > .section-bg-image-wrapper > picture > img { + main>.section[data-background-image]>.section-bg-image-wrapper>picture>img { object-position: unset; } main .section.angled-inverted-background.hero-container .hero { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10vw), 0 100%); } -} -@media screen and (min-width: 900px) { - h1 { - font-size: var(--heading-font-size-xl); + main .section.content-bump-out .default-content-wrapper p:first-of-type { + font-size: var(--body-font-size-m); + text-align: left; } +} - h2 { - font-size: var(--heading-font-size-l); - } +main .section.content-bump-out.content-center .default-content-wrapper p { + font-size: var(--body-font-size-m); + text-align: center; +} - p { - font-size: var(--body-font-size-l); - } - main > .section > div { - padding: 0 90px; +@media screen and (min-width: 900px) { + main>.section>div { + margin: 16px auto 32px; + max-width: 1158px; + } - main > .section[data-background-image] { - padding-bottom: 300px; + main>.section[data-background-image] { + /*padding-bottom: 81px;*/ } - main > .section[data-background-image] > .section-bg-image-wrapper picture { + main>.section[data-background-image]>.section-bg-image-wrapper picture { height: 100%; } @@ -600,7 +716,7 @@ main .section.inverted-gradient-background { column-gap: 3em; } - main .section[data-layout="50/50"] .layout-content-wrapper > div { + main .section[data-layout="50/50"] .layout-content-wrapper>div { flex-basis: calc(50% - 1.5em); } @@ -613,14 +729,12 @@ main .section.inverted-gradient-background { } main .section.cadet-gradient-background[data-background-image] { - background: linear-gradient( - -180deg, + background: linear-gradient(-180deg, #5b6770 0%, #edeff0 76%, rgba(242 244 245 / 55%) 78%, rgba(247 248 248 / 0%) 80%, - rgba(255 255 255 / 0%) 100% - ); + rgba(255 255 255 / 0%) 100%); } main .section.angled-inverted-background.hero-container .hero { @@ -630,17 +744,15 @@ main .section.inverted-gradient-background { @media screen and (min-width: 1200px) { main .section.cadet-gradient-background[data-background-image] { - background: linear-gradient( - -180deg, + background: linear-gradient(-180deg, #5b6770 0%, #edeff0 68%, rgba(242 244 245 / 55%) 70%, rgba(247 248 248 / 0%) 72%, - rgba(255 255 255 / 0%) 92% - ); + rgba(255 255 255 / 0%) 92%); } - main > .section[data-background-image] > .section-bg-image-wrapper { + main>.section[data-background-image]>.section-bg-image-wrapper { height: 30%; } @@ -658,12 +770,18 @@ main .section.inverted-gradient-background { grid-template-columns: 3fr 1fr; } - main .section > div { - padding: 0 40px; - } - main .section.angled-inverted-background.hero-container .hero { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 5vw), 0 100%); } + main .section.content-bump-out.content-center.hero-container .default-content-wrapper { + margin: -64px auto 0; + padding: 0 64px; + } + + main .section.content-bump-out.content-center .default-content-wrapper { + margin: -56px auto 0; + padding: 0 64px; + } + } diff --git a/tools/config.json b/tools/config.json new file mode 100644 index 00000000..8ff29a19 --- /dev/null +++ b/tools/config.json @@ -0,0 +1,17 @@ +{ + "project": "Takeda", + "host": "https://phase-two-redo--takeda-ihs--hlxsites.hlx.page/", + "plugins": [ + { + "id": "asset-library", + "title": "AEM Assets Library", + "environments": [ + "edit" + ], + "url": "https://experience.adobe.com/solutions/CQ-assets-selectors/static-assets/resources/franklin/asset-selector.html", + "isPalette": true, + "includePaths": [ "**.docx**" ], + "paletteRect": "top: 50px; bottom: 10px; right: 10px; left: auto; width:400px; height: calc(100vh - 60px)" + } + ] +} \ No newline at end of file diff --git a/tools/importer/forms/ui/css/styles.css b/tools/importer/forms/ui/css/styles.css new file mode 100644 index 00000000..36462f07 --- /dev/null +++ b/tools/importer/forms/ui/css/styles.css @@ -0,0 +1,316 @@ +.form-preview { + --form-columns: 1; +} +.container { + height: 89%; + display: flex; + flex-wrap: wrap; + line-height: normal; + font-size: 16px; +} +.container hr { + margin-top: 0; + border-bottom: 2px solid gray; +} + +.status { + display: flex; + align-items: center; +} + +.left { + max-width: 20%; + flex-basis: 20%; + padding: 24px; + border-right: 2px solid lightgray; +} + +.right { + flex-basis: 78%; + padding: 20px; +} + +.right .header { + display: flex; + justify-content: space-between; +} + +.icon { + width: 40px; + height: 40px; + display: block; +} +.recaptcha-icon { + background: url(../svg/recaptcha.svg) no-repeat; +} +.marketo-icon { + margin-top: -12px; + background: url(../svg/marketo.svg) no-repeat; +} + +.attachment-icon { + background: url(../svg/attachments.svg) no-repeat; +} + +.pdf-icon { + background: url(../svg/pdf.svg) no-repeat; +} + +form label { + width: 150px; +} + +.left :is(input, select) { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +button:disabled { + background-color: #cccccc; + color: #666666; + cursor: not-allowed; +} + +button { + background-color: #4CAF50; + color: white; + padding: 14px 20px; + margin: 8px 0; + border: none; + border-radius: 4px; + cursor: pointer; + height: 45px; +} + +.cards-container { + display: flex; + flex-wrap: wrap; + flex-direction: column; +} +.cards-container a { + text-decoration: none; +} +.card { + background-color: white; + color: #4a4a4a; + width: 100%; + position: relative; + border-top: 1px solid #dbdbdb; + cursor: pointer; +} +.card.selected { + border-bottom: 4px solid green; + border-top: 4px solid green; +} +.card .card-content .follow-info { + padding: 0 0 1rem; + display: flex; +} +.card .card-content .follow-info h2 { + text-align: center; + width: 50%; + margin: 0; + box-sizing: border-box; +} +.card .card-content .follow-info h2 div { + text-decoration: none; + display: flex; + flex-direction: column; + border-radius: 0.8rem; + transition: background-color 100ms ease-in-out; +} +.card .card-content .follow-info h2 div span { + color: #1c9eff; + font-weight: bold; +} +.card .card-content .follow-info h2 div small { + color: #afafaf; + font-size: 0.85rem; + font-weight: normal; +} +.card-header-title { + align-items: center; + color: #363636; + display: flex; + padding: 0 0.75rem; +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.features { + display: flex; +} + +.title { + color: #4CAF50; + font-weight: bold; +} + +.card-footer { + border-top: 1px solid #dbdbdb; + align-items: stretch; + display: flex; +} +card-footer-item:not(:last-child) { + border-right: 1px solid #dbdbdb; +} + +.card-footer-item { + align-items: center; + display: flex; + flex-basis: 0; + flex-grow: 1; + flex-shrink: 0; + padding: 0.75rem; +} + +.ribbon span { + display: block; + padding: 15px; + background-color: #db9c34; + box-shadow: 0 5px 10px rgba(0,0,0,.1); + color: #fff; + font-weight: bold +} + +.ribbon[data-pdf="true"] span { + background-color: #3498db; +} + +.toolbar { + display: flex; + justify-content: space-between; + gap: 20px; +} + +.smartfill button, +.toolbar button { + width: auto; +} +.stats { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.stats > div { + text-align: center; + min-width: 110px; +} + +.stats span { + font-weight: 700; + font-size: 35px; + color: #1c9eff; +} + +.smartfill { + display: flex; + flex-wrap: wrap; + gap: 20px; + align-items: center; +} + +input[type=file] { + padding-top: 10px; +} + +input::file-selector-button { + color: dodgerblue !important; + background-color: #fff; +} + +#smartprefill:checked ~ .smartprefill-container { + display: block; +} + +#smartprefill:checked ~ .stats-container { + display: none; +} +.smartprefill-container { + display: none; +} + +.error { + color: red; +} + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #4CAF50; +} + +input:focus + .slider { + box-shadow: 0 0 1px #4CAF50; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +.switchcontainer { + display: flex; + align-items: center; + gap: 20px; +} + +#editor { + width: 100%; + height: 500px; +} + +.hide { + display: none; +} + +iframe { + width: 50%; + height: 100%; +} \ No newline at end of file diff --git a/tools/importer/forms/ui/index.html b/tools/importer/forms/ui/index.html new file mode 100644 index 00000000..aa6b5be8 --- /dev/null +++ b/tools/importer/forms/ui/index.html @@ -0,0 +1,71 @@ + + + + + + + + + Forms Importer + + + + +
+
+
+
+

Configuration

+ +
+ + +
+ Include plain text + +
+
+ Use iframe + +
+ +
+
+
Status:
+
Click Scan to Start
+
+
+ +
+
+

Preview Form

+
+
+ JSON View + +
+ +
+
+
+
+
+ Scan & Select the form. +
+
+
+
+
+ + + + diff --git a/tools/importer/forms/ui/js/script.js b/tools/importer/forms/ui/js/script.js new file mode 100644 index 00000000..846e2483 --- /dev/null +++ b/tools/importer/forms/ui/js/script.js @@ -0,0 +1,163 @@ +/* + * 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. + */ + +/* eslint-disable no-undef */ + +const FORM_IMPORTER = 'https://g7ory75qdb.execute-api.ap-south-1.amazonaws.com/vega-services/importer'; + +const scanFormEl = document.querySelector('form'); +const domainEl = document.querySelector('#domainURL'); +const includePlainText = document.querySelector('#includePlainText'); +const startBtn = document.querySelector('#startBtn'); +const copyAction = document.querySelector('#copyAction'); +const msgEl = document.querySelector('#msg'); +const switchView = document.querySelector('#switchView'); +const cardsContainer = document.getElementById('cards-container'); +const formPreview = document.querySelector('.form-preview'); +const jsonPreview = document.querySelector('.json-preview'); +let forms = []; +let editor; +let article; +let selectedForm; + +function convertToCSV(fields, divider = '\t') { + if (fields && fields.length > 0) { + const keys = Object.keys(fields?.[0]); + const th = keys.join(divider); + const rows = fields + .map((field) => Object.values(field).join(divider)) + .join('\n'); + return `${th}\n${rows}`; + } + return 'table is empty'; +} + +function updateStatus(msg, completed = false, error = false) { + startBtn.disabled = !completed; + msgEl.textContent = msg; + if (error) { + msgEl.classList.add('error'); + } else { + msgEl.classList.remove('error'); + } +} + +function cleanUp() { + cardsContainer.innerHTML = ''; +} + +function cardTemplate(form, index) { + return `
+
+
${form.name}
+
+ ${form?.stats?.attachmentsUsed ? '' : ''} + ${form?.stats?.recaptchaUsed ? '' : ''} + ${form?.stats?.isMarketToForm ? '' : ''} +
+
+
+ +
+ +
`; +} + +function renderCards() { + cleanUp(); + const formCards = forms.map(cardTemplate).join(''); + cardsContainer.innerHTML += formCards; +} + +function loadForm(index) { + const formEl = document.createElement('form'); + selectedForm = forms[index]; + generateFormRendition(selectedForm?.data, formEl); + formPreview.replaceChildren(formEl); + // Set JSON data to the editor + editor.session.setValue(JSON.stringify(selectedForm, null, 2)); + copyAction.disabled = false; +} +async function previewForm(event) { + if (article) { + article.classList.toggle('selected'); + } + article = event?.target?.closest('article'); + if (article) { + article.classList.toggle('selected'); + const { index } = article.dataset; + loadForm(index); + } +} + +function setupJSONView() { + editor = ace.edit('editor'); + editor.setTheme('ace/theme/monokai'); + editor.session.setMode('ace/mode/json'); +} + +async function scanNow() { + const valid = scanFormEl.checkValidity(); + if (valid) { + cleanUp(); + updateStatus('Scan Initiated...'); + const domain = domainEl.value; + const payload = { url: domain, options: { includePlainText: includePlainText.checked } }; + try { + const response = await fetch(FORM_IMPORTER, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + if (response.ok) { + forms = await response.json(); + renderCards(forms); + updateStatus('Scanning Completed', true, false); + } else { + const msg = await response.text(); + updateStatus(`Failed to scan ${msg}`, true, true); + } + } catch (e) { + updateStatus(e.message, true, true); + } + startBtn.disabled = false; + } +} + +function copyToClipboard() { + if (selectedForm) { + const data = convertToCSV(selectedForm.data); + navigator.clipboard.writeText(data).then(() => { + // eslint-disable-next-line no-alert + alert('Copied to clipboard'); + }) + .catch(() => { + // eslint-disable-next-line no-alert + alert('Issue in copying to clipboard use json view'); + }); + } +} + +copyAction.addEventListener('click', copyToClipboard); +startBtn.addEventListener('click', scanNow); +cardsContainer.addEventListener('click', previewForm); +switchView.addEventListener('click', () => { + formPreview.classList.toggle('hide'); + jsonPreview.classList.toggle('hide'); +}); +setupJSONView(); diff --git a/tools/importer/forms/ui/svg/attachments.svg b/tools/importer/forms/ui/svg/attachments.svg new file mode 100644 index 00000000..c44f5cae --- /dev/null +++ b/tools/importer/forms/ui/svg/attachments.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/importer/forms/ui/svg/marketo.svg b/tools/importer/forms/ui/svg/marketo.svg new file mode 100644 index 00000000..017b7965 --- /dev/null +++ b/tools/importer/forms/ui/svg/marketo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/importer/forms/ui/svg/pdf.svg b/tools/importer/forms/ui/svg/pdf.svg new file mode 100644 index 00000000..637e1062 --- /dev/null +++ b/tools/importer/forms/ui/svg/pdf.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/importer/forms/ui/svg/recaptcha.svg b/tools/importer/forms/ui/svg/recaptcha.svg new file mode 100644 index 00000000..929c0bab --- /dev/null +++ b/tools/importer/forms/ui/svg/recaptcha.svg @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json new file mode 100644 index 00000000..a079c670 --- /dev/null +++ b/tools/sidekick/config.json @@ -0,0 +1,38 @@ +{ + "project": "Takeda", + "host": "main--takeda-ihs--hlxsites.hlx.page", + "plugins": [ + { + "id": "asset-library", + "title": "AEM Assets Library", + "environments": [ + "edit" + ], + "url": "https://experience.adobe.com/solutions/CQ-assets-selectors/static-assets/resources/franklin/asset-selector.html", + "isPalette": true, + "includePaths": [ "**.docx**" ], + "paletteRect": "top: 50px; bottom: 10px; right: 10px; left: auto; width:400px; height: calc(100vh - 60px)" + }, + { + "project": "ihs", + "plugins": [ + { + "id": "library", + "title": "Library", + "environments": ["edit"], + "url": "http://localhost:3000//tools/sidekick/library.html", + "includePaths": ["**.docx**"] + } + ] + }, + { + "id": "form-editor", + "title": "Form Editor", + "event": "editform", + "environments": ["preview"] + } + + ] +} + + diff --git a/tools/sidekick/library.html b/tools/sidekick/library.html new file mode 100644 index 00000000..d4a659bd --- /dev/null +++ b/tools/sidekick/library.html @@ -0,0 +1,42 @@ + + + + + + + + + + + Sidekick Library + + + + + + + \ No newline at end of file diff --git a/utils/helpers.js b/utils/helpers.js new file mode 100644 index 00000000..9bd6c090 --- /dev/null +++ b/utils/helpers.js @@ -0,0 +1,176 @@ +import createTag from './tag.js'; + +/** + * * @param {HTMLElement} element the element with the parent undesired wrapper, like

+ * * @param {targetSelector} string selector of the target element + * result: removed the undesired wrapper + */ +export function removeOuterElementLayer(element, targetSelector) { + const targetElement = element.querySelector(targetSelector); + if (targetElement) { + const parent = targetElement.parentNode; + if (parent) (parent).replaceWith(targetElement); + } +} + +/** + * * @param {HTMLElement} element the elemen/block with mutilple child + * * that you want to combine that into single div only + * result: single div with all children elements + * e.g. input:
+ * *

+ * *

+ * *
+ * * output:
+ * *
+ * *


+ * *

+ * *
+ */ +export function combineChildrenToSingleDiv(element) { + const targetChildren = element.querySelectorAll(':scope > div'); + if (targetChildren.length === 0) { return; } + + const singleDiv = document.createElement('div'); + targetChildren.forEach((targetChild) => { + const children = Array.from(targetChild.childNodes); + children.forEach((childElement) => { + singleDiv.appendChild(childElement); + }); + targetChild.remove(); + }); + + element.append(singleDiv); +} + +/** + * * @param {HTMLElement} element + * * @param {string} targetTag, like 'ul' or 'div' + * * @param {string} className + * result: return the new element with inner content of the element, desired tag and css class + */ +export function changeTag(element, targetTag, className) { + const newElClass = className || ''; + const innerContent = element.innerHTML; + const newTagElement = createTag(targetTag, { class: newElClass }, innerContent); + + return newTagElement; +} + +/** + * * @param {string} url the href of a link element + * result: return `_self` or `_blank` if the link has the same host + */ +export function returnLinkTarget(url) { + const currentHost = window.location.host; + const urlObject = new URL(url); + const isSameHost = urlObject.host === currentHost; + + // take in pathname that should be opened in new tab, in redirects excel + const redirectExternalPaths = ['/history', '/chat']; + const redirectToExternalPath = redirectExternalPaths.includes(urlObject.pathname); + + if (!isSameHost || redirectToExternalPath) { + return '_blank'; + } + return '_self'; +} + +// as the blocks are loaded in aysnchronously, we don't have a specific timing +// that the all blocks are loaded -> cannot use a single observer to +// observe all blocks, so use functions here in blocks instead +// eslint-disable-next-line max-len +const requireRevealWrapper = ['slide-reveal-up', 'slide-reveal-up-slow']; + +export function addRevealWrapperToAnimationTarget(element) { + const revealWrapper = createTag('div', { class: 'slide-reveal-wrapper' }); + const parent = element.parentNode; + // Insert the wrapper before the element + parent.insertBefore(revealWrapper, element); + revealWrapper.appendChild(element); +} + +// eslint-disable-next-line max-len +export function addAnimatedClassToElement(targetSelector, animatedClass, delayTime, targetSelectorWrapper) { + const target = targetSelectorWrapper.querySelector(targetSelector); + if (target) { + target.classList.add(animatedClass); + if (delayTime) target.style.transitionDelay = `${delayTime}s`; + if (requireRevealWrapper.indexOf(animatedClass) !== -1) { + addRevealWrapperToAnimationTarget(target); + } + } +} + +// eslint-disable-next-line max-len +export function addAnimatedClassToMultipleElements(targetSelector, animatedClass, delayTime, targetSelectorWrapper, staggerTime) { + const targets = targetSelectorWrapper.querySelectorAll(targetSelector); + if (targets) { + targets.forEach((target, i) => { + target.classList.add(animatedClass); + if (delayTime) target.style.transitionDelay = `${delayTime * (i + 1)}s`; + if (staggerTime) target.style.transitionDelay = `${delayTime + staggerTime * (i + 1)}s`; + if (requireRevealWrapper.indexOf(animatedClass) !== -1) { + addRevealWrapperToAnimationTarget(target); + } + }); + } +} + +export function addInviewObserverToTriggerElement(triggerElement) { + const observerOptions = { + threshold: 0.25, // show when is 25% in view + }; + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add('in-view'); + observer.unobserve(entry.target); + } + }); + }, observerOptions); + observer.observe(triggerElement); +} + +// eslint-disable-next-line max-len +export function addInViewAnimationToSingleElement(targetElement, animatedClass, triggerElement, delayTime) { + // if it's HTML element + if (targetElement.nodeType === 1) { + targetElement.classList.add(animatedClass); + if (requireRevealWrapper.indexOf(animatedClass) !== -1) { + addRevealWrapperToAnimationTarget(targetElement); + } + } + // if it's string only, which should be a selector + if (targetElement.nodeType === 3) { + addAnimatedClassToElement(targetElement, animatedClass, triggerElement, delayTime); + } + const trigger = triggerElement || targetElement; + addInviewObserverToTriggerElement(trigger); +} + +export function addInViewAnimationToMultipleElements(animatedItems, triggerElement, staggerTime) { + // set up animation class + animatedItems.forEach((el, i) => { + const delayTime = staggerTime ? i * staggerTime : null; + if (Object.prototype.hasOwnProperty.call(el, 'selector')) { + addAnimatedClassToElement(el.selector, el.animatedClass, delayTime, triggerElement); + } + if (Object.prototype.hasOwnProperty.call(el, 'selectors')) { + // eslint-disable-next-line max-len + addAnimatedClassToMultipleElements(el.selectors, el.animatedClass, el.staggerTime, triggerElement); + } + }); + + // add `.in-view` to triggerElement, so the elements inside will start animating + addInviewObserverToTriggerElement(triggerElement); +} + +export default { + removeOuterElementLayer, + changeTag, + returnLinkTarget, + addInViewAnimationToSingleElement, + addInViewAnimationToMultipleElements, + addInviewObserverToTriggerElement, +}; diff --git a/utils/tag.js b/utils/tag.js new file mode 100644 index 00000000..b264213e --- /dev/null +++ b/utils/tag.js @@ -0,0 +1,23 @@ +/** + * Create an element with ID, class, children, and attributes + * @param {String} tag the tag nav of the element + * @param {Object} attributes the attributes of the tag + * @param {HTMLElement} html the content of the element + * @returns {HTMLElement} the element created + */ +export default function createTag(tag, attributes, html) { + const el = document.createElement(tag); + if (html) { + if (html instanceof HTMLElement) { + el.append(html); + } else { + el.insertAdjacentHTML('beforeend', html); + } + } + if (attributes) { + Object.keys(attributes).forEach((key) => { + el.setAttribute(key, attributes[key]); + }); + } + return el; +}