From 5b25a3f5cd7be4499f4d21c0bbdbc0194546dd0d Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 23 Nov 2023 11:50:32 +0100 Subject: [PATCH 01/17] Style blog post pages and autoblocking --- .eslintignore | 3 ++- scripts/scripts.js | 27 ++++++++++++++++++++++++++- styles/styles.css | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 158f7d56..944d9b0a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ helix-importer-ui /scripts/alloy*.js -/tools/actions/ \ No newline at end of file +/tools/actions/ +/tools/importer \ No newline at end of file diff --git a/scripts/scripts.js b/scripts/scripts.js index 6c2789c3..0eec9ae4 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -44,13 +44,38 @@ function buildHeroBlock(main) { } } +function buildBlockPostPage(main) { + // Below h1 + const h1 = main.querySelector('h1'); + const socialMediaButtons = document.createRange().createContextualFragment('
Sharing Block Placeholder
'); + if (h1) { + h1.parentElement.insertBefore(socialMediaButtons.cloneNode(true), h1.nextSibling); + } + + // Below last content + const lastContentSection = main.querySelector('* > div:last-of-type'); + if (lastContentSection) { + lastContentSection.appendChild(socialMediaButtons.cloneNode(true)); + + const lostPetTeaser = document.createRange().createContextualFragment('
Lost Pet Teaser Block Placeholder
'); + lastContentSection.appendChild(lostPetTeaser); + + const similarBlogs = document.createRange().createContextualFragment('
Similar Blogs Block Placeholder
'); + lastContentSection.appendChild(similarBlogs); + } +} + /** * Builds all synthetic blocks in a container element. * @param {Element} main The container element */ function buildAutoBlocks(main) { try { - buildHeroBlock(main); + if (!document.body.classList.contains('blog-post')) { + buildHeroBlock(main); + } else { + buildBlockPostPage(main); + } } catch (error) { // eslint-disable-next-line no-console console.error('Auto Blocking failed', error); diff --git a/styles/styles.css b/styles/styles.css index ca7fb789..b92ef0fc 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -380,6 +380,27 @@ main .section.highlight p { margin-bottom: 0; } +/* Blog post styles */ +body.blog-post main h1, body.blog-post main h2, body.blog-post main h3 { + text-align: left; +} + +body.blog-post main h1 { + font-size: 2.1875rem; + line-height: 2.813rem; + font-weight: 300; +} + +body.blog-post main h2 { + font-size: 1.625rem; + line-height: 2.25rem; +} + +body.blog-post .default-content-wrapper { + font-size: 1.125rem; + padding: 0 0.9375rem; +} + main .section.highlight p em { display: block; font-size: 2.5rem; From c3fd5dd9f92ac5909ac7c7b082e07f1b2a810b66 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 23 Nov 2023 11:56:10 +0100 Subject: [PATCH 02/17] Add blog author --- scripts/scripts.js | 2 ++ styles/styles.css | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/scripts/scripts.js b/scripts/scripts.js index 0eec9ae4..0e936312 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -48,7 +48,9 @@ function buildBlockPostPage(main) { // Below h1 const h1 = main.querySelector('h1'); const socialMediaButtons = document.createRange().createContextualFragment(''); + const author = document.createRange().createContextualFragment('

By Maranda Elswick, DVM

'); if (h1) { + h1.parentElement.insertBefore(author, h1.nextSibling); h1.parentElement.insertBefore(socialMediaButtons.cloneNode(true), h1.nextSibling); } diff --git a/styles/styles.css b/styles/styles.css index b92ef0fc..0b4884c1 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -401,6 +401,12 @@ body.blog-post .default-content-wrapper { padding: 0 0.9375rem; } +body.blog-post .default-content-wrapper p.author { + font-family: var(--heading-font-family); + font-size: 1.3125rem; + font-weight: 300; +} + main .section.highlight p em { display: block; font-size: 2.5rem; From 2aaf285166282003622c0605e01635671c3b1cd6 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 23 Nov 2023 12:02:57 +0100 Subject: [PATCH 03/17] Update section style for mobile --- styles/styles.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/styles/styles.css b/styles/styles.css index 0b4884c1..f2861119 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -381,6 +381,10 @@ main .section.highlight p { } /* Blog post styles */ +body.blog-post main .section { + padding: 0.9375rem; +} + body.blog-post main h1, body.blog-post main h2, body.blog-post main h3 { text-align: left; } @@ -396,7 +400,7 @@ body.blog-post main h2 { line-height: 2.25rem; } -body.blog-post .default-content-wrapper { +body.blog-post main .section > div { font-size: 1.125rem; padding: 0 0.9375rem; } @@ -421,6 +425,10 @@ main .section.heading-light h2 { } @media (min-width: 768px) { + body.blog-post main .section { + padding: 1.875rem; + } + main .section.highlight p { font-size: 1.625rem; line-height: 2.25rem; From f707bca2ce9e8dec30b5b0e888b1fdb0fa4c76ef Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 23 Nov 2023 13:45:45 +0100 Subject: [PATCH 04/17] Use fragment for blog footer --- blocks/fragment/fragment.css | 14 +++++++++ blocks/fragment/fragment.js | 55 ++++++++++++++++++++++++++++++++++++ scripts/scripts.js | 11 ++++---- 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 blocks/fragment/fragment.css create mode 100644 blocks/fragment/fragment.js diff --git a/blocks/fragment/fragment.css b/blocks/fragment/fragment.css new file mode 100644 index 00000000..46bab39e --- /dev/null +++ b/blocks/fragment/fragment.css @@ -0,0 +1,14 @@ +/* suppress nested section padding */ + +.fragment-wrapper > .section { + padding-left: 0; + padding-right: 0; +} + +.fragment-wrapper > .section:first-of-type { + padding-top: 0; +} + +.fragment-wrapper > .section:last-of-type { + padding-bottom: 0; +} diff --git a/blocks/fragment/fragment.js b/blocks/fragment/fragment.js new file mode 100644 index 00000000..b03b8f0d --- /dev/null +++ b/blocks/fragment/fragment.js @@ -0,0 +1,55 @@ +/* + * Fragment Block + * Include content on a page as a fragment. + * https://www.aem.live/developer/block-collection/fragment + */ + +import { + decorateMain, +} from '../../scripts/scripts.js'; + +import { + loadBlocks, +} from '../../scripts/lib-franklin.js'; + +/** + * Loads a fragment. + * @param {string} path The path to the fragment + * @returns {HTMLElement} The root element of the fragment + */ +export async function loadFragment(path) { + if (path && path.startsWith('/')) { + const resp = await fetch(`${path}.plain.html`); + if (resp.ok) { + const main = document.createElement('main'); + main.innerHTML = await resp.text(); + + // reset base path for media to fragment base + const resetAttributeBase = (tag, attr) => { + main.querySelectorAll(`${tag}[${attr}^="./media_"]`).forEach((elem) => { + elem[attr] = new URL(elem.getAttribute(attr), new URL(path, window.location)).href; + }); + }; + resetAttributeBase('img', 'src'); + resetAttributeBase('source', 'srcset'); + + decorateMain(main); + await loadBlocks(main); + return main; + } + } + return null; +} + +export default async function decorate(block) { + const link = block.querySelector('a'); + const path = link ? link.getAttribute('href') : block.textContent.trim(); + const fragment = await loadFragment(path); + if (fragment) { + const fragmentSection = fragment.querySelector(':scope .section'); + if (fragmentSection) { + block.closest('.section').classList.add(...fragmentSection.classList); + block.closest('.fragment-wrapper').replaceWith(...fragmentSection.childNodes); + } + } +} diff --git a/scripts/scripts.js b/scripts/scripts.js index 0e936312..2264d269 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -59,11 +59,8 @@ function buildBlockPostPage(main) { if (lastContentSection) { lastContentSection.appendChild(socialMediaButtons.cloneNode(true)); - const lostPetTeaser = document.createRange().createContextualFragment('
Lost Pet Teaser Block Placeholder
'); - lastContentSection.appendChild(lostPetTeaser); - - const similarBlogs = document.createRange().createContextualFragment('
Similar Blogs Block Placeholder
'); - lastContentSection.appendChild(similarBlogs); + const fragment = document.createRange().createContextualFragment('
/fragments/blog-footer
'); + lastContentSection.parentElement.appendChild(fragment); } } @@ -73,6 +70,10 @@ function buildBlockPostPage(main) { */ function buildAutoBlocks(main) { try { + if (main.parentNode !== document.body) { + return; + } + if (!document.body.classList.contains('blog-post')) { buildHeroBlock(main); } else { From cceb7c9595e62f4a38f879aa9ea4b406147b5a74 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 23 Nov 2023 16:44:41 +0100 Subject: [PATCH 05/17] Add styles for columns teaser --- blocks/columns/columns.css | 51 +++++++++++++++++++++++++++++++++++++- styles/styles.css | 11 ++++---- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index e4bb6a05..ee3aa30a 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -32,6 +32,48 @@ text-align: center; } +.columns.teaser { + background-color: #fff1d6; + border-radius: 0.625rem; + overflow: hidden; +} + +.columns.teaser > div { + padding: 0; +} + +.columns.teaser p a.button.primary { + width: 100%; +} + +.columns.teaser > div > .columns-img-col { + order: 1; +} + +.columns.teaser > div > div { + font-size: 1rem; + line-height: 1.5625rem; +} + +.columns.teaser > div > div:last-child { + margin-bottom: 1.25rem; + padding-left: 0.9375rem; + padding-right: 0.9375rem; +} + +.columns.teaser p.button-container { + margin: 0; +} + +.columns.teaser p.button-container a.button { + margin: 0.3125rem auto; +} + +.columns.teaser > div > div h3 { + font-size: 1.3125rem; + line-height: 1.938rem; +} + @media (min-width: 768px) { .columns > div { align-items: center; @@ -44,7 +86,6 @@ .columns > div > div { flex: 1; order: unset; - } .columns > div > .columns-img-col { @@ -54,4 +95,12 @@ .columns h2 { text-align: left; } + + .columns.teaser > div > div:last-child { + padding: 3.125rem; + } + + .columns.teaser > div > .columns-img-col { + order: unset; + } } diff --git a/styles/styles.css b/styles/styles.css index f2861119..08087e51 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -385,6 +385,12 @@ body.blog-post main .section { padding: 0.9375rem; } +body.blog-post main .section > div { + margin: 1.25rem auto; + font-size: 1.125rem; + padding: 0 0.9375rem; +} + body.blog-post main h1, body.blog-post main h2, body.blog-post main h3 { text-align: left; } @@ -400,11 +406,6 @@ body.blog-post main h2 { line-height: 2.25rem; } -body.blog-post main .section > div { - font-size: 1.125rem; - padding: 0 0.9375rem; -} - body.blog-post .default-content-wrapper p.author { font-family: var(--heading-font-family); font-size: 1.3125rem; From 7669647cb226bb7ca63c4b6c8f30c062cadd6d5d Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 10:47:09 +0100 Subject: [PATCH 06/17] Update cards component to fetch blog posts from query --- blocks/cards/cards.js | 89 ++++++++++++++++++++++++++++--------------- styles/styles.css | 8 +++- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index ec701b82..1062636a 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -1,12 +1,28 @@ import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; -const host = 'https://www.24petwatch.com'; -const sitesearchUrl = `https://${window.location.hostname.includes('aem-stage') ? 'aem-stage' : 'www'}.24petwatch.com/bin/24pethealth/sitesearch.json`; -const sitesearchPayload = JSON.stringify({ - context: '/content/24petwatch/us/en/blog/jcr:content/root/container/container_177885842/container_51910998/contentsearchresults', - resultsPerPage: 3, - requestedPage: 1, -}); +// eslint-disable-next-line no-unused-vars +const fetchBlogPosts = async (page = 1, tags = [], pagesize = 9) => { + let index = new URL('/blog/query-index.json', window.location.origin); + if (!window.location.hostname.includes('24petwatch.com')) { + index = new URL('https://main--24petwatch--hlxsites.hlx.live/blog/query-index.json'); + } + + const limit = pagesize; + const offset = (page - 1) * pagesize; + index.searchParams.set('limit', limit); + index.searchParams.set('offset', offset); + + const response = await fetch(index); + const json = await response.json(); + + // TODO: Filter by tags once available in index + + return { + items: json.data, + pages: Math.ceil(json.total / pagesize), + currentPage: Math.max(1, page, Math.ceil(json.total / pagesize)), + }; +}; function wrapInAnchor(element, href) { const anchor = document.createElement('a'); @@ -17,33 +33,45 @@ function wrapInAnchor(element, href) { } function createBlogCard(item = {}) { - const blogThumbnail = `${host}${item.url.replace('.html', '')}.thumb.319.319.png`; - const blogUrl = `${host}${item.url.substring(item.url.indexOf('/blog')).replace('.html', '')}`; + let { title, image } = item; + const { path, description } = item; - return `
- ${item.name} -
-
-

${item.name}

-

${item.description}

-

Read more

-
`; + try { + if (image === '0') { + throw new Error('invalid'); + } + image = new URL(image, window.location); + } catch (e) { + // TODO: Dummy image until images are available in index + image = new URL('https://www.24petwatch.com/content/24petwatch/us/en/blog/gifts-for-dog-lovers-2023.thumb.319.319.png'); + } + if (title.startsWith('24Petwatch: ')) { + title = title.replace('24Petwatch: ', ''); + } + + return document.createRange().createContextualFragment(` +
+ + ${title} + +
+
+

${title}

+

${description}

+

+ Read more +

+
+ `); } async function populateBlogTeaser(block) { - const response = await fetch(sitesearchUrl, { - method: 'POST', - body: sitesearchPayload, - headers: { - 'Content-Type': 'application/json', - }, - }); - const blogItems = await response.json(); - - (blogItems.results || []).forEach((item) => { + const tags = Array.from(document.head.querySelectorAll('meta[property="article:tag"]')).map((m) => m.getAttribute('content')); + const response = await fetchBlogPosts(1, tags, 3); + response.items.forEach((item) => { const card = document.createElement('div'); - card.innerHTML = createBlogCard(item); - block.append(card); + card.appendChild(createBlogCard(item)); + block.appendChild(card); }); } @@ -74,7 +102,8 @@ export default async function decorate(block) { }); [...ul.querySelectorAll('img')] - .filter((img) => !(img.src || '').startsWith('http')) // do not optimize absolute images for now + // TODO: Do not optimize absolute images for now + .filter((img) => !(img.src || '').startsWith('http')) .forEach((img) => img.closest('picture').replaceWith(createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }]))); block.textContent = ''; block.append(ul); diff --git a/styles/styles.css b/styles/styles.css index 08087e51..4baa535b 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -235,7 +235,13 @@ a:hover { color: var(--link-hover-color); } -a[href*="//"]:not([href^="https://www.24petwatch.com"]):not([href^="https://24petwatch.com"]):not([href*="tel:"]):not([href^=localhost]):after { +a[href*="//"] + :not([href^="https://www.24petwatch.com"]) + :not([href^="https://24petwatch.com"]) + :not([href*="tel:"]) + :not([href^=localhost]) + :not(.cards a) + :after { font-family: 'Font Awesome 6 Pro', sans-serif; content: ""; margin:.3rem; From 2ce884298874495fc7e0a7cd77f0fdc0147a99be Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 11:03:44 +0100 Subject: [PATCH 07/17] Make blog post links absolute --- blocks/cards/cards.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 1062636a..8bbc94c9 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -2,6 +2,7 @@ import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; // eslint-disable-next-line no-unused-vars const fetchBlogPosts = async (page = 1, tags = [], pagesize = 9) => { + // TODO: Fetch whole index and cache it in sessionStorage to enable filtering and fulltext search let index = new URL('/blog/query-index.json', window.location.origin); if (!window.location.hostname.includes('24petwatch.com')) { index = new URL('https://main--24petwatch--hlxsites.hlx.live/blog/query-index.json'); @@ -33,9 +34,12 @@ function wrapInAnchor(element, href) { } function createBlogCard(item = {}) { - let { title, image } = item; - const { path, description } = item; + let { title, image, path } = item; + const { description } = item; + if (!window.location.hostname.includes('24petwatch.com')) { + path = new URL(path, 'https://www.24petwatch.com').toString(); + } try { if (image === '0') { throw new Error('invalid'); From 209dcca605b485ec178a1b0b030de496c1220e2e Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 11:48:31 +0100 Subject: [PATCH 08/17] Handle canada blog URLs and author metadata --- blocks/cards/cards.js | 10 ++++++---- scripts/scripts.js | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 8bbc94c9..002af680 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -1,11 +1,13 @@ -import { createOptimizedPicture } from '../../scripts/lib-franklin.js'; +import { createOptimizedPicture, getMetadata } from '../../scripts/lib-franklin.js'; + +const isCanada = window.location.pathname.startsWith('/ca/'); // eslint-disable-next-line no-unused-vars const fetchBlogPosts = async (page = 1, tags = [], pagesize = 9) => { // TODO: Fetch whole index and cache it in sessionStorage to enable filtering and fulltext search - let index = new URL('/blog/query-index.json', window.location.origin); + let index = new URL(`${isCanada ? '/ca' : ''}/blog/query-index.json`, window.location.origin); if (!window.location.hostname.includes('24petwatch.com')) { - index = new URL('https://main--24petwatch--hlxsites.hlx.live/blog/query-index.json'); + index = new URL(`https://main--24petwatch--hlxsites.hlx.live${isCanada ? '/ca' : ''}/blog/query-index.json`); } const limit = pagesize; @@ -70,7 +72,7 @@ function createBlogCard(item = {}) { } async function populateBlogTeaser(block) { - const tags = Array.from(document.head.querySelectorAll('meta[property="article:tag"]')).map((m) => m.getAttribute('content')); + const tags = getMetadata('article:tag').split(', '); const response = await fetchBlogPosts(1, tags, 3); response.items.forEach((item) => { const card = document.createElement('div'); diff --git a/scripts/scripts.js b/scripts/scripts.js index 2264d269..c3b2cf10 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -48,9 +48,13 @@ function buildBlockPostPage(main) { // Below h1 const h1 = main.querySelector('h1'); const socialMediaButtons = document.createRange().createContextualFragment(''); - const author = document.createRange().createContextualFragment('

By Maranda Elswick, DVM

'); + if (h1) { - h1.parentElement.insertBefore(author, h1.nextSibling); + const author = getMetadata('author'); + if (author) { + const authorElem = document.createRange().createContextualFragment(`

${author}

`); + h1.parentElement.insertBefore(authorElem, h1.nextSibling); + } h1.parentElement.insertBefore(socialMediaButtons.cloneNode(true), h1.nextSibling); } From 99f37359565fae66ecdfec157be0872f1ae5eedc Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 11:51:01 +0100 Subject: [PATCH 09/17] Update blog post author --- scripts/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/scripts.js b/scripts/scripts.js index c3b2cf10..106d2de9 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -52,7 +52,7 @@ function buildBlockPostPage(main) { if (h1) { const author = getMetadata('author'); if (author) { - const authorElem = document.createRange().createContextualFragment(`

${author}

`); + const authorElem = document.createRange().createContextualFragment(`

By ${author}

`); h1.parentElement.insertBefore(authorElem, h1.nextSibling); } h1.parentElement.insertBefore(socialMediaButtons.cloneNode(true), h1.nextSibling); From 59b5bfb0385f0fc027fb9d014c389a773a95709e Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 12:02:49 +0100 Subject: [PATCH 10/17] Update author display logic --- scripts/scripts.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/scripts.js b/scripts/scripts.js index 106d2de9..bc11b582 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -50,11 +50,12 @@ function buildBlockPostPage(main) { const socialMediaButtons = document.createRange().createContextualFragment(''); if (h1) { - const author = getMetadata('author'); + const author = h1.parentElement.querySelector('h1 + p > em'); if (author) { - const authorElem = document.createRange().createContextualFragment(`

By ${author}

`); - h1.parentElement.insertBefore(authorElem, h1.nextSibling); + const authorElem = document.createRange().createContextualFragment(`

${author.innerText}

`); + author.parentElement.replaceWith(authorElem); } + h1.parentElement.insertBefore(socialMediaButtons.cloneNode(true), h1.nextSibling); } From 3aca3d4ce37bd0560901b037ff7fcdd0bf5022d8 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 13:26:01 +0100 Subject: [PATCH 11/17] Add sharethis buttons --- blocks/sharing/sharing.css | 3 +++ blocks/sharing/sharing.js | 3 +++ scripts/scripts.js | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 blocks/sharing/sharing.css create mode 100644 blocks/sharing/sharing.js diff --git a/blocks/sharing/sharing.css b/blocks/sharing/sharing.css new file mode 100644 index 00000000..4b0dd9cb --- /dev/null +++ b/blocks/sharing/sharing.css @@ -0,0 +1,3 @@ +.sharing { + min-height: 48px; +} \ No newline at end of file diff --git a/blocks/sharing/sharing.js b/blocks/sharing/sharing.js new file mode 100644 index 00000000..d6ced3b3 --- /dev/null +++ b/blocks/sharing/sharing.js @@ -0,0 +1,3 @@ +export default async function decorate(block) { + block.append(document.createRange().createContextualFragment('
')); +} diff --git a/scripts/scripts.js b/scripts/scripts.js index bc11b582..497abc34 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -47,7 +47,7 @@ function buildHeroBlock(main) { function buildBlockPostPage(main) { // Below h1 const h1 = main.querySelector('h1'); - const socialMediaButtons = document.createRange().createContextualFragment(''); + const socialMediaButtons = document.createRange().createContextualFragment(''); if (h1) { const author = h1.parentElement.querySelector('h1 + p > em'); From eac9f841c8b75fee40524786c7f9a553691f33c7 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 14:02:23 +0100 Subject: [PATCH 12/17] Add teasers to blog index page --- blocks/columns/columns.css | 25 ++++++++++++++++++++----- styles/styles.css | 3 ++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index ee3aa30a..c4ce5c46 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -38,7 +38,7 @@ overflow: hidden; } -.columns.teaser > div { +.columns.teaser > div, .columns.gray > div { padding: 0; } @@ -55,25 +55,40 @@ line-height: 1.5625rem; } -.columns.teaser > div > div:last-child { +.columns.teaser > div > div:last-child, .columns.gray > div > div:first-child { margin-bottom: 1.25rem; padding-left: 0.9375rem; padding-right: 0.9375rem; } -.columns.teaser p.button-container { +.columns.teaser p.button-container, .columns.gray p.button-container { margin: 0; } -.columns.teaser p.button-container a.button { +.columns.teaser p.button-container a.buttonm, .columns.gray p.button-container a.button { margin: 0.3125rem auto; } +.columns.gray p.button-container a.button { + display: inline-block; + border: 0.1875rem solid var(--text-color); + background: var(--background-color); + color: var(--text-color); +} + +.columns.gray p.button-container a.button:hover { + background: #d0dfe8; +} + .columns.teaser > div > div h3 { font-size: 1.3125rem; line-height: 1.938rem; } +.columns.gray { + background-color: #e7e9ea; +} + @media (min-width: 768px) { .columns > div { align-items: center; @@ -96,7 +111,7 @@ text-align: left; } - .columns.teaser > div > div:last-child { + .columns.teaser > div > div:last-child, .columns.gray > div > div:first-child { padding: 3.125rem; } diff --git a/styles/styles.css b/styles/styles.css index 4baa535b..4870c915 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -397,7 +397,8 @@ body.blog-post main .section > div { padding: 0 0.9375rem; } -body.blog-post main h1, body.blog-post main h2, body.blog-post main h3 { +body.blog-post main h1, body.blog-post main h2, body.blog-post main h3, body.blog-post main h4, +body.blog-index main h1, body.blog-index main h2, body.blog-index main h3, body.blog-index main h4 { text-align: left; } From e891fac1d4541233c0074c7bded28e201040095f Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 15:25:22 +0100 Subject: [PATCH 13/17] Add blog-grid style --- blocks/cards/cards.css | 26 +++++++++ blocks/cards/cards.js | 123 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 135 insertions(+), 14 deletions(-) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 07916ddb..0fcc2e81 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -1,3 +1,5 @@ +/* stylelint-disable no-descending-specificity */ + .cards { --heading-font-size-m: 1.125rem; } @@ -73,6 +75,30 @@ text-decoration: none; } +.cards-pagination ul { + list-style-type: none; + display: flex; + gap: 0.5rem; + justify-content: center; +} + +.cards-pagination ul li > a { + font-family: var(--body-font-family); + font-size: .75rem; + line-height: 1.313rem; + color: var(--text-color); + text-decoration: none; + border: 2px solid var(--text-color); + border-radius: 10px; + padding: 5px 10px; +} + +.cards-pagination ul li.active > a { + background-color: var(--text-color); + color: var(--background-color); + pointer-events: none; +} + @media (min-width: 768px) { .cards > ul { grid-template-columns: 1fr 1fr 1fr; diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 002af680..405830cb 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -2,28 +2,68 @@ import { createOptimizedPicture, getMetadata } from '../../scripts/lib-franklin. const isCanada = window.location.pathname.startsWith('/ca/'); -// eslint-disable-next-line no-unused-vars -const fetchBlogPosts = async (page = 1, tags = [], pagesize = 9) => { - // TODO: Fetch whole index and cache it in sessionStorage to enable filtering and fulltext search +async function loadBlogPosts() { let index = new URL(`${isCanada ? '/ca' : ''}/blog/query-index.json`, window.location.origin); if (!window.location.hostname.includes('24petwatch.com')) { index = new URL(`https://main--24petwatch--hlxsites.hlx.live${isCanada ? '/ca' : ''}/blog/query-index.json`); } + const chunkSize = 100; + const loadChunk = async (offset) => { + index.searchParams.set('limit', chunkSize); + index.searchParams.set('offset', offset); + + const response = await fetch(index); + const json = await response.json(); + + // Check if more has to be loaded + if (json.total > offset + chunkSize) { + return { + data: [...json.data, ...(await loadChunk(offset + 100)).data], + total: json.total, + }; + } + return { + data: json.data, + total: json.total, + }; + }; + + if (!window.blogPosts) { + window.blogPosts = await loadChunk(0); + } + return window.blogPosts; +} + +// eslint-disable-next-line no-unused-vars +const fetchBlogPosts = async (page = 1, tags = [], searchTerm = '', pagesize = 9) => { + let { data, total } = await loadBlogPosts(); - const limit = pagesize; - const offset = (page - 1) * pagesize; - index.searchParams.set('limit', limit); - index.searchParams.set('offset', offset); + // TODO filter by tags - const response = await fetch(index); - const json = await response.json(); + // Filter by search term + if (searchTerm) { + data = data + .filter(({ title, description }) => title.toLowerCase().includes(searchTerm.toLowerCase()) + || description.toLowerCase().includes(searchTerm.toLowerCase())); + total = data.length; + } - // TODO: Filter by tags once available in index + // Filter by page + const start = (page - 1) * pagesize; + const end = start + pagesize; + + let currentPage = page; + if (currentPage > Math.ceil(total / pagesize)) { + currentPage = Math.ceil(total / pagesize); + } + if (currentPage < 1) { + currentPage = 1; + } return { - items: json.data, - pages: Math.ceil(json.total / pagesize), - currentPage: Math.max(1, page, Math.ceil(json.total / pagesize)), + items: data.slice(start, end), + pages: Math.ceil(total / pagesize), + currentPage, }; }; @@ -71,9 +111,40 @@ function createBlogCard(item = {}) { `); } +function createPagination(block, pages, currentPage) { + let pageSet = new Set([1, pages, currentPage, currentPage - 1, currentPage + 1]); + pageSet = Array.from(pageSet) + .filter((a) => a > 0 && a <= pages) + .sort((a, b) => a - b); + + const onPaginate = (e) => { + const hrefPage = parseInt(new URL(e.target.href).searchParams.get('page'), 10); + const newUrl = new URL(window.location); + newUrl.searchParams.set('page', hrefPage); + window.history.pushState({}, '', newUrl.toString()); + e.preventDefault(); + // eslint-disable-next-line no-use-before-define + decorate(block); + }; + + const pagination = document.createRange().createContextualFragment(` +
+
    + ${currentPage > 1 ? `` : ''} + ${pageSet.map((p, index) => { + const item = index > 0 && p - pageSet[index - 1] > 1 ? '
  • ...
  • ' : ''; + return `${item}
  • ${p}
  • `; + }).join('')} + ${currentPage < pages ? `` : ''} +
+
`); + block.closest('.cards-wrapper').appendChild(pagination); + block.closest('.cards-wrapper').querySelectorAll('.cards-pagination a').forEach((a) => a.addEventListener('click', onPaginate)); +} + async function populateBlogTeaser(block) { const tags = getMetadata('article:tag').split(', '); - const response = await fetchBlogPosts(1, tags, 3); + const response = await fetchBlogPosts(1, tags, '', 3); response.items.forEach((item) => { const card = document.createElement('div'); card.appendChild(createBlogCard(item)); @@ -81,12 +152,36 @@ async function populateBlogTeaser(block) { }); } +async function populateBlogGrid(block) { + const searchParams = new URLSearchParams(window.location.search); + const page = parseInt(searchParams.get('page'), 10) || 1; + const searchTerm = searchParams.get('search') || ''; + const { items, pages, currentPage } = await fetchBlogPosts(page, [], searchTerm.replace(/[^a-zA-Z0-9 ]/g, ''), 9); + items.forEach((item) => { + const card = document.createElement('div'); + card.appendChild(createBlogCard(item)); + block.appendChild(card); + }); + + // TODO: Display search box + // TODO: Filter by tags + createPagination(block, pages, currentPage); +} + export default async function decorate(block) { + // TODO: Clean const isBlogTeaser = block.classList.contains('blog-teaser'); if (isBlogTeaser) { await populateBlogTeaser(block); } + const isBlogGrid = block.classList.contains('blog-grid'); + if (isBlogGrid) { + block.textContent = ''; + block.closest('.cards-wrapper').querySelector('.cards-pagination')?.remove(); + await populateBlogGrid(block); + } + /* change to ul, li */ const ul = document.createElement('ul'); [...block.children].forEach((row) => { From 00a2d2d787b1932666cd0ffa554df10796f74af7 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 15:50:49 +0100 Subject: [PATCH 14/17] Implement blog search --- blocks/cards/cards.css | 19 +++++++++++++++++++ blocks/cards/cards.js | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 0fcc2e81..1341d2c1 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -79,6 +79,7 @@ list-style-type: none; display: flex; gap: 0.5rem; + padding: 0; justify-content: center; } @@ -99,6 +100,24 @@ pointer-events: none; } +.cards-searchbar { + padding: 0.9375rem; + box-shadow: 0 4px 3px #00000040; + border-radius: 10px; + margin-bottom: 40px; +} + +.cards-searchbar label { + display: none; +} + +.cards-searchbar input { + max-width: unset; + border: 0; + border-radius: 0; + border-bottom: 2px solid var(--text-color); +} + @media (min-width: 768px) { .cards > ul { grid-template-columns: 1fr 1fr 1fr; diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 405830cb..84ca6a13 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -142,6 +142,35 @@ function createPagination(block, pages, currentPage) { block.closest('.cards-wrapper').querySelectorAll('.cards-pagination a').forEach((a) => a.addEventListener('click', onPaginate)); } +function createSearchBox(block, searchTerm) { + const onSubmit = (e) => { + const newSearchTerm = e.target.querySelector('input').value; + const newUrl = new URL(window.location); + if (newSearchTerm !== '') { + newUrl.searchParams.set('search', newSearchTerm); + } else { + newUrl.searchParams.delete('search'); + } + newUrl.searchParams.set('page', 1); + window.history.pushState({}, '', newUrl.toString()); + e.preventDefault(); + // eslint-disable-next-line no-use-before-define + decorate(block); + }; + + const searchbar = document.createRange().createContextualFragment(` + + `); + block.closest('.cards-wrapper').prepend(searchbar); + block.closest('.cards-wrapper').querySelector('.cards-searchbar form').addEventListener('submit', onSubmit); +} + async function populateBlogTeaser(block) { const tags = getMetadata('article:tag').split(', '); const response = await fetchBlogPosts(1, tags, '', 3); @@ -163,13 +192,19 @@ async function populateBlogGrid(block) { block.appendChild(card); }); - // TODO: Display search box + if (items.length === 0) { + block.closest('.cards-wrapper').prepend(document.createRange().createContextualFragment(` +

Sorry, there are no results that match your search

+

Please check your spelling or try again using different keywords

+ `)); + } + // TODO: Filter by tags + createSearchBox(block, searchTerm); createPagination(block, pages, currentPage); } export default async function decorate(block) { - // TODO: Clean const isBlogTeaser = block.classList.contains('blog-teaser'); if (isBlogTeaser) { await populateBlogTeaser(block); @@ -178,7 +213,7 @@ export default async function decorate(block) { const isBlogGrid = block.classList.contains('blog-grid'); if (isBlogGrid) { block.textContent = ''; - block.closest('.cards-wrapper').querySelector('.cards-pagination')?.remove(); + block.closest('.cards-wrapper').querySelectorAll(':scope > *:not(.block)').forEach((e) => e.remove()); await populateBlogGrid(block); } @@ -189,7 +224,6 @@ export default async function decorate(block) { li.innerHTML = row.innerHTML; [...li.children].forEach((div) => { const href = li.querySelector('a')?.href; - if (div.children.length === 1 && div.querySelector('picture')) { div.className = 'cards-card-image'; wrapInAnchor(div, href); From d3718c1d279925fd7712330a173afa733acc7136 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 16:28:26 +0100 Subject: [PATCH 15/17] Add tag filter for blog teaser --- blocks/cards/cards.css | 1 + blocks/cards/cards.js | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 1341d2c1..ef24a0de 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -66,6 +66,7 @@ } .cards > ul > li img { + display: inline-block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 84ca6a13..ed58d487 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -38,7 +38,11 @@ async function loadBlogPosts() { const fetchBlogPosts = async (page = 1, tags = [], searchTerm = '', pagesize = 9) => { let { data, total } = await loadBlogPosts(); - // TODO filter by tags + // Filter by tags + if (tags.length > 0) { + data = data.filter(({ tags: blogTag }) => tags.some((t) => blogTag.includes(t))); + total = data.length; + } // Filter by search term if (searchTerm) { @@ -83,14 +87,11 @@ function createBlogCard(item = {}) { path = new URL(path, 'https://www.24petwatch.com').toString(); } try { - if (image === '0') { - throw new Error('invalid'); - } image = new URL(image, window.location); - } catch (e) { - // TODO: Dummy image until images are available in index - image = new URL('https://www.24petwatch.com/content/24petwatch/us/en/blog/gifts-for-dog-lovers-2023.thumb.319.319.png'); - } + image.hostname = window.location.hostname; + image.port = window.location.port; + image.protocol = window.location.protocol; + } catch (e) { /* ignore */ } if (title.startsWith('24Petwatch: ')) { title = title.replace('24Petwatch: ', ''); } @@ -237,8 +238,6 @@ export default async function decorate(block) { }); [...ul.querySelectorAll('img')] - // TODO: Do not optimize absolute images for now - .filter((img) => !(img.src || '').startsWith('http')) .forEach((img) => img.closest('picture').replaceWith(createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }]))); block.textContent = ''; block.append(ul); From b73af7cd518b4328808dbb50d12ebaf3a2119484 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Fri, 24 Nov 2023 17:10:44 +0100 Subject: [PATCH 16/17] Add tag filtering --- blocks/cards/cards.css | 42 +++++++++++++++++++++++++++++++++ blocks/cards/cards.js | 51 +++++++++++++++++++++++++++++++++++++++-- icons/arrow-down.png | Bin 0 -> 281 bytes 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 icons/arrow-down.png diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index ef24a0de..9cdc7b83 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -119,6 +119,48 @@ border-bottom: 2px solid var(--text-color); } +.cards-filterselect { + display: flex; + gap: 0.5rem; + flex-direction: column; +} + +.cards-filterselect .total, .cards-filterselect label { + font-family: var(--heading-font-family); + font-weight: 700; + line-height: 1.625rem; +} + +.cards-filterselect select { + appearance: none; + background-color: transparent; + border: none; + padding: 1rem 1rem 1rem 0; + width: 100%; + font-size: 18px; +} + +.cards-filterselect .select-group { + display: flex; + align-items: center; + cursor: pointer; + border-bottom: 2px solid var(--text-color); + max-width: 300px; + margin: 0 0 2rem; +} + +.cards-filterselect .select-group::after { + content: ''; + justify-self: end; + width: 11px; + height: 11px; + margin-top: 13px; + background-image: url('/icons/arrow-down.png'); + background-repeat: no-repeat; + background-size: contain; + padding-bottom: 1rem; +} + @media (min-width: 768px) { .cards > ul { grid-template-columns: 1fr 1fr 1fr; diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index ed58d487..3381ac41 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -2,6 +2,16 @@ import { createOptimizedPicture, getMetadata } from '../../scripts/lib-franklin. const isCanada = window.location.pathname.startsWith('/ca/'); +async function getTagFilters() { + let index = new URL(`${isCanada ? '/ca' : ''}/blog/tag-filters.json`, window.location.origin); + if (!window.location.hostname.includes('24petwatch.com')) { + index = new URL(`https://main--24petwatch--hlxsites.hlx.page${isCanada ? '/ca' : ''}/blog/tag-filters.json`); // TODO .live + } + const response = await fetch(index); + const json = await response.json(); + return json.data; +} + async function loadBlogPosts() { let index = new URL(`${isCanada ? '/ca' : ''}/blog/query-index.json`, window.location.origin); if (!window.location.hostname.includes('24petwatch.com')) { @@ -68,6 +78,7 @@ const fetchBlogPosts = async (page = 1, tags = [], searchTerm = '', pagesize = 9 items: data.slice(start, end), pages: Math.ceil(total / pagesize), currentPage, + total, }; }; @@ -172,6 +183,38 @@ function createSearchBox(block, searchTerm) { block.closest('.cards-wrapper').querySelector('.cards-searchbar form').addEventListener('submit', onSubmit); } +async function createFilterSelect(block, total, currentTag) { + const tags = await getTagFilters(); + + const onChange = (e) => { + const newUrl = new URL(window.location); + if (e.target.value !== '') { + newUrl.searchParams.set('tag', e.target.value); + } else { + newUrl.searchParams.delete('tag'); + } + newUrl.searchParams.set('page', 1); + window.history.pushState({}, '', newUrl.toString()); + // eslint-disable-next-line no-use-before-define + decorate(block); + }; + + const filterselect = document.createRange().createContextualFragment(` +
+
${total} Results
+ +
+ +
+
+ `); + block.closest('.cards-wrapper').insertBefore(filterselect, block.closest('.cards-wrapper').querySelector('.block')); + block.closest('.cards-wrapper').querySelector('.cards-filterselect select').addEventListener('change', onChange); +} + async function populateBlogTeaser(block) { const tags = getMetadata('article:tag').split(', '); const response = await fetchBlogPosts(1, tags, '', 3); @@ -186,7 +229,10 @@ async function populateBlogGrid(block) { const searchParams = new URLSearchParams(window.location.search); const page = parseInt(searchParams.get('page'), 10) || 1; const searchTerm = searchParams.get('search') || ''; - const { items, pages, currentPage } = await fetchBlogPosts(page, [], searchTerm.replace(/[^a-zA-Z0-9 ]/g, ''), 9); + const tags = (searchParams.get('tag') ? [searchParams.get('tag')] : []).map((t) => t.replace(/[^a-z0-9-]/g, '')); + const { + items, pages, currentPage, total, + } = await fetchBlogPosts(page, tags, searchTerm.replace(/[^a-zA-Z0-9 ]/g, ''), 9); items.forEach((item) => { const card = document.createElement('div'); card.appendChild(createBlogCard(item)); @@ -198,9 +244,10 @@ async function populateBlogGrid(block) {

Sorry, there are no results that match your search

Please check your spelling or try again using different keywords

`)); + } else { + createFilterSelect(block, total, tags.length > 0 ? tags[0] : null); } - // TODO: Filter by tags createSearchBox(block, searchTerm); createPagination(block, pages, currentPage); } diff --git a/icons/arrow-down.png b/icons/arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..c99f1f370b693b22ca139b9002033b5a5d0ed84b GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0vp^+(695!3HFgJ}hYlQk(@Ik;Oo9VGw3ym^DWND9BhG ziLvTSm{a4vbx+*Ge_j*W(E*Y>{7bBSQO<*`iaNB)e&hHF}aMqWRY z{@Tu0Pu4Tv_Pr@%hth>@;zl9YAH6s<<#Z~03B$z|(){1zLgW1Y&X{=8sQiGFK$(<< z{Y%5MQ+HLH?U>qH#NIT)HGf4hbGlfR`@3766J}mp@jm;|uQc&`Gl>`G5BvE!<^Nyf YwtwPmQRw@)2 Date: Fri, 24 Nov 2023 17:13:10 +0100 Subject: [PATCH 17/17] Use tag filters from live --- blocks/cards/cards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 3381ac41..3855beb9 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -5,7 +5,7 @@ const isCanada = window.location.pathname.startsWith('/ca/'); async function getTagFilters() { let index = new URL(`${isCanada ? '/ca' : ''}/blog/tag-filters.json`, window.location.origin); if (!window.location.hostname.includes('24petwatch.com')) { - index = new URL(`https://main--24petwatch--hlxsites.hlx.page${isCanada ? '/ca' : ''}/blog/tag-filters.json`); // TODO .live + index = new URL(`https://main--24petwatch--hlxsites.hlx.live${isCanada ? '/ca' : ''}/blog/tag-filters.json`); } const response = await fetch(index); const json = await response.json();