Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add blog overview page #93

Merged
merged 12 commits into from
Nov 28, 2022
89 changes: 89 additions & 0 deletions blocks/blog-posts/blog-card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
.blog-cards-container h4 {
font-weight: 400;
font-size: var(--heading-font-size-xl);
line-height: 46px;
color: var(--c-light-teal);
margin-bottom: 25px;
}

.blog-cards-container .related-list-item {
background-color: var(--c-white);
padding: 10px 20px;
border: 0;
max-width: 450px;
}

.blog-cards-container a.teaser-link {
text-decoration: none;
}

.blog-cards-container h2.teaser-description {
font-size: var(--heading-font-size-s);
color: var(--c-dark-plum);
font-weight: 400;
line-height: 1.25em;
}

.blog-cards-container .authorprofile-container {
padding-bottom: 35px;
color: var(--text-color);
}

.blog-cards-container .authorprofile-info {
display: inline-flex;
line-height: 1.4em;
flex-flow: column wrap;
vertical-align: top;
}

.blog-cards-container .authorprofile-name {
font-size: 14px;
font-weight: 700;
letter-spacing: -0.5px;
line-height: 1.4em;
}

.blog-cards-container .authorprofile-position {
font-size: 14px;
font-weight: 400;
letter-spacing: -0.5px;
line-height: 1.4em;
}

.blog-cards-container .authorprofile-image {
width: 35px;
height: 35px;
margin-right: 8px;
display: inline-block;
vertical-align: middle;
}

.blog-cards-container img.nc-image {
border-radius: 50%;
overflow: hidden;
}

.blog-cards-container div.nc-image-container {
max-width: 100%;
display: flex;
align-items: center;
position: relative;
}

.blog-cards-container .teaser p.tags {
margin: 0;
font-size: var(--body-font-size-s);
text-transform: uppercase;
font-weight: 700;
}

.blog-cards-container .teaser picture img {
aspect-ratio: 1200 / 628;
object-fit: cover;
}

@media (min-width: 992px) {
.blog-cards-container h2.teaser-description {
min-height: 100px;
}
}
89 changes: 89 additions & 0 deletions blocks/blog-posts/blog-posts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
.blog-posts ul.related-list {
list-style: none;
padding-left: 0;
display: flex;
gap: 40px;
flex-direction: column;
justify-content: left;
margin-bottom: 25px;
}

.blog-posts ul.related-list li {
padding: 0;
}

.blog-posts select {
padding: 12px;
font-size: var(--body-font-size-m);
color: var(--c-dark-plum);
background: none;
border: 1px solid #e5e5e5;
}

.blog-posts .related-button-row {
display: flex;
justify-content: center;
}

@media (min-width: 600px) {
.blog-posts ul.related-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
align-items: center;
gap: 40px;
margin-bottom: 25px;
}

.blog-posts ul.related-list li:first-child {
grid-column: span 2;
width: 100%;
max-width: unset;
}

.blog-posts ul.related-list li:first-child article {
display: grid;
grid-template: 'picture tags' auto 'picture title' 1fr 'picture author' auto / 2fr 1fr;
column-gap: 20px;
}

.blog-posts ul.related-list li:first-child article picture img {
max-width: unset;
}

.blog-posts ul.related-list li:first-child article picture {
grid-area: picture;
}

.blog-posts ul.related-list li:first-child article > *:not(picture) {
margin-left: 20px;
}

.blog-posts ul.related-list li:first-child article > .authorprofile-container {
padding-bottom: 8px;
grid-area: author;
}

.blog-posts ul.related-list li:first-child article > .tags {
grid-area: tags;
border-top: 2px solid #e5e5e5;
padding-top: 12px;
}

.blog-posts ul.related-list li:first-child article > .teaser-link {
grid-area: title;
}

.blog-posts h2.teaser-description {
min-height: 100px;
}
}

@media (min-width: 992px) {
.blog-posts ul.related-list {
grid-template-columns: repeat(3, 1fr);
}

.blog-posts ul.related-list li:first-child {
grid-column: span 3;
}
}
164 changes: 164 additions & 0 deletions blocks/blog-posts/blog-posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {
createOptimizedPicture, decorateIcons, loadCSS,
readBlockConfig,
} from '../../scripts/lib-franklin.js';

const defaultAuthorName = 'Cognizant Netcentric';
const defaultAuthorRole = '';
const defaultAuthorImage = '/icons/nc.svg';

