diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 860bb022..b396b723 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,5 +4,5 @@ Resolves: [MWPW-NUMBER](https://jira.corp.adobe.com/browse/MWPW-NUMBER) **Test URLs:** -- Before: https://main--bacom--adobecom.aem.live/?martech=off -- After: https://--bacom--adobecom.aem.live/?martech=off +- Before: https://main--da-bacom--adobecom.aem.live/?martech=off +- After: https://--da-bacom--adobecom.aem.live/?martech=off diff --git a/.github/workflows/import/constants.js b/.github/workflows/import/constants.js deleted file mode 100644 index 71ff454e..00000000 --- a/.github/workflows/import/constants.js +++ /dev/null @@ -1,15 +0,0 @@ -export const AEM_ORIGIN = 'https://admin.hlx.page'; - -export const SUPPORTED_FILES = { - html: 'text/html', - jpeg: 'image/jpeg', - json: 'application/json', - jpg: 'image/jpeg', - png: 'image/png', - gif: 'image/gif', - mp4: 'video/mp4', - pdf: 'application/pdf', - svg: 'image/svg+xml', -}; - -export const DA_ORIGIN = 'https://admin.da.live' diff --git a/.github/workflows/import/converters.js b/.github/workflows/import/converters.js deleted file mode 100644 index ac12771a..00000000 --- a/.github/workflows/import/converters.js +++ /dev/null @@ -1,157 +0,0 @@ -import { unified } from 'unified'; -import remarkParse from 'remark-parse'; -import remarkGridTable from '@adobe/remark-gridtables'; -import { toHast as mdast2hast, defaultHandlers } from 'mdast-util-to-hast'; -import { raw } from 'hast-util-raw'; -import { mdast2hastGridTablesHandler } from '@adobe/mdast-util-gridtables'; -import { toHtml } from 'hast-util-to-html'; - -import { JSDOM } from 'jsdom'; - -function toBlockCSSClassNames(text) { - if (!text) return []; - const names = []; - const idx = text.lastIndexOf('('); - if (idx >= 0) { - names.push(text.substring(0, idx)); - names.push(...text.substring(idx + 1).split(',')); - } else { - names.push(text); - } - - return names.map((name) => name - .toLowerCase() - .replace(/[^0-9a-z]+/g, '-') - .replace(/^-+/, '') - .replace(/-+$/, '')) - .filter((name) => !!name); -} - -function convertBlocks(dom) { - const tables = dom.window.document.querySelectorAll('body > table'); - - tables.forEach((table) => { - const rows = [...table.querySelectorAll(':scope > tbody > tr, :scope > thead > tr')]; - const nameRow = rows.shift(); - const divs = rows.map((row) => { - const cols = row.querySelectorAll(':scope > td, :scope > th'); - // eslint-disable-next-line no-shadow - const divs = [...cols].map((col) => { - const { innerHTML } = col; - const div = dom.window.document.createElement('div'); - div.innerHTML = innerHTML; - return div; - }); - const div = dom.window.document.createElement('div'); - div.append(...divs); - return div; - }); - - const div = dom.window.document.createElement('div'); - div.className = toBlockCSSClassNames(nameRow.textContent).join(' '); - div.append(...divs); - table.parentElement.replaceChild(div, table); - }); -} - -function makePictures(dom) { - const imgs = dom.window.document.querySelectorAll('img'); - imgs.forEach((img) => { - const clone = img.cloneNode(true); - clone.setAttribute('loading', 'lazy'); - // clone.src = `${clone.src}?optimize=medium`; - - let pic = dom.window.document.createElement('picture'); - - const srcMobile = dom.window.document.createElement('source'); - srcMobile.srcset = clone.src; - - const srcTablet = dom.window.document.createElement('source'); - srcTablet.srcset = clone.src; - srcTablet.media = '(min-width: 600px)'; - - pic.append(srcMobile, srcTablet, clone); - - const hrefAttr = img.getAttribute('href'); - if (hrefAttr) { - const a = dom.window.document.createElement('a'); - a.href = hrefAttr; - const titleAttr = img.getAttribute('title'); - if (titleAttr) { - a.title = titleAttr; - } - a.append(pic); - pic = a; - } - - // Determine what to replace - const imgParent = img.parentElement; - const imgGrandparent = imgParent.parentElement; - if (imgParent.nodeName === 'P' && imgGrandparent?.childElementCount === 1) { - imgGrandparent.replaceChild(pic, imgParent); - } else { - imgParent.replaceChild(pic, img); - } - }); -} - -function makeSections(dom) { - const children = dom.window.document.body.querySelectorAll(':scope > *'); - - const section = dom.window.document.createElement('div'); - const sections = [...children].reduce((acc, child) => { - if (child.nodeName === 'HR') { - child.remove(); - acc.push(dom.window.document.createElement('div')); - } else { - acc[acc.length - 1].append(child); - } - return acc; - }, [section]); - - dom.window.document.body.append(...sections); -} - -// Generic docs have table blocks and HRs, but not ProseMirror decorations -export function docDomToAemHtml(dom) { - convertBlocks(dom); - makePictures(dom); - makeSections(dom); - - return dom.window.document.body.innerHTML; -} - -function makeHast(mdast) { - const handlers = { ...defaultHandlers, gridTable: mdast2hastGridTablesHandler() }; - const hast = mdast2hast(mdast, { handlers, allowDangerousHtml: true }); - return raw(hast); -} - -function removeImageSizeHash(dom) { - const imgs = dom.window.document.querySelectorAll('[src*="#width"]'); - imgs.forEach((img) => { - img.setAttribute('src', img.src.split('#width')[0]); - }); -} - -export function mdToDocDom(md) { - // convert linebreaks - const converted = md.replace(/(\r\n|\n|\r)/gm, '\n'); - - // convert to mdast - const mdast = unified() - .use(remarkParse) - .use(remarkGridTable) - .parse(converted); - - const hast = makeHast(mdast); - - let htmlText = toHtml(hast); - htmlText = htmlText.replaceAll('.hlx.page', '.hlx.live'); - htmlText = htmlText.replaceAll('.aem.page', '.aem.live'); - - const dom = new JSDOM(htmlText); - removeImageSizeHash(dom); - - return dom; -} diff --git a/.github/workflows/import/daFetch.js b/.github/workflows/import/daFetch.js deleted file mode 100644 index c9e90f13..00000000 --- a/.github/workflows/import/daFetch.js +++ /dev/null @@ -1,103 +0,0 @@ -import { DA_ORIGIN } from './constants.js'; - -async function getImsToken() { - const params = new URLSearchParams(); - params.append('client_id', process.env.CLIENT_ID); - params.append('client_secret', process.env.CLIENT_SECRET); - params.append('code', process.env.CODE); - params.append('grant_type', process.env.GRANT_TYPE); - - const response = await fetch(process.env.IMS_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: params, - }); - - if (!response.ok) { - throw new Error('Failed to retrieve IMS token'); - } - - const data = await response.json(); - return data.access_token; -} - -let token -export const daFetch = async (url, opts = {}) => { - opts.headers ||= {}; - if(!token) console.log("Fetching IMS token") - token = token || await getImsToken(); - if(!token) console.log("Fetched IMS token") - opts.headers.Authorization = `Bearer ${token}`; - const resp = await fetch(url, opts); - if(!resp.ok) throw new Error("DA import failed") - return resp; -}; - -export function replaceHtml(text, fromOrg, fromRepo) { - let inner = text; - if (fromOrg && fromRepo) { - const fromOrigin = `https://main--${fromRepo}--${fromOrg}.aem.live`; - inner = text - .replaceAll('./media', `${fromOrigin}/media`) - .replaceAll('href="/', `href="${fromOrigin}/`); - } - - return ` - -
-
${inner}
-
- - `; -} - -export async function saveToDa(text, url) { - const daPath = `/${url.org}/${url.repo}${url.pathname}`; - const daHref = `https://da.live/edit#${daPath}`; - const { org, repo } = url; - - const body = replaceHtml(text, org, repo); - - const blob = new Blob([body], { type: 'text/html' }); - const formData = new FormData(); - formData.append('data', blob); - const opts = { method: 'PUT', body: formData }; - try { - const daResp = await daFetch(`${DA_ORIGIN}/source${daPath}.html`, opts); - return { daHref, daStatus: daResp.status, daResp, ok: daResp.ok }; - } catch (e) { - console.log(`Couldn't save ${url.daUrl} `); - throw e - } -} - -function getBlob(url, content) { - const body = url.type === 'json' - ? content : replaceHtml(content, url.fromOrg, url.fromRepo); - - const type = url.type === 'json' ? 'application/json' : 'text/html'; - - return new Blob([body], { type }); -} - -export async function saveAllToDa(url, content) { - const { toOrg, toRepo, destPath, editPath, type } = url; - - const route = type === 'json' ? '/sheet' : '/edit'; - url.daHref = `https://da.live${route}#/${toOrg}/${toRepo}${editPath}`; - - const blob = getBlob(url, content); - const body = new FormData(); - body.append('data', blob); - const opts = { method: 'PUT', body }; - - try { - const resp = await daFetch(`${DA_ORIGIN}/source/${toOrg}/${toRepo}${destPath}`, opts); - return resp.status; - } catch { - console.log(`Couldn't save ${destPath}`); - return 500; - } -} diff --git a/.github/workflows/import/index.js b/.github/workflows/import/index.js deleted file mode 100644 index 772a753a..00000000 --- a/.github/workflows/import/index.js +++ /dev/null @@ -1,235 +0,0 @@ -import { DA_ORIGIN } from './constants.js'; -import { replaceHtml, daFetch } from './daFetch.js'; -import { mdToDocDom, docDomToAemHtml } from './converters.js'; -import { JSDOM } from 'jsdom'; - -// Run from the root of the project for local testing: node --env-file=.env .github/workflows/import/index.js -const EXTS = ['json', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'mp4', 'pdf']; - -const toOrg = 'adobecom'; -const toRepo = 'da-bacom'; -const importFrom = "https://main--bacom--adobecom.aem.live" -const liveDomain = "https://business.adobe.com"; -const excludedFiles = ["/redirects.json", "/metadata.json", "/metadata-seo.json", "/redirects_fancy.json"]; -const LINK_SELECTORS = [ - 'a[href*="/fragments/"]', - 'a[href*=".mp4"]', - 'a[href*=".pdf"]', - 'a[href*=".svg"]', - 'img[alt*=".mp4"]', -]; -// For any case where we need to find SVGs outside of any elements // in their text. -const LINK_SELECTOR_REGEX = /https:\/\/[^"'\s]+\.svg/g; - -export function calculateTime(startTime) { - const totalTime = Date.now() - startTime; - return `${String((totalTime / 1000) / 60).substring(0, 4)}`; -} - -async function importMedia(pageUrl, text) { - // Determine commmon prefixes - const aemLessOrigin = pageUrl.origin.split('.')[0]; - const prefixes = [aemLessOrigin]; - if (liveDomain) prefixes.push(liveDomain); - - const dom = new JSDOM(text) - const results = dom.window.document.body.querySelectorAll(LINK_SELECTORS.join(', ')); - - // TODO clean this up to be ready to be contributed to the DA-Importer - // const pattern = /https:\/\/[^"'\s]+\.(?:svg|mp4|pdf)/g; - // const results = text.match(pattern) ? - const matches = text.match(LINK_SELECTOR_REGEX)?.map((svgUrl) => { - const a = dom.window.document.createElement('a'); - a.href = svgUrl; - return a; - }) || []; - - const linkedMedia = [...results, ...matches].reduce((acc, a) => { - let href = a.getAttribute('href') || a.getAttribute('alt'); - // Don't add any off origin content. - const isSameDomain = prefixes.some((prefix) => href.startsWith(prefix)); - if (!isSameDomain) return acc; - - href = href.replace('.hlx.', '.aem.'); - - [href] = href.match(/^[^?#| ]+/); - - const url = new URL(href); - - // Check if its already in our URL list - const found = acc.some((existing) => existing.pathname === url.pathname); - if (found) return acc; - - // Mine the page URL for where to send the file - const { toOrg, toRepo } = pageUrl; - - url.toOrg = toOrg; - url.toRepo = toRepo; - - acc.push(url); - return acc; - }, []); - - for (const mediaUrl of linkedMedia) { - // This would be something such as - // '/assets/videos/customer-success-stories/media_12c330631cac835def2ef03bc64ae94ee23cff8ef.mp4' - console.log(`Importing media: ${mediaUrl.href}`); - try { - await importUrl(mediaUrl); - } catch (error) { - await slackNotification( - `Failed importing media /${toOrg}/${toRepo}/main${mediaUrl.href}. Error: ${error.message}` - ); - } - } -} - -async function saveAllToDa(url, blob) { - const { destPath, editPath, route } = url; - - url.daHref = `https://da.live${route}#/${toOrg}/${toRepo}${editPath}`; - - const body = new FormData(); - body.append('data', blob); - const opts = { method: 'PUT', body }; - - // Convert underscores to hyphens - const formattedPath = destPath.replaceAll('media_', 'media-'); - - try { - const resp = await daFetch(`${DA_ORIGIN}/source/${toOrg}/${toRepo}${formattedPath}`, opts); - return resp.status; - } catch { - console.log(`Couldn't save ${destPath}`); - return 500; - } -} - -async function previewOrPublish({path, action}) { - const previewUrl = `https://admin.hlx.page/${action}/${toOrg}/${toRepo}/main${path}`; - const opts = { method: 'POST' }; - const resp = await fetch(previewUrl, opts); - if (!resp.ok){ - console.log(`Posting to ${action} failed. ${action}/${toOrg}/${toRepo}/main${path}`); - await slackNotification( - `Failed ${action}/${toOrg}/${toRepo}/main${path}. Error: ${resp.status} ${resp.statusText}` - ); - } else { - console.log(`Posted to ${action} successfully ${action}/${toOrg}/${toRepo}/main${path}`); - } -} - -const slackNotification = (text) => { - return fetch(process.env.ROLLING_IMPORT_SLACK, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ text }), - }) -}; - -// If an image in metadata starts with new line -// we'll need to remove the new line to prevent losing the reference to the img -// IMPORTANT: This currently not used as we only found this occuring on one page. -// It's still left in to enable in case we find more cases of this. -function safeguardMetadataImages(dom) { - const metadata = dom.window.document.querySelector('.metadata') - if (metadata) { - metadata.querySelectorAll('div').forEach(row => { - const metadataKey = row.querySelector('div:first-child')?.textContent.trim().toLowerCase(); - if (metadataKey === 'image') row.querySelectorAll('br')?.forEach(br => br.remove()); - }); - } - const cardMetadata = dom.window.document.querySelector('.card-metadata') - if (cardMetadata) { - cardMetadata.querySelectorAll('div').forEach(row => { - const metadataKey = row.querySelector('div:first-child')?.textContent.trim().toLowerCase(); - if (metadataKey === 'cardImage') row.querySelectorAll('br')?.forEach(br => br.remove()); - }); - } -} - -async function importUrl(url) { - // Exclude auto publishing files from Sharepoint - if(excludedFiles.some((excludedFile => url.pathname === excludedFile))) { - console.log(`Stopped processing ${url.pathname}`); - return - } - - console.log("Started path: ", url.href); - const [fromRepo, fromOrg] = url.hostname.split('.')[0].split('--').slice(1).slice(-2); - if (!(fromRepo || fromOrg)) { - console.log(liveDomain, url.origin === liveDomain); - if (url.origin !== liveDomain) { - url.status = '403'; - url.error = 'URL is not from AEM.'; - return; - } - } - - url.fromRepo ??= fromRepo; - url.fromOrg ??= fromOrg; - - const { pathname, href } = url; - if (href.endsWith('.xml') || href.endsWith('.html') || href.includes('query-index')) { - url.status = 'error'; - url.error = 'DA does not support XML, HTML, or query index files.'; - return; - } - - - const isExt = EXTS.some((ext) => href.endsWith(`.${ext}`)); - const path = href.endsWith('/') ? `${pathname}index` : pathname; - const srcPath = isExt ? path : `${path}.md`; - url.destPath = isExt ? path : `${path}.html`; - url.editPath = href.endsWith('.json') ? path.replace('.json', '') : path; - - if (isExt) { - url.route = url.destPath.endsWith('json') ? '/sheet' : '/media'; - } else { - url.route = '/edit'; - } - - try { - const resp = await fetch(`${url.origin}${srcPath}`); - console.log("fetched resource from AEM at:", `${url.origin}${srcPath}`) - if (resp.redirected && !(srcPath.endsWith('.mp4') || srcPath.endsWith('.png') || srcPath.endsWith('.jpg'))) { - url.status = 'redir'; - console.log("Skipped importing redirected resource") - return - } - if (!resp.ok && resp.status !== 304) { - url.status = 'error'; - console.log(`Failed Status ${resp.status} /${toOrg}/${toRepo}/main${path}. Error: ${resp.status} ${resp.statusText}`) - await slackNotification( - `Failed Status ${resp.status} /${toOrg}/${toRepo}/main${path}. Error: ${resp.status} ${resp.statusText}` - ); - return - } - let content = isExt ? await resp.blob() : await resp.text(); - if (!isExt) { - const dom = mdToDocDom(content) - // safeguardMetadataImages(dom); - const aemHtml = docDomToAemHtml(dom) - // Difference to nexter: "findFragments" alternative, since we always import on publish - // we always import fragments when they are published, we don't need to discover them here - // nexter uses "findFragments" to discover mp4/svg/pdf files though - await importMedia(url, aemHtml) - let html = replaceHtml(aemHtml, url.fromOrg, url.fromRepo); - content = new Blob([html], { type: 'text/html' }); - } - url.status = await saveAllToDa(url, content); - console.log("imported resource to DA " + url.daHref); - await previewOrPublish({path: pathname, action: 'preview'}); - await previewOrPublish({path: pathname, action: 'live'}); - console.log(`Resource: https://main--${toRepo}--${toOrg}.aem.live${url.pathname}`); - } catch (e) { - await slackNotification(`Resource: https://main--${toRepo}--${toOrg}.aem.live${url.pathname} failed to publish. Error: ${e.message}`); - console.log("Failed to import resource to DA " + toOrg + "/" + toRepo + " | destination: " + url.pathname + " | error: " + e.message); - if (!url.status) url.status = 'error'; - throw e; - } -} - -importUrl(new URL(importFrom + process.env.AEM_PATH.replace(".md", ""))); diff --git a/.github/workflows/import/package-lock.json b/.github/workflows/import/package-lock.json deleted file mode 100644 index 49fc22c8..00000000 --- a/.github/workflows/import/package-lock.json +++ /dev/null @@ -1,1653 +0,0 @@ -{ - "name": "da-importer", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "da-importer", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@adobe/mdast-util-gridtables": "^4.0.9", - "@adobe/remark-gridtables": "^3.0.8", - "form-data": "^4.0.1", - "hast-util-raw": "^9.1.0", - "hast-util-to-html": "^9.0.4", - "jsdom": "^26.0.0", - "mdast-util-to-hast": "^13.2.0", - "node-fetch": "^3.3.2", - "remark-parse": "^11.0.0", - "unified": "^11.0.5" - } - }, - "node_modules/@adobe/mdast-util-gridtables": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@adobe/mdast-util-gridtables/-/mdast-util-gridtables-4.0.9.tgz", - "integrity": "sha512-E2v6jNedNprV84WiQnvJ1D9FVKVqRpm4nLUYJyH6VqiRKz/DGCuzpQblFN8T58/Y1ngPG0d4Qq713s7xfgGF4A==", - "dependencies": { - "@adobe/micromark-extension-gridtables": "^2.0.0", - "mdast-util-from-markdown": "2.0.2", - "mdast-util-to-hast": "13.2.0", - "mdast-util-to-markdown": "2.1.2", - "unist-util-visit": "5.0.0" - } - }, - "node_modules/@adobe/micromark-extension-gridtables": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@adobe/micromark-extension-gridtables/-/micromark-extension-gridtables-2.0.2.tgz", - "integrity": "sha512-3JCTeBSwh/orfBvIMwhoQXzSYYScKnfzQAcVquKqgs0isE++ZcKCVrCrYqwUYIKM0AvTr99Zn1boxdGsC2m1eA==", - "dependencies": { - "micromark": "^4.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/@adobe/remark-gridtables": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@adobe/remark-gridtables/-/remark-gridtables-3.0.8.tgz", - "integrity": "sha512-8ralSRizEPWpy4vKVNmNn8VaTeXaUAhqLoxctqUldz9qzfXrs8NAkIM1HmNRhccn6PhPEkqoJE2LUDN0Lr025g==", - "dependencies": { - "@adobe/mdast-util-gridtables": "4.0.8", - "@adobe/micromark-extension-gridtables": "2.0.2" - } - }, - "node_modules/@adobe/remark-gridtables/node_modules/@adobe/mdast-util-gridtables": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@adobe/mdast-util-gridtables/-/mdast-util-gridtables-4.0.8.tgz", - "integrity": "sha512-cUNlMk8sAUqV8qSDTYPDgzexbAsopC9o4nfUEn0sN+REIu81q65lGOm+psftZzH5/HVgLMu4dODMf6ddjKmOig==", - "dependencies": { - "@adobe/micromark-extension-gridtables": "^2.0.0", - "mdast-util-from-markdown": "2.0.2", - "mdast-util-to-hast": "13.2.0", - "mdast-util-to-markdown": "2.1.2", - "unist-util-visit": "5.0.0" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", - "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", - "dependencies": { - "@csstools/css-calc": "^2.1.1", - "@csstools/css-color-parser": "^3.0.7", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", - "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", - "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", - "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^5.0.1", - "@csstools/css-calc": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", - "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==" - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cssstyle": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", - "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", - "dependencies": { - "@asamuzakjp/css-color": "^2.8.2", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" - }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", - "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^6.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", - "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", - "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/jsdom": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz", - "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.1", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", - "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", - "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", - "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", - "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/nwsapi": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", - "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==" - }, - "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/tldts": { - "version": "6.1.73", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.73.tgz", - "integrity": "sha512-/h4bVmuEMm57c2uCiAf1Q9mlQk7cA22m+1Bu0K92vUUtTVT9D4mOFWD9r4WQuTULcG9eeZtNKhLl0Il1LdKGog==", - "dependencies": { - "tldts-core": "^6.1.73" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.73", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.73.tgz", - "integrity": "sha512-k1g5eX87vxu3g//6XMn62y4qjayu4cYby/PF7Ksnh4F4uUK1Z1ze/mJ4a+y5OjdJ+cXRp+YTInZhH+FGdUWy1w==" - }, - "node_modules/tough-cookie": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.0.tgz", - "integrity": "sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", - "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/.github/workflows/import/package.json b/.github/workflows/import/package.json deleted file mode 100644 index 918d7e8e..00000000 --- a/.github/workflows/import/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "da-importer", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@adobe/mdast-util-gridtables": "^4.0.9", - "@adobe/remark-gridtables": "^3.0.8", - "form-data": "^4.0.1", - "hast-util-raw": "^9.1.0", - "hast-util-to-html": "^9.0.4", - "jsdom": "^26.0.0", - "mdast-util-to-hast": "^13.2.0", - "node-fetch": "^3.3.2", - "remark-parse": "^11.0.0", - "unified": "^11.0.5" - }, - "type": "module" -} diff --git a/.github/workflows/listen-to-publish-events.yaml b/.github/workflows/listen-to-publish-events.yaml deleted file mode 100644 index a5c9bdaf..00000000 --- a/.github/workflows/listen-to-publish-events.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: DA Rolling Import - -on: - repository_dispatch: - types: [resource-published, resources-published] - -env: - AEM_PATH: ${{ github.event.client_payload.path }} - CLIENT_ID: ${{ secrets.CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - CODE: ${{ secrets.CODE }} - GRANT_TYPE: ${{ secrets.GRANT_TYPE }} - IMS_URL: ${{ secrets.IMS_URL }} - ROLLING_IMPORT_SLACK: ${{ secrets.ROLLING_IMPORT_SLACK }} - -permissions: - contents: read - -jobs: - run-script: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - - name: Set up Node.js - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e - with: - node-version: 18 - - - name: Install dependencies - run: cd ./.github/workflows/import/ && npm install - - - name: Run script - run: node ./.github/workflows/import/index.js diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 123c99de..0fe822c3 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -22,15 +22,13 @@ jobs: uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: ${{ matrix.node-version }} - - - name: Install XVFB - run: sudo apt-get install xvfb + cache: 'npm' - name: Install dependencies run: npm install - name: Run the tests - run: xvfb-run -a npm test + run: npm test - name: Upload coverage to Codecov uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 diff --git a/.kodiak/config.yaml b/.kodiak/config.yaml index f24c98dc..57ab16dd 100644 --- a/.kodiak/config.yaml +++ b/.kodiak/config.yaml @@ -1,28 +1,32 @@ version: 1.0 +#### If you have questions about this, please contact #kodiak-security on Slack. #### +# Every repository requires a unique Service Now ID number. You need to create a new service using +# the Service Registry Portal https://adobe.service-now.com/service_registry_portal.do#/search +# You should NOT leave this default! snow: - - id: 546046 # Milo Bacom (business.adobe.com) + - id: 546046 # Milo Bacom (business.adobe.com) https://adobe.service-now.com/service_registry_portal.do#/service/546046 notifications: jira: default: - project: MWPW # Mandatory + project: MWPW # Project queue in which the security ticket will be created. Can be any valid JIRA project. filters: include: - risk_rating: R3 + risk_rating: R3 # Please do not change this unless instructed to do so. fields: assignee: name: slavin - customfield_11800: MWPW-164516 #epic link - customfield_12900: + customfield_11800: MWPW-164516 # Jira epic security tickets will be assigned to. + customfield_12900: # Jira Team security tickets will be assigned to. value: Magma - watchers: + watchers: # Everyone who is an admin on your repository should be a watcher. - casalino - bmarshal - methomas - labels: + labels: # You can add additional labels to your tickets here. Do not delete/change the first three. - "OriginatingProcess=Kodiak" - "security" - "kodiak-ticket" - components: + components: # Please do not change this. - name: "DevOps Security" diff --git a/README.md b/README.md index c8a209d5..043f7ec7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bacom +# Adobe for Business The Franklin based project for business.adobe.com. Based off of milo-college. ## Contributing diff --git a/blocks/tree-view/tree-view.js b/blocks/tree-view/tree-view.js index 9796b129..2f093585 100644 --- a/blocks/tree-view/tree-view.js +++ b/blocks/tree-view/tree-view.js @@ -1,7 +1,6 @@ import { LIBS } from '../../scripts/scripts.js'; -const BACOM_HOSTS = ['localhost', '--bacom--adobecom.hlx.page', '--bacom--adobecom.hlx.live', - '--bacom--adobecom.aem.page', '--bacom--adobecom.aem.live', 'business.adobe.com']; +const BACOM_HOSTS = ['localhost', '--da-bacom--adobecom.aem.page', '--da-bacom--adobecom.aem.live', 'business.adobe.com']; export const isCurrentPage = (link) => { const currentPath = window.location.pathname.replace('.html', ''); @@ -110,6 +109,18 @@ const init = async (el) => { const topList = el.querySelector('ul'); if (!topList) return; + // TODO: Remove after bugfix PR adobe/helix-html2md#556 is merged + const listItems = topList.querySelectorAll('li'); + [...listItems].forEach((liEl) => { + const pElements = liEl.querySelectorAll('p'); + pElements.forEach((pElement) => { + while (pElement?.firstChild) { + pElement.parentNode.insertBefore(pElement.firstChild, pElement); + } + pElement.remove(); + }); + }); + // END TODO: Remove after bugfix PR adobe/helix-html2md#556 is merged const { createTag } = await import(`${LIBS}/utils/utils.js`); const subLists = topList.querySelectorAll('ul'); @@ -167,7 +178,7 @@ const init = async (el) => { topListItem.setAttribute('aria-haspopup', 'menu'); topListItem.setAttribute('aria-expanded', false); - const label = topListItem.textContent.split('\n')[0]; + const label = topListItem.textContent.split('\n')?.find((t) => t.trim() !== '') || ''; const id = label.trim().replaceAll(' ', '-'); const button = createTag('button', { id }, label); const subList = topListItem.querySelector('ul'); diff --git a/fstab.yaml b/fstab.yaml deleted file mode 100644 index be97d182..00000000 --- a/fstab.yaml +++ /dev/null @@ -1,2 +0,0 @@ -mountpoints: - /: https://adobe.sharepoint.com/:f:/r/sites/adobecom/Shared%20Documents/bacom diff --git a/head.html b/head.html index 930afa88..45b34a21 100644 --- a/head.html +++ b/head.html @@ -3,9 +3,10 @@ + + + + + + + diff --git a/test/tools/generator/generator.test.js b/test/tools/generator/generator.test.js new file mode 100644 index 00000000..1ee3468d --- /dev/null +++ b/test/tools/generator/generator.test.js @@ -0,0 +1,59 @@ +/* eslint-disable import/no-unresolved */ +import { readFile } from '@web/test-runner-commands'; +import { expect } from '@esm-bundle/chai'; +import { applyTemplateFields } from '../../../tools/generator/generator.js'; +import { LIBS } from '../../../scripts/scripts.js'; + +const { loadArea } = await import(`${LIBS}/utils/utils.js`); + +const gated = await readFile({ path: './mocks/template-gated.html' }); +const ungated = await readFile({ path: './mocks/template-ungated.html' }); + +const data = { + contentType: 'Webinar', + marqueeHeadline: 'Join our upcoming webinar', + pdfAsset: '/path/to/pdf', + sectionStyle: 'light', + bodyDescription: 'Adobe is proud to be recognized as a Leader', + bodyBackground: 'white', + cardTitle: 'Webinar Title', + cardLink: '/path/to/card', + allResourcesLink: '/path/to/all/resources', + cardDate: '2025-05-21', + cardImage: 'http://localhost:3000/resources/reports/media_16c066517a3696208ca4c721dc7d5855e6c4f0d98.png', + cardDescription: 'Description of the webinar', + caasContentType: 'Webinar', + caasPrimaryProduct: 'Product A', + marketoDataUrl: 'https://milo.adobe.com/tools/marketo#', + experienceFragment: 'http://localhost:3000/fragments/resources/cards/thank-you-collections/advertising', + marqueeImage: 'http://localhost:3000/media_1df026981984b08eea7a3e56f0ed6014bd00bde08.png', + formDescription: 'Please fill out the form to register for the webinar.', + formSuccessType: 'thank-you', + formSuccessSection: 'form-success', + formSuccessContent: 'You will receive a confirmation email shortly.', +}; + +describe('Generator', () => { + beforeEach(() => { + document.body.innerHTML = ''; + document.head.innerHTML = ''; + }); + + it('applies ungated template fields correctly', async () => { + document.body.innerHTML = ungated; + const result = applyTemplateFields(ungated, data); + document.body.innerHTML = result; + await loadArea(); + const remainingFields = result.match(/{{[^}]+}}/g) || []; + expect(remainingFields).to.deep.equal([]); + }); + + it.skip('applies gated template fields correctly', async () => { + document.body.innerHTML = gated; + const result = applyTemplateFields(gated, data); + document.body.innerHTML = result; + await loadArea(); + const remainingFields = result.match(/{{[^}]+}}/g) || []; + expect(remainingFields).to.deep.equal([]); + }); +}); diff --git a/test/tools/generator/image-dropzone.test.html b/test/tools/generator/image-dropzone.test.html new file mode 100644 index 00000000..ec2b4042 --- /dev/null +++ b/test/tools/generator/image-dropzone.test.html @@ -0,0 +1,299 @@ + + + + + + + + + + + + diff --git a/test/tools/generator/landing-page.test.html b/test/tools/generator/landing-page.test.html new file mode 100644 index 00000000..95a7039f --- /dev/null +++ b/test/tools/generator/landing-page.test.html @@ -0,0 +1,156 @@ + + + + + + + + + + +
+ + + + diff --git a/test/tools/generator/mocks/da-fetch.js b/test/tools/generator/mocks/da-fetch.js new file mode 100644 index 00000000..7130206a --- /dev/null +++ b/test/tools/generator/mocks/da-fetch.js @@ -0,0 +1,69 @@ +import sinon from 'sinon'; + +export const daFetch = sinon.stub(); + +daFetch.callsFake(async (url, options = {}) => { + const method = options.method || 'GET'; + + if (method === 'GET') { + if (url.includes('.html')) { + return { + ok: true, + text: async () => ` + + Mock Document + +
+
+ Mock Image +

Mock Headline

+

Mock description

+
+
+

Mock content

+
+
+ + + `, + json: async () => ({ success: true }), + }; + } + + return { + ok: true, + text: async () => 'Mock response', + json: async () => ({ success: true }), + }; + } + + if (method === 'PUT') { + return { + ok: true, + json: async () => ({ + source: { + contentUrl: url.includes('image') || url.includes('.jpg') || url.includes('.png') + ? '/test/tools/generator/mocks/mock-image.jpg' + : '/test/tools/generator/mocks/mock-document.html', + }, + }), + }; + } + + return { + ok: false, + status: 404, + statusText: 'Not Found', + text: async () => 'Not Found', + json: async () => ({ error: 'Not Found' }), + }; +}); + +export const replaceHtml = (html, org, repo) => html.replace(/href="([^"]*?)"/g, (match, url) => { + if (url.startsWith('/')) { + return `href="https://main--${repo}--${org}.hlx.page${url}"`; + } + return match; +}); + +export default { daFetch, replaceHtml }; diff --git a/test/tools/generator/mocks/da-sdk.js b/test/tools/generator/mocks/da-sdk.js new file mode 100644 index 00000000..b26b760c --- /dev/null +++ b/test/tools/generator/mocks/da-sdk.js @@ -0,0 +1,14 @@ +const mockContext = { + repo: 'adobecom', + owner: 'test', + ref: 'main', +}; + +const mockToken = 'mock-token'; + +const mockSDK = { + context: mockContext, + token: mockToken, +}; + +export default Promise.resolve(mockSDK); diff --git a/test/tools/generator/mocks/mock-image.jpg b/test/tools/generator/mocks/mock-image.jpg new file mode 100644 index 00000000..6bfc93be Binary files /dev/null and b/test/tools/generator/mocks/mock-image.jpg differ diff --git a/test/tools/generator/mocks/template-gated.html b/test/tools/generator/mocks/template-gated.html new file mode 100644 index 00000000..535e288c --- /dev/null +++ b/test/tools/generator/mocks/template-gated.html @@ -0,0 +1,111 @@ +
+
+
+
#F5F5F5
+
+
+
+

