From 7da138104c0b1dd02ffa32d07d4ae843a9d378e7 Mon Sep 17 00:00:00 2001 From: Putra Bonaccorsi Date: Mon, 2 Oct 2023 16:18:10 -0400 Subject: [PATCH] feat(support): add blocks to support pages --- .../content-text-body/content-text-body.css | 4 +- blocks/right-nav/right-nav.css | 46 ++++++ blocks/right-nav/right-nav.js | 0 blocks/search/search.css | 75 +++++++++ blocks/search/search.js | 47 ++++++ blocks/table/table.css | 35 ++++ blocks/table/table.js | 0 scripts/scripts.js | 18 ++ styles/styles.css | 8 +- tools/importer/{import.js => import-docs.js} | 0 .../{importpaths.txt => import-paths.txt} | 0 tools/importer/import-support.js | 154 ++++++++++++++++++ 12 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 blocks/right-nav/right-nav.css create mode 100644 blocks/right-nav/right-nav.js create mode 100644 blocks/search/search.css create mode 100644 blocks/search/search.js create mode 100644 blocks/table/table.css create mode 100644 blocks/table/table.js rename tools/importer/{import.js => import-docs.js} (100%) rename tools/importer/{importpaths.txt => import-paths.txt} (100%) create mode 100644 tools/importer/import-support.js diff --git a/blocks/content-text-body/content-text-body.css b/blocks/content-text-body/content-text-body.css index 2cea57b3..540cf225 100644 --- a/blocks/content-text-body/content-text-body.css +++ b/blocks/content-text-body/content-text-body.css @@ -1,12 +1,12 @@ /* Section - Content Text Body */ .section.content-text-body-container .content-text-body, .section.content-text-body-container .divider { - max-width: 840px; + max-width: var(--body-text-container); } .section.content-text-body-container > div, .section.content-text-body-container > div:first-child { - margin-bottom: 96px; + margin-bottom: var(--spacer-layout-06); } /* Block - Content Text Body */ diff --git a/blocks/right-nav/right-nav.css b/blocks/right-nav/right-nav.css new file mode 100644 index 00000000..e181523c --- /dev/null +++ b/blocks/right-nav/right-nav.css @@ -0,0 +1,46 @@ +/* Block - Right Nav */ +.right-nav h3, +.right-nav h4, +.right-nav h5 { + margin-top: 0; + font-size: var(--font-size-21); + font-family: var(--sans-serif-font-medium); +} + +.right-nav p { + font-size: var(--font-size-16); +} + +/* Section - Right Nav */ +.section.right-nav-container .default-content-wrapper h2, +.section.right-nav-container .default-content-wrapper h3 { + margin-bottom: 0; +} + +/* Desktop */ +@media only screen and (min-width: 1200px) { + .section.right-nav-container > div { + width: var(--text-max-container); + } + + .section.right-nav-container { + position: relative; + display: grid; + grid-template-columns: 3fr 1fr; + width: var(--secton-max-container); + margin: 0 auto var(--spacer-layout-07) auto; + align-items: flex-start; + } + + .section.right-nav-container > *:not(.right-nav-wrapper) { + grid-column-start: 1; + } + + .section.right-nav-container > .right-nav-wrapper { + grid-column-start: 2; + grid-row-start: 1; + padding-top: var(--spacer-layout-07); + margin-left: var(--gap-120); + width: var(--sidebar-max-container); + } +} \ No newline at end of file diff --git a/blocks/right-nav/right-nav.js b/blocks/right-nav/right-nav.js new file mode 100644 index 00000000..e69de29b diff --git a/blocks/search/search.css b/blocks/search/search.css new file mode 100644 index 00000000..ed3eca6e --- /dev/null +++ b/blocks/search/search.css @@ -0,0 +1,75 @@ +/* Search Block CSS */ +.search.block { + width: 100%; + font-family: var(--sans-serif-font-light); + font-size: var(--font-size-16); + letter-spacing: var(--letter-spacing-001-em); + line-height: var(--line-height-160); + font-weight: var(--font-weight-light); + color: var(--neutral-carbon); +} + +.search.block form { + position: relative; + display: flex; + width: 100%; + height: var(--spacer-element-10); + background-color: var(--neutral-bone); + border-bottom: 1px solid var(--neutral-carbon); +} + +.search.block form input { + font-size: var(--font-size-16); + position: relative; + width: 100%; + box-sizing: border-box; + padding: var(--spacer-element-03) var(--spacer-element-05); + border: 0; + background-color: var(--neutral-bone); +} + +.search.block form span.search-icon { + display: block; + position: relative; + background: url('../../icons/search.svg') no-repeat; + color: var(--neutral-carbon); + height: var(--spacer-element-07); + width: var(--spacer-element-07); + margin: var(--spacer-element-04) var(--spacer-element-04) 0 0; + padding: 0 var(--spacer-element-07) 0 0; + min-width: var(--spacer-element-05); +} + +.search.block div.results ul { + max-height: 280px; + overflow-y: auto; + padding: unset; + margin: 0; + box-shadow: var(--box-shadow-small); +} + +.search.block div.results ul li { + padding: var(--spacer-element-05); + text-decoration: none; + display: block; + margin: 0; + border-bottom: 1px solid var(--neutral-sand); +} + +.search.block div.results ul li:hover { + background-color: var(--purple-20); + cursor: pointer; +} + +.search.block div.results ul li a { + text-decoration: none; + border: unset; + color: var(--neutral-carbon); +} + +/* Tablet styles */ +@media only screen and (min-width: 768px) { + .search.block { + width: var(--text-max-container); + } +} \ No newline at end of file diff --git a/blocks/search/search.js b/blocks/search/search.js new file mode 100644 index 00000000..14020682 --- /dev/null +++ b/blocks/search/search.js @@ -0,0 +1,47 @@ +import { createTag, getSearchIndex } from '../../scripts/scripts.js'; +import { readBlockConfig } from '../../scripts/lib-franklin.js'; + +let index; +let searchTerm = ''; +let results; + +async function showResults() { + // clear the results + results.innerHTML = ''; + // filter and redraw the results + const ul = createTag('ul'); + index + .filter((row) => (row.title.toLowerCase().includes(searchTerm.toLowerCase()) + || row.description.toLowerCase().includes(searchTerm.toLowerCase()))) + .forEach((row) => { + const link = createTag('a', { href: row.path, 'aria-label': row.title }, row.title); + const li = createTag('li', { class: 'search-result' }, link); + ul.append(li); + }); + results.append(ul); +} + +export default async function decorate(block) { + const cfg = await readBlockConfig(block); + block.textContent = ''; + const filter = cfg.filter || ''; + index = await getSearchIndex(filter); + const input = createTag('input', { + type: 'text', + placeholder: 'Type a search term or article type, such as release notes or news.', + }); + const form = createTag('form', { onsubmit: 'event.preventDefault();' }, input); + const searchIcon = createTag('span', { class: 'search-icon' }); + results = createTag('div', { class: 'results' }); + form.append(searchIcon); + block.append(form); + block.append(results); + + // Search input listener + input.addEventListener('input', async (e) => { + // saving the input value + searchTerm = e.target.value; + // re-displaying results based on the new search_term + await showResults(); + }); +} diff --git a/blocks/table/table.css b/blocks/table/table.css new file mode 100644 index 00000000..15ec642e --- /dev/null +++ b/blocks/table/table.css @@ -0,0 +1,35 @@ +/* Section - Table */ +main .section > .table-wrapper { + margin-top: var(--spacer-layout-04); + margin-bottom: var(--spacer-layout-04); +} + +/* Block - Table */ +.table.block { + width: 100%; + overflow-x: auto; + border-radius: var(--spacer-element-06) var(--spacer-element-06) 0 0; +} + +table { + width: 100%; + border-spacing: 0; +} + +table thead tr { + background-color: var(--neutral-bone); +} + +table tr td, +table tr th { + padding: var(--spacer-element-06); + font-size: var(--font-size-16); + line-height: 160%; + text-align: left; + width: 20%; + border-bottom: 1px solid var(--neutral-sand); +} + +table p { + font-size: var(--font-size-16); +} diff --git a/blocks/table/table.js b/blocks/table/table.js new file mode 100644 index 00000000..e69de29b diff --git a/scripts/scripts.js b/scripts/scripts.js index 6b359651..58106784 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -747,6 +747,24 @@ export async function lookupDocuments(pathnames) { return (result); } +/** + * Gets details about pages that are indexed filtered by path + * @param {String} filter return only pages that start with this filter + */ + +export async function getSearchIndex(filter) { + if (!window.pageIndex) { + const resp = await fetch(`${window.hlx.codeBasePath}/query-index.json`); + const json = await resp.json(); + window.pageIndex = { + data: json.data, + }; + } + + const result = window.pageIndex.data.filter((row) => row.path.startsWith(filter)); + return (result); +} + /** * Gets pdf and documents list that are indexed */ diff --git a/styles/styles.css b/styles/styles.css index a8f65e73..45a69c79 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -250,6 +250,7 @@ --section-width-tablet-large: 928px; --text-max-container: 648px; --sidebar-max-container: 360px; + --body-text-container: 840px; /* box shadow */ --box-shadow-large: 0 var(--spacer-element-02) var(--spacer-element-06) 0 rgb(185 181 174 / 40%); @@ -1074,7 +1075,8 @@ a.button span.icon.bookmark { /* Paragraph link styles */ main h2 a:not(.button):any-link, main p a:not(.button):any-link, -main ul a:not(.button):any-link { +main ul a:not(.button):any-link, +main table a:not(.button):any-link { text-decoration: none; color: var(--primary-purple); border-bottom: 1px solid var(--primary-purple); @@ -1087,9 +1089,11 @@ main h2 a:not(.button):any-link { main h2 a:not(.button):hover, main p a:not(.button):hover, main ul a:not(.button):hover, +main table a:not(.button):hover, main h2 a:not(.button):active, main p a:not(.button):active, -main ul a:not(.button):active { +main ul a:not(.button):active, +main table a:not(.button):active { background: var(--gradient-left-right); background-clip: text; -webkit-background-clip: text; diff --git a/tools/importer/import.js b/tools/importer/import-docs.js similarity index 100% rename from tools/importer/import.js rename to tools/importer/import-docs.js diff --git a/tools/importer/importpaths.txt b/tools/importer/import-paths.txt similarity index 100% rename from tools/importer/importpaths.txt rename to tools/importer/import-paths.txt diff --git a/tools/importer/import-support.js b/tools/importer/import-support.js new file mode 100644 index 00000000..e5b9fd9e --- /dev/null +++ b/tools/importer/import-support.js @@ -0,0 +1,154 @@ +/* eslint-disable no-undef */ +const createMetadataBlock = (main, document) => { + const meta = {}; + // add the template + meta.Template = 'Support'; + + // find the element + const title = document.querySelector('title'); + if (title) { + meta.Title = title.innerHTML.replace(/[\n\t]/gm, ''); + } + + const breadcrumb = document.querySelector('.breadcrumb.breadcrumb--primary'); + if (breadcrumb) { + meta.Breadcrumb = '/fragments/breadcrumbs/curam-support'; + breadcrumb.remove(); + } + + // find the <meta property="og:description"> element + // const desc = document.querySelector('[property="og:description"]'); + // if (desc) { + // meta.Description = desc.content; + // } + + // helper to create the metadata block + const block = WebImporter.Blocks.getMetadataBlock(document, meta); + + // append the block to the main element + main.append(block); + + // returning the meta object might be usefull to other rules + return meta; +}; + +export default { + transform: ({ + // eslint-disable-next-line no-unused-vars + document, + url, + }) => { + // Remove unnecessary parts of the content + WebImporter.DOMUtils.remove(document.body, ['header', 'footer']); + const main = document.body; + const results = []; + + // Remove other stuff that shows up in the page + const skipToContent = main.querySelector('.button--skipToContent'); + if (skipToContent) skipToContent.remove(); + main.querySelectorAll('iframe').forEach((el) => el.remove()); + main.querySelector('div#onetrust-consent-sdk')?.remove(); + if (main.querySelector('.cmp-pdfbasicinfo__action-container')) main.querySelector('.cmp-pdfbasicinfo__action-container').remove(); + + // move title from span to H1 + const title = main.querySelector('span.cmp-text__heading-h1'); + if (title) { + const h1 = document.createElement('H1'); + h1.innerHTML += title.innerHTML; + main.prepend(h1); + // remove elements already added to blocks from main + title.remove(); + } + + const headingTwos = main.querySelectorAll('span.cmp-text__heading-h2'); + if (headingTwos.length > 0) { + headingTwos.forEach((h2) => { + const newH2 = document.createElement('H2'); + newH2.innerHTML += h2.innerHTML; + h2.replaceWith(newH2); + }); + } + + const headingThrees = main.querySelectorAll('span.cmp-text__heading-h3'); + if (headingThrees.length > 0) { + headingThrees.forEach((h3) => { + const newH3 = document.createElement('H3'); + newH3.innerHTML += h3.innerHTML; + h3.replaceWith(newH3); + }); + } + + // Handle Tables from the source content + const tables = main.querySelectorAll('table'); + if (tables.length > 0) { + tables.forEach((table) => { + const cells = [ + ['Table'], + [table.outerHTML], + ]; + const newTable = WebImporter.DOMUtils.createTable(cells, document); + table.parentElement.append(newTable); + table.remove(); + }); + } + + // Handle bold text + const boldText = main.querySelectorAll('span.cmp-text__body-small-regular, span.cmp-text__body-normal-medium'); + if (boldText.length > 0) { + boldText.forEach((txt) => { + const newText = document.createElement('STRONG'); + newText.innerHTML += txt.innerHTML; + txt.replaceWith(newText); + }); + } + + // Need this to fix issue where b>span does not translate well like span>b + const boldSpans = main.querySelectorAll('b span'); + if (boldSpans.length > 0) { + boldSpans.forEach((boldSpan) => { + boldSpan.innerHTML = `<b>${boldSpan.textContent.trim()}</b> `; + if (boldSpan.parentElement) boldSpan.parentElement.replaceWith(boldSpan); + }); + } + + // Fix empty strong tags + const strongs = main.querySelectorAll('strong'); + if (strongs.length > 0) { + strongs.forEach((s) => { + if (!s.textContent) s.remove(); + }); + } + + // Check if the page has a Document Information right nav and import it as a block instead + const documentInfoSpan = main.querySelector('span.cmp-text__eyebrow-eyebrow'); + if (documentInfoSpan && documentInfoSpan.innerHTML === 'Document Information') { + // Update the text to H5 + const newDocInfoSpan = document.createElement('H5'); + newDocInfoSpan.innerHTML += documentInfoSpan.innerHTML.trim(); + documentInfoSpan.parentElement.prepend(newDocInfoSpan); + documentInfoSpan.remove(); + const docInfo = newDocInfoSpan.parentElement.parentElement; + if (docInfo) { + // Add Right Nav block + const cells = [ + ['Right Nav'], + [docInfo.innerHTML], + ]; + const table = WebImporter.DOMUtils.createTable(cells, document); + main.append(table); + // remove elements already added to blocks from main + docInfo.remove(); + } + } + + createMetadataBlock(main, document); + + // main page import - "element" is provided, i.e. a docx will be created + results.push({ + element: main, + path: new URL(url).pathname, + }); + + return results; + }, +};