export function buildCard(card, large = false) {
const {
path, title, image, tags, profiles: authorProfile,
} = card;

const cardElement = document.createElement('article');
cardElement.classList.add('teaser');

if (authorProfile.image === '') authorProfile.image = defaultAuthorImage;

cardElement.innerHTML = `
<p class="tags">${JSON.parse(tags).join(', ')}</p>
<a href="${path}" target="_self" class="teaser-link">
<h2 class="teaser-description">
${title}
</h2>
</a>
<div class="authorprofile-container">
<div class="authorprofile-image">
<div class="nc-image-base">
<div class="nc-image-container " itemscope="" itemtype="http://schema.org/ImageObject">
<img class="nc-image" src="${authorProfile.image ?? defaultAuthorImage}" itemprop="contentUrl" alt="" sizes="10vw" />
</div>
</div>
</div>
<div class="authorprofile-info">
<div class="authorprofile-name">${authorProfile.name ?? defaultAuthorName}</div>
<div class="authorprofile-position">${authorProfile.role ?? defaultAuthorRole}</div>
</div>
</div>`;

// Width based on max-width set in css
const breakpoints = [{ width: large ? '1000' : '450' }];
const pictureElement = createOptimizedPicture(image, `Image symbolising ${title}`, false, breakpoints);
if (image && pictureElement) {
cardElement.prepend(pictureElement);
}

return cardElement;
}

function addCardsToCardList(cards, cardList) {
cards.forEach((card, index) => {
const blogListItem = document.createElement('li');
blogListItem.classList.add('related-list-item');

const largeCard = index === 0;

blogListItem.append(buildCard(card, largeCard));
cardList.appendChild(blogListItem);
});
}

export function createCardsList(parent, cards = []) {
const blogList = document.createElement('ul');
blogList.classList.add('related-list', 'blog-cards-container');

addCardsToCardList(cards, blogList);

parent.appendChild(blogList);
}

export async function joinArticlesWithProfiles(articles) {
const response = await fetch('/profiles/query-index.json');
const json = await response.json();

Object.values(articles).forEach((value) => {
value.profiles = json.data.find((profile) => profile.name === value.authors) ?? {};
});

return articles;
}

export async function getArticles(filter = () => true, maxItems = 7, offset = 0) {
const response = await fetch('/insights/query-index.json');
const json = await response.json();
verlok-cn marked this conversation as resolved.
Show resolved Hide resolved
const queryResult = json.data
.filter(filter)
.slice(offset, offset + maxItems);

return joinArticlesWithProfiles(queryResult);
}

function buildCTASection(parent, callback) {
const buttonRow = document.createElement('div');
buttonRow.classList.add('related-button-row');
buttonRow.innerHTML = '<button id="load-more-button" class="button secondary">Show More</button>';
decorateIcons(buttonRow);
parent.append(buttonRow);
parent.querySelector('#load-more-button').addEventListener('click', callback);
}

let articleOffset = 0;
let selectedCategory = 'All categories';

function getCardFilter() {
return selectedCategory === 'All categories'
// TODO: Only articles with tags can show up now because there are some items that arent
// articles without tags
? (article) => JSON.parse(article.tags).length > 0
: (article) => article.tags.includes(selectedCategory);
}

async function loadMoreArticles(numArticles = 6) {
const filter = getCardFilter();
const articles = await getArticles(filter, numArticles, articleOffset);
articleOffset += numArticles;

const blogList = document.querySelector('.blog-posts ul');

addCardsToCardList(articles, blogList);
}

async function updateFilter(newFilter) {
selectedCategory = newFilter;

// Reset all loaded articles
articleOffset = 0;
document.querySelector('ul.related-list').innerHTML = '';
await loadMoreArticles(7);
}

function createCategoryDropdown(options, parent, callback) {
const input = document.createElement('select');
options.forEach((option) => {
const optionTag = document.createElement('option');
optionTag.innerText = option;
optionTag.value = option;
input.append(optionTag);
});
parent.append(input);
input.addEventListener('change', callback);
}

export default async function decorate(block) {
loadCSS('/blocks/blog-posts/blog-card.css');

const config = readBlockConfig(block);

const categories = config.categories.split(', ');

block.innerHTML = '';

createCategoryDropdown(categories, block, (e) => {
updateFilter(e.target.value);
});

createCardsList(block);

await loadMoreArticles(7);

buildCTASection(block, () => {
loadMoreArticles();
});
}
Loading