{{content-type}}

+

{{marquee-headline}}

+
+
+ + + + + + +
+
+
+
+
+

{{body-description}}

+
+
+ +
+
+
Description
+
{{form-description}}
+
+
+
Success Type
+
{{form-success-type}}
+
+
+
Success Section
+
{{form-success-section}}
+
+
+
Success Content
+
{{form-success-content}}
+
+
+ +
+
+

{{pdf-asset}}

+ +
+
+
+
+
+

{{card-title}}

+
+
+
+

{{experience-fragment}}

+ + +
diff --git a/test/tools/generator/mocks/template-ungated.html b/test/tools/generator/mocks/template-ungated.html new file mode 100644 index 00000000..eb773a9b --- /dev/null +++ b/test/tools/generator/mocks/template-ungated.html @@ -0,0 +1,104 @@ +
+
+
+
+

{{content-type}}

+

{{marquee-headline}}

+
+
+
+
+
+

{{pdf-asset}}

+ +
+
+
+
+

{{card-title}}

+
+
+
+

{{experience-fragment}}

+ +
+
+ + +
diff --git a/test/tools/generator/toast.test.html b/test/tools/generator/toast.test.html new file mode 100644 index 00000000..d2282703 --- /dev/null +++ b/test/tools/generator/toast.test.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + diff --git a/test/tools/locale-nav/locale-selector.test.js b/test/tools/locale-nav/locale-selector.test.js new file mode 100644 index 00000000..8e57efa2 --- /dev/null +++ b/test/tools/locale-nav/locale-selector.test.js @@ -0,0 +1,141 @@ +/* eslint-disable import/no-unresolved */ +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import '../../../tools/locale-nav/locale-selector.js'; + +const locales = [ + { + code: 'uk', + path: '/uk/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/uk/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/uk/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/uk/blog/', + }, + { + code: 'au', + path: '/au/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/au/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/au/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/au/blog/', + }, + { + code: 'de', + path: '/de/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/de/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/de/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/de/blog/', + }, + { + code: 'fr', + path: '/fr/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/fr/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/fr/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/fr/blog/', + }, + { + code: 'kr', + path: '/kr/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/kr/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/kr/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/kr/blog/', + }, + { + code: 'ja', + path: '/ja/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/ja/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/ja/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/ja/blog/', + }, + { + code: 'en', + path: '/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/blog/', + }, + { + code: 'langstore/en', + path: '/langstore/en/blog/index', + edit: 'https://da.live/edit#/adobecom/da-bacom-blog/langstore/en/blog/index', + preview: 'https://main--da-bacom-blog--adobecom.aem.page/langstore/en/blog/', + live: 'https://main--da-bacom-blog--adobecom.aem.live/langstore/en/blog/', + }, +]; + +const status = { + '/uk/blog/index': { preview: 200, live: 200 }, + '/au/blog/index': { preview: 200, live: 404 }, + '/de/blog/index': { preview: 200, live: 404 }, + '/fr/blog/index': { preview: 200, live: 404 }, + '/kr/blog/index': { preview: 200, live: 404 }, + '/ja/blog/index': { preview: 200, live: 404 }, + '/blog/index': { preview: 200, live: 200 }, + '/langstore/en/blog/index': {}, +}; + +const ogLana = window.lana; + +const delay = (milliseconds) => new Promise((resolve) => { setTimeout(resolve, milliseconds); }); + +const init = (localeCode = '') => { + const localeNav = document.createElement('da-locale-selector'); + const altLocales = locales.filter((locale) => locale.code !== localeCode); + const currLocale = locales.find((locale) => locale.code === localeCode); + + localeNav.altLocales = altLocales; + localeNav.currLocale = currLocale; + localeNav.status = status; + document.body.append(localeNav); + + return localeNav; +}; + +describe('Locale Selector', () => { + beforeEach(async () => { + document.body.innerHTML = ''; + window.lana = { log: sinon.spy() }; + }); + + afterEach(() => { + window.lana = ogLana; + }); + + it('render the locale selector', async () => { + const localeSelector = init('en'); + await delay(100); + + expect(localeSelector).to.exist; + expect(localeSelector.shadowRoot).to.exist; + + const localeSelectorEl = localeSelector.shadowRoot.querySelector('.locale-selector'); + expect(localeSelectorEl).to.exist; + + const currentLocale = localeSelectorEl.querySelector('.current .detail'); + expect(currentLocale).to.exist; + expect(currentLocale.querySelector('span').textContent).to.equal('en'); + expect(currentLocale.querySelector('.edit').href).to.equal('https://da.live/edit#/adobecom/da-bacom-blog/blog/index'); + expect(currentLocale.querySelector('.preview').href).to.equal('https://main--da-bacom-blog--adobecom.aem.page/blog/'); + expect(currentLocale.querySelector('.live').href).to.equal('https://main--da-bacom-blog--adobecom.aem.live/blog/'); + }); + + it('handle search', async () => { + const localeSelector = init('uk'); + await delay(100); + + const localeSelectorEl = localeSelector.shadowRoot.querySelector('.locale-selector'); + expect(localeSelectorEl).to.exist; + + const searchInput = localeSelectorEl.querySelector('.locale-search'); + searchInput.value = 'en'; + searchInput.dispatchEvent(new Event('keyup')); + + const localeElements = localeSelectorEl.querySelectorAll('.locales li'); + localeElements.forEach((element) => { + if (element.textContent.includes('en')) { + expect(element.style.display).to.equal(''); + } else { + expect(element.style.display).to.equal('none'); + } + }); + }); +}); diff --git a/test/tools/tags/tag-browser.test.js b/test/tools/tags/tag-browser.test.js new file mode 100644 index 00000000..93b86d3a --- /dev/null +++ b/test/tools/tags/tag-browser.test.js @@ -0,0 +1,121 @@ +/* eslint-disable import/no-unresolved */ +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import '../../../tools/tags/tag-browser.js'; + +const tags = [ + { + path: '/content/cq:tags/audience.1.json', + activeTag: '', + name: 'audience', + title: 'User Guide Audience', + }, + { + path: '/content/cq:tags/authoring.1.json', + activeTag: '', + name: 'authoring', + title: 'Authoring', + }, + { + path: '/content/cq:tags/caas.1.json', + activeTag: '', + name: 'caas', + title: 'CaaS', + }, +]; + +const ogLana = window.lana; + +const delay = (milliseconds) => new Promise((resolve) => { setTimeout(resolve, milliseconds); }); + +const init = () => { + const daTagBrowser = document.createElement('da-tag-browser'); + document.body.append(daTagBrowser); + daTagBrowser.rootTags = tags; + daTagBrowser.actions = { sendText: sinon.spy() }; + daTagBrowser.getTags = async () => tags; + return daTagBrowser; +}; + +describe('Locale Selector', () => { + beforeEach(async () => { + document.body.innerHTML = ''; + window.lana = { log: sinon.spy() }; + }); + + afterEach(() => { + window.lana = ogLana; + }); + + it('render the tag browser', async () => { + const tagBrowser = init(); + await delay(100); + + expect(tagBrowser.shadowRoot.querySelector('.tag-browser')).to.exist; + const groups = tagBrowser.shadowRoot.querySelector('.tag-groups'); + expect(groups).to.exist; + expect(groups.children).to.have.lengthOf(1); + + const firstTitle = groups.firstElementChild.querySelector('.tag-title'); + expect(firstTitle).to.exist; + expect(firstTitle.textContent.trim()).to.equal('User Guide Audience'); + + const firstInsert = groups.firstElementChild.querySelector('.tag-insert'); + expect(firstInsert).to.exist; + }); + + it('expand tag group', async () => { + const tagBrowser = init(); + await delay(100); + + const groups = tagBrowser.shadowRoot.querySelector('.tag-groups'); + const firstTitle = groups.firstElementChild.querySelector('.tag-title'); + firstTitle.click(); + await delay(100); + + expect(firstTitle.classList.contains('active')).to.be.true; + expect(groups.children).to.have.lengthOf(2); + }); + + it('send tag text', async () => { + const tagBrowser = init(); + await delay(100); + + const groups = tagBrowser.shadowRoot.querySelector('.tag-groups'); + const firstInsert = groups.firstElementChild.querySelector('.tag-insert'); + + firstInsert.click(); + expect(tagBrowser.actions.sendText.calledOnce).to.be.true; + expect(tagBrowser.actions.sendText.getCall(0).args[0]).to.equal('audience'); + }); + + it('collapses tag group on back button click', async () => { + const tagBrowser = init(); + await delay(100); + + const groups = tagBrowser.shadowRoot.querySelector('.tag-groups'); + const firstTitle = groups.firstElementChild.querySelector('.tag-title'); + firstTitle.click(); + await delay(100); + + const backButton = tagBrowser.shadowRoot.querySelector('.tag-search button'); + backButton.click(); + await delay(100); + + expect(groups.children).to.have.lengthOf(1); + }); + + it('filters tags based on search input', async () => { + const tagBrowser = init(); + await delay(100); + + const searchBar = tagBrowser.shadowRoot.querySelector('.tag-search input'); + searchBar.value = 'Authoring'; + searchBar.dispatchEvent(new Event('input')); + await delay(100); + + const groups = tagBrowser.shadowRoot.querySelector('.tag-groups'); + expect(groups.children).to.have.lengthOf(1); + expect(groups.firstElementChild.querySelector('.tag-title').textContent.trim()).to.equal('Authoring'); + }); +}); diff --git a/tools/caas-tag-selector/index.html b/tools/caas-tag-selector/index.html new file mode 100644 index 00000000..e2fade1b --- /dev/null +++ b/tools/caas-tag-selector/index.html @@ -0,0 +1,22 @@ + + + + DA App SDK Sample + + + + + + + + + +
+ +
+ + diff --git a/tools/caas-tag-selector/tag-selector.css b/tools/caas-tag-selector/tag-selector.css new file mode 100644 index 00000000..0409c9ba --- /dev/null +++ b/tools/caas-tag-selector/tag-selector.css @@ -0,0 +1,5 @@ +.tag-selector-group { + margin: 20px; + display: flex; + flex-direction: column; +} diff --git a/tools/caas-tag-selector/tag-selector.js b/tools/caas-tag-selector/tag-selector.js new file mode 100644 index 00000000..f5617976 --- /dev/null +++ b/tools/caas-tag-selector/tag-selector.js @@ -0,0 +1,110 @@ +/* eslint-disable no-underscore-dangle, import/no-unresolved */ +import 'https://da.live/nx/public/sl/components.js'; +import { LitElement, html } from 'https://da.live/nx/deps/lit/lit-core.min.js'; +import getStyle from 'https://da.live/nx/utils/styles.js'; +import DA_SDK from 'https://da.live/nx/utils/sdk.js'; +import { + getTags, + getAemRepo, + getRootTags, +} from '../tags/tag-utils.js'; + +const style = await getStyle(import.meta.url); +const { context, token } = await DA_SDK.catch(() => null); +const options = { headers: { Authorization: `Bearer ${token}` } }; +const collectionName = 'dx-tags/dx-caas'; +const jcrTitle = 'jcr:title'; +const caasContentType = 'caas:content-type'; +const caasProducts = 'caas:products'; + +async function processRootTags(opts) { + const aemConfig = await getAemRepo(context, opts).catch(() => null); + const errorEvent = (message) => { + const error = new CustomEvent('caasOptionsError', { details: { message } }); + document.dispatchEvent(error); + }; + + if (!aemConfig || !aemConfig.aemRepo) { + const repoError = 'Error in retrieving AemRepo'; + errorEvent(repoError); + return []; + } + + const namespaces = aemConfig?.namespaces.split(',').map((namespace) => namespace.trim()) || []; + const rootTags = await getRootTags(namespaces, aemConfig, opts); + + if (!rootTags || rootTags.length === 0) { + const rootTagError = 'Error in getting RootTags'; + errorEvent(rootTagError); + } + return rootTags; +} + +async function getTagCollection(root, name, opts) { + let collection = []; + for (const tag of root) { + const currentCollection = await getTags(tag.path, opts); + const firstTag = currentCollection[0]; + if (firstTag && firstTag?.activeTag === name) { + collection = currentCollection; + return collection; + } + } + return collection; +} +class PageGeneratorCaaSTagSelector extends LitElement { + static properties = { + propCollection: { Type: Array }, + _caasPPN: { state: true }, + _contentTypes: { state: true }, + }; + + constructor() { + super(); + this._contentTypes = []; + this._caasPPN = []; + } + + static styles = style; + + async setCollections() { + let currentCollection; + if (this?.propCollection?.length > 0) { + currentCollection = this.propCollection; + } else { + const rootCollections = await processRootTags(options); + currentCollection = await getTagCollection(rootCollections, collectionName, options); + } + const caasContentTypeCollection = currentCollection + .filter((tag) => tag.details[jcrTitle].includes(caasContentType)); + const caasPrimaryProductCollection = currentCollection + .filter((tag) => tag.details[jcrTitle].includes(caasProducts)); + this._contentTypes = caasContentTypeCollection; + this._caasPPN = caasPrimaryProductCollection; + } + + async connectedCallback() { + super.connectedCallback(); + // fetch for collection should be in parent. Prepping for collection passed as props + this.setCollections(); + } + + render() { + return html` +
+ + + + +
+ `; + } +} + +customElements.define('pg-caas-tag-selector', PageGeneratorCaaSTagSelector); diff --git a/tools/generator/da-utils.js b/tools/generator/da-utils.js new file mode 100644 index 00000000..b4c9e1ee --- /dev/null +++ b/tools/generator/da-utils.js @@ -0,0 +1,79 @@ +/* eslint-disable import/no-unresolved */ +import { daFetch, replaceHtml } from 'da-fetch'; +import { DA_ORIGIN } from 'constants'; + +const ORG = 'adobecom'; +const REPO = 'da-bacom'; + +function getDaPath(path) { + return `${DA_ORIGIN}/source/${ORG}/${REPO}${path}`; +} + +export async function getSource(path) { + const daPath = `${getDaPath(path)}.html`; + const opts = { method: 'GET', headers: { accept: '*/*' } }; + + try { + const response = await daFetch(daPath, opts); + if (response.ok) { + const html = await response.text(); + const newParser = new DOMParser(); + const parsedPage = newParser.parseFromString(html, 'text/html'); + + return parsedPage; + } + /* c8 ignore next 5 */ + } catch (error) { + // eslint-disable-next-line no-console + console.log(`Error fetching document ${daPath}`, error); + } + return null; +} + +export async function saveSource(path, document) { + const main = document.querySelector('main'); + const text = main.innerHTML; + const daPath = `${getDaPath(path)}.html`; + const body = replaceHtml(text, ORG, REPO); + const blob = new Blob([body], { type: 'text/html' }); + const formData = new FormData(); + const opts = { method: 'PUT', body: formData }; + + formData.append('data', blob); + try { + const daResp = await daFetch(daPath, opts); + if (daResp.ok) { + const json = await daResp.json(); + + return json?.source?.contentUrl; + } + /* c8 ignore next 5 */ + } catch (error) { + // eslint-disable-next-line no-console + console.log(`Couldn't save ${daPath}`, error); + } + return null; +} + +export async function saveImage(path, file) { + const daPath = getDaPath(`${path}${file.name}`); + const formData = new FormData(); + const opts = { method: 'PUT', body: formData }; + + formData.append('data', file); + try { + const resp = await daFetch(daPath, opts); + + if (resp.ok) { + const json = await resp.json(); + return json?.source?.contentUrl; + } + /* c8 ignore next 7 */ + return null; + } catch (error) { + // eslint-disable-next-line no-console + console.log(`Couldn't save ${path}${file.name}`, error); + + return null; + } +} diff --git a/tools/generator/generator.js b/tools/generator/generator.js new file mode 100644 index 00000000..43cea565 --- /dev/null +++ b/tools/generator/generator.js @@ -0,0 +1,84 @@ +import { LIBS } from '../../scripts/scripts.js'; + +const { utf8ToB64 } = await import(`${LIBS}/utils/utils.js`); + +const DA_ORIGIN = 'https://admin.da.live'; +const ORG = 'adobecom'; +const REPO = 'da-bacom'; + +// Template questions +// Should we have Asset H2? +// Should we have Form Title and Description? +const templatedFields = { + contentType: '{{content-type}}', + gated: '{{gated}}', + formTemplate: '{{form-template}}', + formDescription: '{{form-description}}', + formSuccessType: '{{form-success-type}}', + formSuccessSection: '{{form-success-section}}', + formSuccessContent: '{{form-success-content}}', + campaignId: '{{campaign-id}}', + poi: '{{poi}}', + marqueeEyebrow: '{{marquee-eyebrow}}', + marqueeHeadline: '{{marquee-headline}}', + marqueeDescription: '{{marquee-description}}', + marqueeImage: '{{marquee-image}}', + bodyDescription: '{{body-description}}', + bodyImage: '{{body-image}}', + cardTitle: '{{card-title}}', + cardDescription: '{{card-description}}', + cardImage: '{{card-image}}', + cardDate: '{{card-date}}', + caasContentType: '{{caas-content-type}}', + caasPrimaryProduct: '{{caas-primary-product}}', + primaryProductName: '{{primary-product-name}}', + seoMetadataTitle: '{{seo-metadata-title}}', + seoMetadataDescription: '{{seo-metadata-description}}', + experienceFragment: '{{experience-fragment}}', + assetDelivery: '{{asset-delivery}}', + pdfAsset: '{{pdf-asset}}', + marketoDataUrl: '{{marketo-data-url}}', +}; + +// TODO: Use default URL or generated URL +export function marketoUrl(state) { + const url = 'https://milo.adobe.com/tools/marketo'; + return `${url}#${utf8ToB64(JSON.stringify(state))}`; +} + +async function fetchTemplate(daPath) { + const res = await fetch(daPath); + if (!res.ok) throw new Error(`Failed to fetch template: ${res.statusText}`); + return res.text(); +} + +export function applyTemplateFields(templateString, data) { + return Object.entries(templatedFields).reduce( + (text, [key, placeholder]) => { + if (!data[key]) return text; + return text.replaceAll(placeholder, data[key]); + }, + templateString, + ); +} + +async function uploadTemplatedText(daPath, templatedText) { + const formData = new FormData(); + const blob = new Blob([templatedText], { type: 'text/html' }); + formData.set('data', blob); + const updateRes = await fetch(daPath, { method: 'POST', body: formData }); + if (!updateRes.ok) throw new Error(`Failed to update template: ${updateRes.statusText}`); +} + +export async function template(path, data) { + const daPath = `${DA_ORIGIN}/source/${ORG}/${REPO}${path}`; + const text = await fetchTemplate(daPath); + const templatedText = applyTemplateFields(text, data); + await uploadTemplatedText(daPath, templatedText); +} + +export async function replaceTemplate(data) { + const templatePaths = ['/index.html', '/nav.html', '/footer.html']; + + await Promise.all(templatePaths.map((path) => template(path, data))); +} diff --git a/tools/generator/image-dropzone/delete.svg b/tools/generator/image-dropzone/delete.svg new file mode 100644 index 00000000..9adcd9d8 --- /dev/null +++ b/tools/generator/image-dropzone/delete.svg @@ -0,0 +1,11 @@ + + + + + S Delete 18 N + + \ No newline at end of file diff --git a/tools/generator/image-dropzone/image-add.svg b/tools/generator/image-dropzone/image-add.svg new file mode 100644 index 00000000..d99594c2 --- /dev/null +++ b/tools/generator/image-dropzone/image-add.svg @@ -0,0 +1,13 @@ + + + + + S ImageAdd 18 N + + + + diff --git a/tools/generator/image-dropzone/image-dropzone.css b/tools/generator/image-dropzone/image-dropzone.css new file mode 100644 index 00000000..d63c22ea --- /dev/null +++ b/tools/generator/image-dropzone/image-dropzone.css @@ -0,0 +1,88 @@ +.img-file-input-wrapper { + border: 2px dashed #B6B6B6; + border-radius: 8px; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + align-items: center; +} + +.solid-border { + border: 2px solid #B6B6B6; +} + +.img-file-input-wrapper .preview-wrapper { + width: 100%; + height: 100%; + position: relative; + z-index: 1; +} + +.img-file-input-wrapper .preview-wrapper .icon-delete { + position: absolute; + top: 8px; + right: 8px; + cursor: pointer; + filter: drop-shadow(1px 1px 1px white); +} + +.img-file-input-wrapper .preview-img-placeholder { + height: 100%; + overflow: hidden; + border-radius: 6px; +} + +.img-file-input-wrapper .preview-img-placeholder img { + height: 100%; + width: 100%; + object-fit: cover; + display: block; +} + +.img-file-input-wrapper label { + display: flex; + color: rgb(80 80 80); + margin-bottom: 4px; + border-radius: 8px; + align-items: center; + flex-direction: column; + box-sizing: border-box; + padding: 24px; + height: 100%; + width: 100%; +} + +.img-file-input-wrapper label:hover { + background-color: var(--color-gray-100); +} + +.img-file-input-wrapper input.img-file-input { + display: none; +} + +.img-file-input-wrapper label img.icon { + width: 40px; + opacity: 0.5; + margin-bottom: 24px; +} + +.img-file-input-wrapper label p { + margin: 0; + font-size: var(--type-body-xs-size); +} + +.img-file-input-wrapper .hidden { + display: none; +} + +@media (min-width: 900px) { + .image-dropzones { + grid-template-columns: 1fr 1fr 1fr; + } + + .img-upload-component.hero .image-dropzones { + grid-template-columns: 2fr 1fr; + } +} diff --git a/tools/generator/image-dropzone/image-dropzone.js b/tools/generator/image-dropzone/image-dropzone.js new file mode 100644 index 00000000..7882cc5e --- /dev/null +++ b/tools/generator/image-dropzone/image-dropzone.js @@ -0,0 +1,172 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable class-methods-use-this */ +import { LitElement, html } from 'da-lit'; +import getStyle from 'styles'; + +const MAX_FILE_SIZE = 26214400; // 25MB + +const style = await getStyle(import.meta.url.split('?')[0]); + +async function isImageTypeValid(file) { + const validTypes = ['jpeg', 'jpg', 'png', 'svg']; + let currentFileType = ''; + + const blob = file.slice(0, 128); + + const arrayBuffer = await blob.arrayBuffer(); + const bytes = new Uint8Array(arrayBuffer); + + const signatures = { + jpeg: [0xFF, 0xD8, 0xFF], + png: [0x89, 0x50, 0x4E, 0x47], + }; + + if (signatures.jpeg.every((byte, i) => byte === bytes[i])) { + const extension = file.name.split('.').pop().toLowerCase(); + /* c8 ignore next 7 */ + if (extension === 'jpg' || extension === 'jpeg') { + currentFileType = extension; + } else { + currentFileType = 'jpeg'; + } + } + + if (signatures.png.every((byte, i) => byte === bytes[i])) { + currentFileType = 'png'; + } + + const text = await blob.text(); + + if (text.trim().startsWith(' {}; + this.handleDelete = this.handleDelete || null; + } + + cleanupFile() { + if (this.file?.url && this.file.url.startsWith('blob:')) { + URL.revokeObjectURL(this.file.url); + this.file.url = null; + } + } + + disconnectedCallback() { + this.cleanupFile(); + super.disconnectedCallback(); + } + + async setFile(files) { + const [file] = files; + + if (!isImageSizeValid(file, MAX_FILE_SIZE)) { + this.dispatchEvent(new CustomEvent('show-toast', { + detail: { type: 'error', message: 'File size should be less than 25MB', timeout: 0 }, + bubbles: true, + composed: true, + })); + return; + } + + const isValid = await isImageTypeValid(file); + if (isValid) { + this.cleanupFile(); + + this.file = file; + this.file.url = URL.createObjectURL(file); + this.requestUpdate(); + } else { + this.dispatchEvent(new CustomEvent('show-toast', { + detail: { type: 'error', message: 'Invalid file type. The image file should be in one of the following format: .jpeg, .jpg, .png, .svg', timeout: 0 }, + bubbles: true, + composed: true, + })); + } + } + + getFile() { + return this.file; + } + + async handleImageDrop(e) { + e.preventDefault(); + e.stopPropagation(); + const { files } = e.dataTransfer; + + if (files.length > 0) { + await this.setFile(files); + this.handleImage(); + } + if (this.file) this.dispatchEvent(new CustomEvent('image-change', { detail: { file: this.file } })); + } + + async onImageChange(e) { + const { files } = e.currentTarget; + + if (files.length > 0) { + await this.setFile(files); + this.handleImage(); + } + if (this.file) this.dispatchEvent(new CustomEvent('image-change', { detail: { file: this.file } })); + } + + handleDragover(e) { + e.preventDefault(); + e.stopPropagation(); + + e.currentTarget.classList.add('dragover'); + } + + handleDragleave(e) { + e.preventDefault(); + e.stopPropagation(); + + e.currentTarget.classList.remove('dragover'); + } + + deleteImage() { + this.cleanupFile(); + this.file = null; + + this.dispatchEvent(new CustomEvent('image-change', { detail: { file: this.file } })); + } + + render() { + return this.file?.url ? html` +
+
+
+ preview image +
+ delete icon +
+
` + : html` +
+ +
`; + } +} diff --git a/tools/generator/landing-page.css b/tools/generator/landing-page.css new file mode 100644 index 00000000..0430c58f --- /dev/null +++ b/tools/generator/landing-page.css @@ -0,0 +1,55 @@ +:host { + display: block; + max-width: var(--grid-container-width); + margin: var(--spacing-800) auto var(--spacing-800) auto; +} + +h1 { + font-size: var(--type-heading-xxl-size); + line-height: var(--spectrum-line-height-100); + margin: 0 0 6px; +} + +h2 { + margin: 0 0 8px; +} + +p { + font-size: var(--s2-font-size-200); + line-height: var(--type-body-m-lh); + margin: 0 0 24px; +} + +.form-row { + margin-bottom: 32px; +} + +.submit-row { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 12px; +} + +.image-dropzone-container { + position: relative; +} + +.image-dropzone-container label { + font-size: var(--s2-body-xs-size); + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #505050; +} + +.dropzone-wrapper { + position: relative; + min-height: 120px; +} + +.dropzone-wrapper image-dropzone { + width: 100%; + height: 120px; + display: block; + transition: all 0.3s ease; +} diff --git a/tools/generator/landing-page.html b/tools/generator/landing-page.html new file mode 100644 index 00000000..ecce13f2 --- /dev/null +++ b/tools/generator/landing-page.html @@ -0,0 +1,30 @@ + + + + + Campaign Landing Page Generator + + + + + + + + + +
+
+ + + diff --git a/tools/generator/landing-page.js b/tools/generator/landing-page.js new file mode 100644 index 00000000..ada4e65a --- /dev/null +++ b/tools/generator/landing-page.js @@ -0,0 +1,266 @@ +/* eslint-disable class-methods-use-this */ +/* eslint-disable import/no-unresolved */ +import 'components'; +import getStyle from 'styles'; +import DA_SDK from 'da-sdk'; +import { LitElement, html } from 'da-lit'; +import ImageDropzone from './image-dropzone/image-dropzone.js'; +import ToastMessage, { createToast } from './toast/toast.js'; +import { getSource, saveSource, saveImage } from './da-utils.js'; + +const style = await getStyle(import.meta.url.split('?')[0]); + +const SDK_TIMEOUT = 3000; +const TOAST_TIMEOUT = 5000; +const DOCUMENT_NAME = 'generator'; +const DOCUMENT_PATH = `/drafts/bmarshal/${DOCUMENT_NAME}`; +const MEDIA_PATH = `/drafts/bmarshal/.${DOCUMENT_NAME}/`; +const EDIT_DOCUMENT_URL = `https://da.live/edit#/adobecom/da-bacom${DOCUMENT_PATH}`; + +// Data +// TODO: Fetch POI options from Marketo Configurator options +// TODO: Fetch Tags from AEM for CaaS Content Type & Primary Product Name + +// Lists +// TODO: Get Eyebrow copy +// TODO: Get Fragment list + +// Features +// TODO: If select "ungated" don't show show form template, campaign template, POI +// TODO: Page generator will generate URL based on the content type and marquee title + +function withTimeout(promise, ms) { + return Promise.race([promise, new Promise((_, reject) => { setTimeout(() => reject(new Error('timeout')), ms); })]); +} + +class LandingPageForm extends LitElement { + static properties = { + data: { state: true }, + marqueeImage: { state: true }, + }; + + static styles = style; + + constructor() { + super(); + this.data = {}; + this.marqueeImage = null; + } + + updateMarqueeImage() { + const marquee = this.data.document?.querySelector('.marquee'); + const marqueeImage = marquee?.querySelector('img'); + if (marqueeImage?.src) { + this.marqueeImage = { + url: marqueeImage.src, + name: marqueeImage.alt || 'Marquee Image', + }; + } + } + + handleToast(e) { + e.stopPropagation(); + e.preventDefault(); + const detail = e.detail || {}; + + const toast = createToast(detail.message, detail.type, detail.timeout); + document.body.appendChild(toast); + // eslint-disable-next-line no-console + if (detail.type === 'error') console.error('Error:', detail.message, detail); + } + + connectedCallback() { + super.connectedCallback(); + + this.addEventListener('show-toast', this.handleToast); + + withTimeout(DA_SDK, SDK_TIMEOUT) + .then(({ context, token }) => { + this.data = { context, token }; + return getSource(DOCUMENT_PATH); + }).then((document) => { + if (!document) { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'error', message: 'Error fetching document' } })); + return; + } + this.data = { ...this.data, document }; + this.updateMarqueeImage(); + }).catch((error) => { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'error', message: `Error connecting to DA SDK: ${error.message}` } })); + }); + } + + disconnectedCallback() { + this.removeEventListener('show-toast', this.handleToast); + + if (this.marqueeImage?.url && this.marqueeImage.url.startsWith('blob:')) { + URL.revokeObjectURL(this.marqueeImage.url); + } + + super.disconnectedCallback(); + } + + updateDocumentMarquee(imageUrl) { + const marqueeBlock = this.data.document.querySelector('.marquee'); + if (!marqueeBlock) return; + + const img = marqueeBlock.querySelector('img'); + if (img) img.src = imageUrl; + } + + async uploadFile(file) { + try { + const imageUrl = await saveImage(MEDIA_PATH, file); + + if (!imageUrl) throw new Error('Failed to upload file'); + + this.marqueeImage = { url: imageUrl, name: file.name }; + + return imageUrl; + } catch (e) { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'error', message: 'Failed to upload file' } })); + return null; + } + } + + handleImageChange(e) { + if (this.marqueeImage?.url && this.marqueeImage.url.startsWith('blob:')) { + URL.revokeObjectURL(this.marqueeImage.url); + } + + this.marqueeImage = { url: '', name: '' }; + + const { file } = e.detail; + if (!file) return; + + this.uploadFile(file) + .then((url) => { + if (!url) return; + + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'success', message: 'Image Uploaded', timeout: TOAST_TIMEOUT } })); + this.updateDocumentMarquee(url); + saveSource(DOCUMENT_PATH, this.data.document) + .then(() => { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'success', message: 'Document Marquee Updated', timeout: TOAST_TIMEOUT } })); + }) + .catch((error) => { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'error', message: `Failed to save document: ${error.message}` } })); + }); + }).catch((error) => { + this.dispatchEvent(new CustomEvent('show-toast', { detail: { type: 'error', message: `Upload failed: ${error.message}` } })); + }); + } + + async handleSubmit(e) { + e.preventDefault(); + } + + render() { + return html` +

Campaign Landing Page Generator

+

Page URL: ${EDIT_DOCUMENT_URL}

+
+
+

Content Type

+ + + + + + +
+
+

Form

+ + + + + + + + + + + + + +
+
+

Marquee

+ + + + + +
+ +
+ + + +
+
+
+
+

Body

+ + +
+
+

Card

+ + + +
+
+

CaaS Content

+ + + + + + + + + +
+
+

SEO Metadata

+ + +
+
+

Experience Fragment

+ + + +
+
+

Asset Delivery

+ + +
+
+

URL

+ +
+
+ Generate + View Page + Edit Content +
+
+ `; + } +} + +customElements.define('image-dropzone', ImageDropzone); +customElements.define('da-generator', LandingPageForm); +customElements.define('toast-message', ToastMessage); + +export default async function init(el) { + const bulk = document.createElement('da-generator'); + el.append(bulk); +} + +init(document.querySelector('main')); diff --git a/tools/generator/toast/toast.css b/tools/generator/toast/toast.css new file mode 100644 index 00000000..7f07ac1a --- /dev/null +++ b/tools/generator/toast/toast.css @@ -0,0 +1,49 @@ +:host { + position: fixed; + top: 20px; + right: 20px; + z-index: 1000; + opacity: 0; + transform: translateX(100%); + transition: all 0.3s ease; +} + +:host([visible]) { + opacity: 1; + transform: translateX(0); +} + +.toast { + background: white; + border-radius: 6px; + box-shadow: 0 4px 12px #00000026; + padding: 12px 16px; + min-width: 250px; + border-left: 4px solid #3b82f6; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.toast.success { border-left-color: #10b981; } +.toast.error { border-left-color: #ef4444; } +.toast.warning { border-left-color: #f59e0b; } + +.toast button { + background: none; + border: none; + cursor: pointer; + font-size: 18px; + color: #666; + padding: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.toast button:hover { + color: #333; +} diff --git a/tools/generator/toast/toast.js b/tools/generator/toast/toast.js new file mode 100644 index 00000000..0ae35c1f --- /dev/null +++ b/tools/generator/toast/toast.js @@ -0,0 +1,59 @@ +/* eslint-disable import/no-unresolved */ +import { LitElement, html } from 'da-lit'; +import getStyle from 'styles'; + +const style = await getStyle(import.meta.url.split('?')[0]); + +const FADE_TIMEOUT = 300; + +export default class ToastMessage extends LitElement { + static properties = { + message: { type: String }, + type: { type: String }, + timeout: { type: Number }, + visible: { type: Boolean, reflect: true }, + }; + + static styles = style; + + constructor() { + super(); + this.message = ''; + this.type = 'info'; + this.timeout = 0; + this.visible = false; + } + + connectedCallback() { + super.connectedCallback(); + setTimeout(() => { + this.visible = true; + if (this.timeout > 0) { + setTimeout(() => this.hide(), this.timeout); + } + }, FADE_TIMEOUT); + } + + hide() { + this.visible = false; + setTimeout(() => this.remove(), FADE_TIMEOUT); + } + + render() { + return html` + + `; + } +} + +export const createToast = (message, type = 'info', timeout = 0) => { + const toast = document.createElement('toast-message'); + toast.message = message; + toast.type = type; + toast.timeout = timeout; + + return toast; +}; diff --git a/tools/locale-nav.html b/tools/locale-nav.html new file mode 100644 index 00000000..05a32c16 --- /dev/null +++ b/tools/locale-nav.html @@ -0,0 +1,16 @@ + + + + Locale Nav + + + + + + + + + + + + diff --git a/tools/locale-nav/img/Smock_ExperienceCloud_24_N.svg b/tools/locale-nav/img/Smock_ExperienceCloud_24_N.svg new file mode 100644 index 00000000..03cdee71 --- /dev/null +++ b/tools/locale-nav/img/Smock_ExperienceCloud_24_N.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/tools/locale-nav/img/Smock_FileHTML_18_N.svg b/tools/locale-nav/img/Smock_FileHTML_18_N.svg new file mode 100644 index 00000000..ebdc0ee2 --- /dev/null +++ b/tools/locale-nav/img/Smock_FileHTML_18_N.svg @@ -0,0 +1,13 @@ + + + + + + S FileHTML 18 N + + + \ No newline at end of file diff --git a/tools/locale-nav/img/spectrum-search.svg b/tools/locale-nav/img/spectrum-search.svg new file mode 100644 index 00000000..45b85586 --- /dev/null +++ b/tools/locale-nav/img/spectrum-search.svg @@ -0,0 +1,11 @@ + + + + + S Search 18 N + + diff --git a/tools/locale-nav/locale-nav.js b/tools/locale-nav/locale-nav.js new file mode 100644 index 00000000..36da2752 --- /dev/null +++ b/tools/locale-nav/locale-nav.js @@ -0,0 +1,103 @@ +/* eslint-disable import/no-unresolved */ +import DA_SDK from 'https://da.live/nx/utils/sdk.js'; +import './locale-selector.js'; + +const ADMIN_SOURCE = 'https://admin.da.live/source/'; +const ADMIN_STATUS = 'https://admin.hlx.page/status/'; +const EDIT_URL = 'https://da.live/edit#/'; +const ROOT_LANG = 'en'; +const ROOT_PATH = '/'; + +function extractPaths(languages) { + const langPaths = languages.data.reduce((acc, { location, locales }) => { + if (!location.startsWith('/langstore') && location !== ROOT_PATH) { + acc.push(location); + } + if (locales) { + acc.push(...locales.split(', ').filter((loc) => loc !== ROOT_PATH)); + } + return acc; + }, []); + + return [...langPaths, ROOT_PATH, `/langstore/${ROOT_LANG}`]; +} + +async function fetchLangPaths({ org, repo }, token) { + const opts = { headers: { Authorization: `Bearer ${token}` } }; + const langConfigUrl = `${ADMIN_SOURCE}${org}/${repo}/.da/translate-v2.json`; + + try { + const resp = await fetch(langConfigUrl, opts); + if (!resp.ok) return null; + const { languages } = await resp.json(); + + return extractPaths(languages); + } catch (e) { + return null; + } +} + +async function fetchStatus({ org, repo }, locPath) { + const statusUrl = `${ADMIN_STATUS}${org}/${repo}/main/${locPath}`; + try { + const res = await fetch(statusUrl); + if (!res.ok) { throw new Error(res.status); } + const data = await res.json(); + return { preview: data.preview.status, live: data.live.status }; + } catch { + return null; + } +} + +(async function init() { + const { context, token } = await DA_SDK; + if (!context) { + // eslint-disable-next-line no-console + console.error('No context found'); + return; + } + const { org, repo, path } = context; + const aemPath = `/${path.replace(/^\/|index$/g, '')}`; + + const langPaths = await fetchLangPaths(context, token); + if (!langPaths) { + // eslint-disable-next-line no-console + console.error('No languages found'); + return; + } + + const currLang = langPaths.find((lang) => aemPath.startsWith(lang)); + const langPathList = langPaths.map((lang) => { + const code = lang === ROOT_PATH ? ROOT_LANG : lang.replace(ROOT_PATH, ''); + const currLocation = lang === ROOT_PATH ? '' : lang; + const editPath = path.replace(currLang === ROOT_PATH ? '' : currLang, currLocation); + const locPath = aemPath.replace(currLang === ROOT_PATH ? '' : currLang, currLocation); + return { + code, + path: locPath, + edit: `${EDIT_URL}${org}/${repo}${editPath}`, + preview: `https://main--${repo}--${org}.aem.page${locPath}`, + live: `https://main--${repo}--${org}.aem.live${locPath}`, + }; + }); + + const tagBrowser = document.createElement('da-locale-selector'); + const status = langPathList.reduce((acc, locale) => { + acc[locale.path] = { }; + return acc; + }, {}); + const currLangIndex = langPathList.findIndex((lang) => lang.code === (currLang === ROOT_PATH ? ROOT_LANG : currLang.replace(ROOT_PATH, ''))); + const [currentLocale] = langPathList.splice(currLangIndex, 1); + tagBrowser.status = status; + tagBrowser.currLocale = currentLocale; + tagBrowser.altLocales = langPathList; + + document.body.append(tagBrowser); + + Object.keys(status).forEach((locPath) => { + fetchStatus(context, locPath).then((stat) => { + status[locPath] = stat; + tagBrowser.status = { ...status }; + }); + }); +}()); diff --git a/tools/locale-nav/locale-selector.css b/tools/locale-nav/locale-selector.css new file mode 100644 index 00000000..4fa9183c --- /dev/null +++ b/tools/locale-nav/locale-selector.css @@ -0,0 +1,140 @@ +:host { + font-family: 'Adobe Clean', adobe-clean, sans-serif; + + --local-padding: 0 6px; +} + +.locale-selector { + width: 100%; + font-size: 0.875rem; +} + +.locale-selector .locale-header { + background-color: #f8f8f8; + display: flex; + justify-content: space-between; + padding: var(--local-padding); + line-height: 36px; + text-transform: uppercase; + font-weight: 700; +} + +.locale-selector .actions { + display: grid; + grid-template-columns: 48px 60px 48px; + text-align: center; +} + +.locale-selector .action { + height: 42px; + width: 42px; + margin: 2px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.locale-selector .detail { + display: flex; + justify-content: space-between; + padding: var(--local-padding); + line-height: 46px; + font-size: 18px; + text-transform: uppercase; +} + +.locale-selector li::after { + content: ""; + display: block; + height: 1px; + background: #d1d1d1; + width: 100%; + flex: 0 0 auto; +} + +.locale-selector .action .details { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.locale-selector .action .icon { + height: 26px; + width: 26px; +} + +.locale-selector .action .icon-html { + background: url('/tools/locale-nav/img/Smock_FileHTML_18_N.svg') center / 26px no-repeat; +} + +.locale-selector .action .icon-aem { + border-radius: 5px; + background-color: #959595; + background-image: url('/tools/locale-nav/img/Smock_ExperienceCloud_24_N.svg'); +} + +.locale-selector .action .icon-aem.status-200 { + background-color: #fa0f00; +} + +.locale-selector .action .icon-aem.status-404 { + cursor: default; + background-color: #fa0f00; + opacity: 0.25; +} + +.locale-selector ul.locales { + margin: 0; + padding: 0; + list-style: none; +} + +.locale-selector .locale-search-wrapper { + position: relative; +} + +.locale-selector .locale-search { + width: 100%; + background-color: #f8f8f8; + display: block; + font-family: var(--body-font-family); + border: none; + margin: 0; + padding: var(--local-padding); + line-height: 36px; + text-transform: uppercase; + font-weight: 700; + box-sizing: border-box; + transition: padding .2s ease-in-out; +} + +.locale-selector .locale-search-icon { + position: absolute; + content: ''; + width: 36px; + height: 36px; + left: -36px; + top: 0; + background: url('/tools/locale-nav/img/spectrum-search.svg') center / 24px no-repeat; + transition: left .2s ease-in-out; +} + +.locale-selector .locale-search:hover + .locale-search-icon, +.locale-selector .locale-search:focus + .locale-search-icon { + left: 6px; +} + +.locale-selector .locale-search:hover, +.locale-selector .locale-search:focus { + padding: 0 42px; +} + +.locale-selector .locale-search::placeholder { + color: #676767; +} diff --git a/tools/locale-nav/locale-selector.js b/tools/locale-nav/locale-selector.js new file mode 100644 index 00000000..0765658c --- /dev/null +++ b/tools/locale-nav/locale-selector.js @@ -0,0 +1,90 @@ +/* eslint-disable import/no-unresolved */ +import { LitElement, html, nothing } from 'https://da.live/nx/deps/lit/lit-core.min.js'; +import getStyle from 'https://da.live/nx/utils/styles.js'; + +const style = await getStyle(import.meta.url); + +export default class DaLocaleSelector extends LitElement { + static properties = { + currLocale: { type: Object }, + altLocales: { type: Array }, + status: { type: Object }, + }; + + connectedCallback() { + super.connectedCallback(); + this.shadowRoot.adoptedStyleSheets = [style]; + } + + handleSearch = (event) => { + const localeElements = this.shadowRoot.querySelectorAll('.locales li'); + const search = event.target.value.toLowerCase().trim(); + localeElements.forEach((subject) => { + if (subject.textContent.includes(search)) { + subject.style.display = ''; + } else { + subject.style.display = 'none'; + } + }); + }; + + decorateLocale(locale) { + const { edit = '#', preview = '#', live = '#' } = locale; + const status = this.status[locale.path] || {}; + + return html` +
+ ${locale.code} + +
`; + } + + decorateLocales(locales) { + const details = locales.map((locale) => { + const decoratedLocale = this.decorateLocale(locale); + return html`
  • ${decoratedLocale}
  • `; + }); + + return html`
      ${details}
    `; + } + + render() { + return html` +
    +
    + Current +
    + Edit + Preview + Live +
    +
    +
    + ${this.currLocale ? this.decorateLocale(this.currLocale) : nothing} +
    +
    + +
    +
    +
    + ${this.altLocales ? this.decorateLocales(this.altLocales) : nothing} +
    +
    + `; + } +} + +customElements.define('da-locale-selector', DaLocaleSelector); diff --git a/tools/mkto-poi/index.html b/tools/mkto-poi/index.html new file mode 100644 index 00000000..5c91ad8e --- /dev/null +++ b/tools/mkto-poi/index.html @@ -0,0 +1,33 @@ + + + + Mkto POI Landing Page + + + + + + + + +
    +
    + + + diff --git a/tools/mkto-poi/mkto-poi.js b/tools/mkto-poi/mkto-poi.js new file mode 100644 index 00000000..0330418e --- /dev/null +++ b/tools/mkto-poi/mkto-poi.js @@ -0,0 +1,66 @@ +/* eslint-disable no-underscore-dangle, import/no-unresolved */ +import getStyle from 'https://da.live/nx/utils/styles.js'; +import { LitElement, html } from 'da-lit'; + +const POI_URL = 'https://milo.adobe.com/tools/marketo-options.json'; + +async function getMarketoPOI() { + const resp = await fetch(POI_URL); + if (!resp.ok) { + const error = 'Error fetching marketo options'; + const mktoError = new CustomEvent('mktoOptionsError', { detail: { message: error } }); + document.dispatchEvent(mktoError); + return []; + } + + const data = await resp.json(); + return data; +} + +function poiPair(data) { + const poiList = data.poi.data; + return poiList.reduce((rdx, item) => { + const { Key, Value } = item; + if (Key.length === 0 || Value.length === 0) return rdx; + rdx.push({ Key, Value }); + return rdx; + }, []); +} + +export default class mktoPoiSelector extends LitElement { + static properties = { + propOptions: { Type: Array }, + style: { Type: String }, + _options: { state: true }, + }; + + constructor() { + super(); + this._options = []; + } + + async connectedCallback() { + super.connectedCallback(); + const style = this._style ? await getStyle(this._style) : await getStyle(import.meta.url); + this.shadowRoot.adoptedStyleSheets = [style]; + + if (!this.propOptions) { + const data = await getMarketoPOI(); + this._options = poiPair(data); + } else { + this._options = this.propOptions; + } + } + + render() { + return html` +
    + + + + + +
    + +
    + + +
    + + + ${this._prefixes ? html` +
    + ${this.renderList('Include', this._active)} + ${this.renderList('Exclude', this._inactive)} + ` : nothing} +
    +
    + +
    + `; + } +} + +customElements.define('da-rollout', DaRollout); + +(async function init() { + const { context, token } = await DA_SDK; + + const daRollout = document.createElement('da-rollout'); + daRollout.path = context.path; + daRollout.token = token; + daRollout.repo = context.repo; + daRollout.org = context.org; + document.body.append(daRollout); +}()); diff --git a/tools/sidekick/README.md b/tools/sidekick/README.md new file mode 100644 index 00000000..c1fa1bbc --- /dev/null +++ b/tools/sidekick/README.md @@ -0,0 +1,21 @@ +# Notes on the sidekick / DA library + +The DA config controls the tools in the DA library for this site, not the sidekick/config.json . + +## Editing the DA Library +To edit the DA library tools, go to https://da.live/config#/adobecom/da-bacom/ and edit the "library" sheet. + +### Ref +You can test your changes by setting a ref. If there's no ref listed, the tool will be live in the library. + +Example: + +``` +// da-bacom config sheet +ref: methomas-tag-browser + +// Test page with ?ref=methomas-tag-browser +https://da.live/edit?ref=methomas-tag-browser#/adobecom/da-bacom/drafts/methomas/brand-concierge +``` + +This will pull code from `https://methomas-tag-browser--da-bacom--adobecom.aem.live` so make sure your ref is your branch name if you have corresponding code changes. diff --git a/tools/sidekick/config.js b/tools/sidekick/config.js deleted file mode 100644 index d9de6bda..00000000 --- a/tools/sidekick/config.js +++ /dev/null @@ -1,92 +0,0 @@ -function hasSchema(host) { - if (window.location.hostname === host) { - const schema = document.querySelector('script[type="application/ld+json"]'); - return schema !== null; - } - return false; -} - -// This file contains the project-specific configuration for the sidekick. -(() => { - window.hlx.initSidekick({ - libraries: [ - { - text: 'Blocks', - paths: [ - 'https://main--milo--adobecom.aem.page/docs/library/blocks.json', - 'https://main--bacom--adobecom.aem.page/docs/library/blocks.json', - ], - }, - ], - plugins: [ - { - id: 'send-to-caas', - condition: (s) => s.isHelix() && s.isProd() && !window.location.pathname.endsWith('.json'), - button: { - text: 'Send to CaaS', - action: async (_, sk) => { - // eslint-disable-next-line import/no-unresolved - const { default: sendToCaaS } = await import('https://milo.adobe.com/tools/send-to-caas/sidekick.js'); - sendToCaaS(_, sk); - }, - }, - }, - // TOOLS --------------------------------------------------------------------- - { - id: 'library', - condition: () => true, - button: { - text: 'Library', - action: (_, s) => { - const domain = 'https://main--milo--adobecom.aem.page'; - const { config } = s; - const script = document.createElement('script'); - script.type = 'module'; - script.onload = () => { - const skEvent = new CustomEvent( - 'hlx:library-loaded', - { detail: { domain, libraries: config.libraries } }, - ); - document.dispatchEvent(skEvent); - }; - script.src = `${domain}/libs/ui/library/library.js`; - document.head.appendChild(script); - }, - }, - }, - { - id: 'tools', - condition: (s) => s.isEditor(), - button: { - text: 'Tools', - action: (_, s) => { - const { config } = s; - window.open(`https://${config.innerHost}/tools/`, 'milo-tools'); - }, - }, - }, - { - id: 'translate', - condition: (s) => s.isEditor() && s.location.href.includes('/:x'), - button: { - text: 'Translate', - action: (_, sk) => { - const domain = 'https://main--milo--adobecom.aem.page'; - const { config } = sk; - window.open(`${domain}/tools/translation/index.html?sp=${encodeURIComponent(window.location.href)}&owner=${config.owner}&repo=${config.repo}&ref=${config.ref}`, 'hlx-sidekick-spark-translation'); - }, - }, - }, - { - id: 'seo', - condition: (s) => hasSchema(s.config.host), - button: { - text: 'Check Schema', - action: () => { - window.open(`https://search.google.com/test/rich-results?url=${encodeURIComponent(window.location.href)}`, 'check-schema'); - }, - }, - }, - ], - }); -})(); diff --git a/tools/sidekick/config.json b/tools/sidekick/config.json index 502060ee..8dd91dee 100644 --- a/tools/sidekick/config.json +++ b/tools/sidekick/config.json @@ -1,73 +1,15 @@ { - "project": "BACOM", + "project": "Adobe for Business", "host": "business.adobe.com", - "previewHost": "main--bacom--adobecom.aem.page", - "liveHost": "main--bacom--adobecom.aem.live", + "editUrlLabel": "Document Authoring", + "editUrlPattern": "https://da.live/edit#/{{org}}/{{site}}{{pathname}}", "trustedHosts": ["milo.adobe.com"], "plugins": [ - { - "id": "path", - "title": "Path", - "environments": [ "edit" ], - "url": "https://milo.adobe.com/tools/path-finder", - "isPalette": true, - "passReferrer": true, - "passConfig": true, - "paletteRect": "top: 50%; left: 50%; transform: translate(-50%,-50%); height: 84px; width: 900px; overflow: hidden; border-radius: 6px; box-shadow: 0 20px 35px 0px rgba(0, 0, 0, 0.5);", - "includePaths": [ "**.docx**", "**.xlsx**" ] - }, - { - "id": "library", - "title": "Library", - "environments": [ "edit" ], - "isPalette": true, - "passConfig": true, - "paletteRect": "top: auto; bottom: 20px; left: 20px; height: 398px; width: 360px;", - "url": "https://milo.adobe.com/tools/library", - "includePaths": [ "**.docx**" ] - }, { "id": "tools", "title": "Tools", "isContainer": true }, - { - "containerId": "tools", - "id": "localize", - "title": "Localize", - "environments": [ "edit" ], - "url": "https://milo.adobe.com/tools/loc/index.html?project=bacom--adobecom", - "passReferrer": true, - "includePaths": [ "**.xlsx**" ] - }, - { - "containerId": "tools", - "id": "localize-2", - "title": "Localize (V2)", - "environments": [ "edit" ], - "url": "https://main--bacom--adobecom.aem.page/tools/loc?milolibs=locui", - "passReferrer": true, - "passConfig": true, - "includePaths": [ "**.xlsx**" ] - }, - { - "containerId": "tools", - "id": "floodgate", - "title": "Floodgate", - "environments": [ "edit" ], - "url": "https://main--bacom--adobecom.aem.page/tools/floodgate?milolibs=floodgateui", - "passReferrer": true, - "passConfig": true, - "includePaths": [ "**/:x**" ] - }, - { - "containerId": "tools", - "title": "Send to CaaS", - "id": "sendtocaas", - "environments": ["dev","preview", "live", "prod"], - "event": "send-to-caas", - "excludePaths": ["https://milo.adobe.com/tools/caas**", "*.json"] - }, { "containerId": "tools", "title": "Check Schema", @@ -84,74 +26,44 @@ "event": "preflight" }, { - "containerId": "tools", - "id": "locales", - "title": "Locales", - "environments": [ "edit", "dev", "preview", "live" ], - "isPalette": true, - "passConfig": true, - "passReferrer": true, - "paletteRect": "top: auto; bottom: 25px; left: 75px; height: 388px; width: 360px;", - "url": "https://milo.adobe.com/tools/locale-nav", - "includePaths": [ "**.docx**" ] - }, - { - "containerId": "tools", - "title": "Tag Selector", - "id": "tag-selector", - "environments": ["edit"], - "url": "https://main--bacom--adobecom.aem.live/tools/tag-selector", - "isPalette": true, - "paletteRect": "top: 150px; left: 7%; height: 675px; width: 85vw;" - }, - { - "containerId": "tools", - "id": "version-history", - "title": "Version History", - "environments": [ "edit" ], - "url": "https://milo.adobe.com/tools/version-history", - "isPalette": true, - "passReferrer": true, - "passConfig": true, - "paletteRect": "top: auto; bottom: 20px; left: 20px; height: 498px; width: 460px;", - "includePaths": [ "**.docx**", "**.xlsx**" ] - }, - { - "containerId": "tools", - "id": "caas-configurator", - "title": "CaaS Configurator", - "environments": [ "edit", "preview", "dev" ], - "url": "https://milo.adobe.com/tools/caas", - "isPalette": false, - "includePaths": [ "**.docx**"] - }, - { - "containerId": "tools", - "id": "faas-configurator", - "title": "FaaS Configurator", - "environments": [ "edit", "preview", "dev" ], - "url": "https://milo.adobe.com/tools/faas", - "isPalette": false, - "includePaths": [ "**.docx**"] - }, - { - "containerId": "tools", - "id": "bulk", - "title": "Bulk operations", - "environments": [ "edit", "dev", "preview", "live" ], - "url": "https://main--bacom--adobecom.aem.page/tools/bulk" - }, - { - "containerId": "tools", "id": "rollout", "title": "Rollout", - "environments": [ "preview" ], - "isPalette": true, - "passReferrer": true, - "passConfig": true, - "url": "https://milo.adobe.com/tools/rollout", - "includePaths": [ "**.docx**", "**.xlsx**" ], - "paletteRect": "top: 40%; left: 50%; transform: translate(-50%,-50%); height: 350px; width: 500px; overflow: hidden; border-radius: 15px; box-shadow: 0 20px 35px 0px rgba(0, 0, 0, 0.5);" + "environments": ["edit"], + "daLibrary": true, + "experience": "dialog", + "dialogSize": "medium", + "includePaths": ["DA"], + "icon": "https://da.live/nx/public/plugins/rollout/media_195da69764de2782d555abed3042d8434a040e31c.png", + "url": "https://da.live/nx/public/plugins/rollout.html" } - ] + ], + "apps": [ + { + "title": "CaaS Configurator", + "description": "Content as a Service.", + "url": "https://milo.adobe.com/tools/caas" + }, + { + "title": "FaaS Configurator", + "description": "Deprecated.", + "url": "https://milo.adobe.com/tools/faas" + }, + { + "title": "Search", + "description": "Unlocking Precision with Advanced Contextual Filters", + "image": "https://milostudio--milo--adobecom.aem.live/img/tools/search.jpg", + "url": "https://da.live/app/adobecom/da-bacom/tools/ms-apps/search" + }, + { + "title": "Bulk Operations", + "description": "Bulk Operations", + "url": "https://da.live/app/adobecom/da-bacom/tools/ms-apps/bulkops" + }, + { + "title": "Graybox", + "description": "Admin tools for Milo Graybox", + "image": "https://publish-p133406-e1301188.adobeaemcloud.com/content/dam/milo/app-icons/graybox.jpg", + "url": "https://da.live/app/adobecom/milo/tools/graybox" + } + ] } diff --git a/tools/tags.html b/tools/tags.html new file mode 100644 index 00000000..68a93c05 --- /dev/null +++ b/tools/tags.html @@ -0,0 +1,20 @@ + + + + DA App SDK Sample + + + + + + + + + +
    + + diff --git a/tools/tags/tag-browser.css b/tools/tags/tag-browser.css new file mode 100644 index 00000000..18cad453 --- /dev/null +++ b/tools/tags/tag-browser.css @@ -0,0 +1,112 @@ +:host { + font-family: 'Adobe Clean', adobe-clean, sans-serif; +} + +.tag-browser { + display: block; + position: relative; + overflow: hidden; + width: 100%; + height: 100vh; +} + +.tag-browser .search-details { + height: 36px; + display: flex; + align-items: center; + justify-content: space-between; + line-height: 36px; + font-size: 16px; + padding: 0 12px; +} + +.tag-browser .tag-search input[type="text"] { + flex: 1; + height: 32px; + padding: 0 8px; + font-size: 16px; + font-family: 'Adobe Clean', adobe-clean, sans-serif; + border: 1px solid #d1d1d1; + border-radius: 2px; + box-sizing: border-box; +} + +.tag-browser .tag-groups { + display: flex; + overflow: auto; + position: absolute; + width: 100%; + height: calc(100% - 36px); +} + +.tag-browser ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tag-browser .tag-group-column { + flex-shrink: 0; + width: 100%; + height: 100%; + overflow: auto; + max-width: 280px; + padding: 0 12px; + box-sizing: border-box; +} + +.tag-browser .tag-group-column:first-child { + padding: 0 12px; +} + +.tag-browser .tag-group .tag-details { + cursor: pointer; + display: flex; + justify-content: space-between; + line-height: 36px; +} + +.tag-browser .tag-group .tag-title { + flex: 1; + padding: 0 6px; + font-size: 16px; + font-family: 'Adobe Clean', adobe-clean, sans-serif; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + background: none; + border: none; + text-align: left; + cursor: pointer; +} + +.tag-browser .tag-search button, +.tag-browser .tag-details button { + cursor: pointer; + height: 32px; + width: 32px; + margin: 2px; + padding: 0; + border: none; + display: block; + border-radius: 2px; + background-color: #efefef; +} + +.tag-browser .tag-group .tag-title:hover { + background-color: #efefef; +} + +.tag-browser .tag-search button:hover, +.tag-browser .tag-group button:hover, +.tag-browser .tag-group .tag-title.active { + background-color: #e9e9e9; +} + +.tag-browser .tag-search::after, +.tag-browser .tag-group::after { + content: ""; + display: block; + height: 1px; + background: #d1d1d1; +} diff --git a/tools/tags/tag-browser.js b/tools/tags/tag-browser.js new file mode 100644 index 00000000..6a6de953 --- /dev/null +++ b/tools/tags/tag-browser.js @@ -0,0 +1,164 @@ +/* eslint-disable no-underscore-dangle, import/no-unresolved */ +import { LitElement, html, nothing } from 'https://da.live/nx/deps/lit/lit-core.min.js'; +import getStyle from 'https://da.live/nx/utils/styles.js'; + +const style = await getStyle(import.meta.url); + +class DaTagBrowser extends LitElement { + static properties = { + rootTags: { type: Array }, + actions: { type: Object }, + getTags: { type: Function }, + tagValue: { type: String }, + _tags: { state: true }, + _activeTag: { state: true }, + _searchQuery: { state: true }, + _secondaryTags: { state: true }, + }; + + constructor() { + super(); + this._tags = []; + this._activeTag = {}; + this._searchQuery = ''; + this._secondaryTags = false; + } + + getTagSegments() { + return (this._activeTag.activeTag ? this._activeTag.activeTag.split('/') : []).concat(this._activeTag.name); + } + + getTagValue() { + if (this.tagValue === 'title') return this._activeTag.title; + const tagSegments = this.getTagSegments(); + return tagSegments.join(tagSegments.length > 2 ? '/' : ':').replace('/', ':'); + } + + handleBlur() { + this._secondaryTags = false; + } + + connectedCallback() { + super.connectedCallback(); + this.shadowRoot.adoptedStyleSheets = [style]; + this.addEventListener('blur', this.handleBlur, true); + } + + disconnectedCallback() { + this.removeEventListener('blur', this.handleBlur, true); + super.disconnectedCallback(); + } + + updated(changedProperties) { + if (changedProperties.has('rootTags')) { + this._tags = [this.rootTags]; + this._activeTag = {}; + } + + if (changedProperties.has('_tags')) { + setTimeout(() => { + const groups = this.renderRoot.querySelector('.tag-groups'); + if (!groups) return; + const firstTag = groups.lastElementChild?.querySelector('.tag-title'); + firstTag?.focus(); + groups.scrollTo({ left: groups.scrollWidth, behavior: 'smooth' }); + }, 100); + } + } + + async handleTagClick(tag, idx) { + this._activeTag = tag; + if (!this.getTags) return; + const newTags = await this.getTags(tag); + if (!newTags || newTags.length === 0) return; + this._tags = [...this._tags.toSpliced(idx + 1), newTags]; + } + + handleTagInsert(tag) { + this._activeTag = tag; + const tagValue = this._secondaryTags ? `, ${this.getTagValue()}` : this.getTagValue(); + this.actions.sendText(tagValue); + this._secondaryTags = true; + } + + handleBackClick() { + if (this._tags.length === 0) return; + this._tags = this._tags.slice(0, -1); + this._activeTag = this._tags[this._tags.length - 1] + .find((tag) => this._activeTag.activeTag.includes(tag.name)) || {}; + } + + handleSearchInput(event) { + this._searchQuery = event.target.value.toLowerCase(); + } + + filterTags(tags) { + if (!this._searchQuery) return tags; + return tags.filter((tag) => tag.title.toLowerCase().includes(this._searchQuery)); + } + + renderSearchBar() { + return html` + + `; + } + + renderTag(tag, idx) { + const active = this.getTagSegments()[idx] === tag.name; + return html` +
  • +
    + + +
    +
  • + `; + } + + renderTagGroup(group, idx) { + const filteredGroup = this.filterTags(group); + return html` + + `; + } + + render() { + if (this._tags.length === 0) return nothing; + return html` +
    + ${this.renderSearchBar()} +
      + ${this._tags.map((group, idx) => html` +
    • + ${this.renderTagGroup(group, idx)} +
    • + `)} +
    +
    + `; + } +} + +customElements.define('da-tag-browser', DaTagBrowser); diff --git a/tools/tags/tag-utils.js b/tools/tags/tag-utils.js new file mode 100644 index 00000000..676b8d75 --- /dev/null +++ b/tools/tags/tag-utils.js @@ -0,0 +1,68 @@ +/* eslint-disable import/no-unresolved */ +import { DA_ORIGIN } from 'https://da.live/nx/public/utils/constants.js'; + +export const tagPathConfig = { + root: '/content/cq:tags', + ext: '.1.json', +}; + +export function setTagPathConfig({ root, ext }) { + tagPathConfig.root = root; + tagPathConfig.ext = ext; +} + +export async function getAemRepo(project, opts) { + const configUrl = `${DA_ORIGIN}/config/${project.org}/${project.repo}`; + const resp = await fetch(configUrl, opts); + if (!resp.ok) return null; + const json = await resp.json(); + const data = Array.isArray(json.data?.data) ? json.data.data : json.data; + const aemRepo = data?.find((entry) => entry.key === 'aem.repositoryId')?.value; + const namespaces = data?.find((entry) => entry.key === 'aem.tags.namespaces')?.value; + return { aemRepo, namespaces }; +} + +export async function getTags(path, opts) { + const activeTag = path.split('cq:tags').pop().replace('.1.json', '').slice(1); + const resp = await fetch(path, opts); + if (!resp.ok) return null; + const json = await resp.json(); + const tags = Object.keys(json).reduce((acc, key) => { + if (json[key]['jcr:primaryType'] === 'cq:Tag') { + acc.push({ + path: `${path.replace(tagPathConfig.ext, '')}/${key}${tagPathConfig.ext}`, + activeTag, + name: key, + title: json[key]['jcr:title'] || key, + details: json[key], + }); + } + return acc; + }, []); + + return tags; +} + +export const getRootTags = async (namespaces, aemConfig, opts) => { + const createTagUrl = (namespace = '') => `https://${aemConfig.aemRepo}${tagPathConfig.root}${namespace ? `/${namespace}` : ''}${tagPathConfig.ext}`; + + if (namespaces.length === 0) { + return getTags(createTagUrl(), opts).catch(() => null); + } + + if (namespaces.length === 1) { + const namespace = namespaces[0].toLowerCase().replaceAll(' ', '-'); + return getTags(createTagUrl(namespace), opts).catch(() => null); + } + + return namespaces.map((title) => { + const namespace = title.toLowerCase().replaceAll(' ', '-'); + return { + path: createTagUrl(namespace), + name: namespace, + title, + activeTag: '', + details: {}, + }; + }); +}; diff --git a/tools/tags/tags.js b/tools/tags/tags.js new file mode 100644 index 00000000..5c2c1a1a --- /dev/null +++ b/tools/tags/tags.js @@ -0,0 +1,57 @@ +/* eslint-disable import/no-unresolved */ +import DA_SDK from 'https://da.live/nx/utils/sdk.js'; +import { getAemRepo, getTags, getRootTags } from './tag-utils.js'; +import './tag-browser.js'; + +const UI_TAG_PATH = '/ui#/aem/aem/tags'; + +function showError(message, link = null) { + const mainElement = document.body.querySelector('main'); + const errorMessage = document.createElement('p'); + errorMessage.textContent = message; + + if (link) { + const linkEl = document.createElement('a'); + linkEl.textContent = 'View Here'; + linkEl.href = link; + linkEl.target = '_blank'; + errorMessage.append(linkEl); + } + + const reloadButton = document.createElement('button'); + reloadButton.textContent = 'Reload'; + reloadButton.addEventListener('click', () => window.location.reload()); + + mainElement.append(errorMessage, reloadButton); +} + +(async function init() { + const { context, actions, token } = await DA_SDK.catch(() => null); + if (!context || !actions || !token) { + showError('Please log in to view tags.'); + return; + } + + const opts = { headers: { Authorization: `Bearer ${token}` } }; + const aemConfig = await getAemRepo(context, opts).catch(() => null); + if (!aemConfig || !aemConfig.aemRepo) { + showError('Failed to retrieve config. ', `https://da.live/config#/${context.org}/${context.repo}/`); + return; + } + + const namespaces = aemConfig?.namespaces.split(',').map((namespace) => namespace.trim()) || []; + const rootTags = await getRootTags(namespaces, aemConfig, opts); + + if (!rootTags || rootTags.length === 0) { + showError('Could not load tags. ', `https://${aemConfig.aemRepo}${UI_TAG_PATH}`); + return; + } + + const daTagBrowser = document.createElement('da-tag-browser'); + daTagBrowser.tabIndex = 0; + daTagBrowser.rootTags = rootTags; + daTagBrowser.getTags = async (tag) => getTags(tag.path, opts); + daTagBrowser.tagValue = aemConfig.namespaces ? 'title' : 'path'; + daTagBrowser.actions = actions; + document.body.querySelector('main').append(daTagBrowser); +}()); diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs index 886636ca..563f8c2f 100644 --- a/web-test-runner.config.mjs +++ b/web-test-runner.config.mjs @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { importMapsPlugin } from '@web/dev-server-import-maps'; export default { @@ -10,4 +11,53 @@ export default { ], }, plugins: [importMapsPlugin({})], + testRunnerHtml: (testFramework) => ` + + + + + + + + + `, };