This commit is contained in:
Mihajlo Medjedovic
2024-06-05 15:53:46 +02:00
commit fd510b88c4
270 changed files with 27578 additions and 0 deletions

118
src/templates/blog-list.tsx Normal file
View File

@ -0,0 +1,118 @@
import { Link, graphql } from 'gatsby'
import React from 'react'
import kebabCase from 'lodash/kebabCase'
import styled from 'styled-components'
import Layout from '../components/layout'
import Seo from '../components/seo'
import { Section } from '../components/shared'
import {
SectionHeading,
SectionDesc,
SolidButton
} from '../components/shared/styledComponents'
import Post from './postpreview'
import { SideBar } from './sidebar'
const BlogListTemplate = ({ data, location, pageContext }) => {
const posts = data.remark.posts
const iniPath =
pageContext.page == 'index'
? `/blog/`
: pageContext.page == 'year'
? `/${pageContext.year}/`
: pageContext.page == 'category'
? `/category/${kebabCase(pageContext.tag)}/`
: null
const pageInfo = `Page ${pageContext.currentPage} of ${pageContext.numPages}`
Array.from({ length: 5 }, (v, k) => k + 1)
const paginationJSX = Array.from(
{ length: pageContext.numPages },
(_, i) => i + 1
).map((pageIndex) => {
const link = pageIndex === 1 ? iniPath : `${iniPath}page/${pageIndex}`
return (
<Link
to={link}
className={`btn btn-outline-dark btn-sm ${
pageIndex === pageContext.currentPage ? 'disabled' : ''
}`}
style={{
borderRadius: '50%',
padding: '.375rem .75rem',
margin: '0.25rem'
}}
>
{pageIndex}
</Link>
)
})
return (
<Layout
location={location}
heading="Data Controllers Knowledge Base"
desc="A section dedicated to news, updates and educational pieces."
>
<Seo title="Blog" />
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<div className="row">
{posts.map((data, i) => (
<Post key={i} post={data.post} />
))}
</div>
<span className="float-start">{paginationJSX}</span>
<span className="float-end">{pageInfo}</span>
</div>
<div className="col-md-5">
<SideBar pageContext={pageContext} location={location} />
</div>
</div>
</Section>
</Layout>
)
}
export default BlogListTemplate
export const pageQuery = graphql`
query BlogListQuery(
$filter: MarkdownRemarkFilterInput!
$skip: Int!
$limit: Int!
) {
remark: allMarkdownRemark(
filter: $filter
limit: $limit
skip: $skip
sort: { fields: [frontmatter___date], order: DESC }
) {
posts: edges {
post: node {
html
fields {
slug
}
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
author
authorLink
previewImg {
childImageSharp {
gatsbyImageData(layout: CONSTRAINED)
}
}
}
}
}
}
}
`
// tags

View File

@ -0,0 +1,63 @@
import React from 'react'
import { Link, graphql } from 'gatsby'
import Layout from '../components/layout'
import Seo from '../components/seo'
import { Section } from '../components/shared'
import Post from './post'
import { SideBar } from './sidebar'
const BlogPostTemplate = ({ data, location, pageContext }) => {
const { post } = data
const { previewImg } = post.frontmatter
return (
<Layout location={location} heroSection={false}>
<Seo
title={post?.frontmatter?.title}
description={post?.frontmatter?.description}
previewImg={
previewImg?.childImageSharp?.gatsbyImageData?.images?.fallback?.src
}
/>
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<Post post={post} location={location} />
</div>
<div className="col-md-5">
<SideBar pageContext={pageContext} location={location} />
</div>
</div>
</Section>
</Layout>
)
}
export default BlogPostTemplate
export const pageQuery = graphql`
query PostByPath($id: String!) {
post: markdownRemark(id: { eq: $id }) {
id
html
fields {
slug
}
frontmatter {
title
description
date(formatString: "MMMM DD, YYYY")
author
authorLink
previewImg {
childImageSharp {
gatsbyImageData(layout: CONSTRAINED)
}
}
tags
}
}
}
`

View File

@ -0,0 +1,88 @@
import { Link, graphql, useStaticQuery } from 'gatsby'
import React from 'react'
import kebabCase from 'lodash/kebabCase'
import { useFlexSearch } from 'react-use-flexsearch'
import styled from 'styled-components'
import Layout from '../components/layout'
import Seo from '../components/seo'
import { Section } from '../components/shared'
import {
SectionHeading,
SectionDesc,
SolidButton
} from '../components/shared/styledComponents'
import Post from './postpreview'
import { SideBar } from './sidebar'
const BlogSearchTemplate = ({ location, pageContext }) => {
const queryData = useStaticQuery(graphql`
query {
localSearchBlog {
index
store
}
}
`)
const params = new URLSearchParams(location.search.substring(1))
const query = params.get('s')
const posts = useFlexSearch(
query,
queryData.localSearchBlog.index,
queryData.localSearchBlog.store
)
const postsJSX = []
if (query) {
posts.forEach((post, i) => {
const _post = {
id: post.id,
html: post.html,
fields: {
slug: post.slug
},
frontmatter: {
title: post.title,
date: post.date,
previewImg: post.previewImg
}
}
postsJSX.push(<Post key={i} post={_post} />)
})
}
return (
<Layout
location={location}
heading="Data Controllers Knowledge Base"
desc="A section dedicated to news, updates and educational pieces."
>
<Seo title="Blog" />
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<div className="row">
{query ? (
<h1>
{postsJSX.length} results found related to &quot;{query}&quot;
</h1>
) : (
<h1>No Query Entered</h1>
)}
{postsJSX}
</div>
</div>
<div className="col-md-5">
<SideBar pageContext={pageContext} location={location} />
</div>
</div>
</Section>
</Layout>
)
}
export default BlogSearchTemplate

View File

@ -0,0 +1,159 @@
import React from 'react'
import { Link } from 'gatsby'
import kebabCase from 'lodash/kebabCase'
import { GatsbyImage } from 'gatsby-plugin-image'
import styled from 'styled-components'
export const StyledLink = styled((props) => <Link {...props} />)`
text-decoration: none;
color: black;
&:hover {
opacity: 0.8;
color: black;
}
`
const StyledTitle = styled.h5`
font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
margin-bottom: 0;
`
const StyledDate = styled.span`
opacity: 0.5;
font-size: 0.8rem;
a {
color: black;
text-decoration: none;
&:hover {
text-decoration: underline;
color: black;
opacity: 1;
}
}
`
const StyledDesc = styled.p`
margin-top: 10px;
opacity: 0.7;
font-size: 0.8rem;
margin-bottom: 100px;
`
const StyledContent = styled.div`
padding: 15px 0;
color: #666666;
font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
a {
color: black;
text-decoration: none;
&:hover {
text-decoration: underline;
color: #666666;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #222222;
}
* + h2,
* + h3 {
margin: 2rem auto 0.8rem;
}
img {
float: right;
max-width: 100%;
margin: 10px;
&.alignleft {
float: left;
}
&.aligncenter {
float: none;
max-width: 90%;
display: block;
margin: 10px auto;
}
}
.imgHolder {
padding: 5px;
margin-left: 5px;
float: right;
text-align: center;
border: 1px solid #e1e1e1;
border-radius: 2px;
font-style: italic;
font-family: Georgia, 'Times New Roman';
img {
float: none;
}
> div {
display: flex;
span {
flex-grow: 1;
width: 0;
}
}
}
pre {
background-image: linear-gradient(
rgba(0, 0, 0, 0.05) 50%,
transparent 50%,
transparent
);
background-size: 100% 4em;
border: 1px solid #e1e1e1;
padding: 2em;
line-height: 2em;
font-family: Monaco, 'Andale Mono', 'Courier New', Courier, monospace;
}
`
const Post = ({ post, location }) => {
const tagsJSX = (post.frontmatter?.tags || []).map((tag, index) => (
<span key={index}>
{index > 0 && ', '}
<Link to={`/category/${kebabCase(tag)}/`} rel="category tag">
{tag}
</Link>
</span>
))
const authorJSX = post.frontmatter.authorLink ? (
<span>
<a href={post.frontmatter.authorLink} target="_blank" rel="noopener">
{post.frontmatter.author}
</a>
</span>
) : (
<span>{post.frontmatter.author}</span>
)
return (
<div>
<GatsbyImage
image={post.frontmatter.previewImg.childImageSharp.gatsbyImageData}
style={{ width: '100%', marginBottom: '1rem' }}
imgStyle={{ objectFit: 'contain' }}
alt={post.frontmatter.title}
/>
<StyledTitle>{post.frontmatter.title}</StyledTitle>
<StyledDate>
{post.frontmatter.date} / in {tagsJSX} / by {authorJSX}
</StyledDate>
<StyledContent
dangerouslySetInnerHTML={{
__html: post.html
}}
/>
</div>
)
}
export default Post

View File

@ -0,0 +1,54 @@
import React from 'react'
import { Link } from 'gatsby'
import { GatsbyImage } from 'gatsby-plugin-image'
import styled from 'styled-components'
import getDescription from '../shared/getDescription'
export const StyledLink = styled((props) => <Link {...props} />)`
text-decoration: none;
color: black;
&:hover {
opacity: 0.8;
color: black;
}
`
const StyledTitle = styled.h5`
font-family: 'Montserrat', 'HelveticaNeue', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
font-size: 1rem;
margin-bottom: 0;
`
const StyledDate = styled.span`
opacity: 0.5;
font-size: 0.8rem;
`
const StyledDesc = styled.p`
margin-top: 10px;
opacity: 0.7;
font-size: 0.8rem;
margin-bottom: 100px;
`
const Post = ({ post }) => (
<div className="col-md-6">
<StyledLink to={post.fields.slug}>
<GatsbyImage
image={post.frontmatter.previewImg.childImageSharp.gatsbyImageData}
style={{ minHeight: '150px', maxHeight: '200px' }}
imgStyle={{ objectFit: 'contain' }}
alt={post.frontmatter.title}
/>
<StyledTitle>{post.frontmatter.title}</StyledTitle>
<StyledDate>{post.frontmatter.date}</StyledDate>
<StyledDesc>{getDescription(post.html)}</StyledDesc>
</StyledLink>
</div>
)
export default Post

View File

@ -0,0 +1,13 @@
const extractContent = (s: string): string => {
if (typeof document !== `undefined`) {
const span = document.createElement('span')
span.innerHTML = s
return span.textContent || span.innerText
}
return s
}
const getDescription = (content: string): string => {
return extractContent(content).substr(0, 100) + '...'
}
export default getDescription

181
src/templates/sidebar.tsx Normal file
View File

@ -0,0 +1,181 @@
import React, { useState, useEffect } from 'react'
import { Link, navigate } from 'gatsby'
import kebabCase from 'lodash/kebabCase'
import styled, { css } from 'styled-components'
import { pathPrefix } from '../../gatsby-config.js'
import { StyledHeading } from '../styledComponents/blog'
import { SolidButton } from '../components/shared/styledComponents'
const styles = css`
color: #314351;
opacity: 0.8;
font:
13px/1.65em 'HelveticaNeue',
'Helvetica Neue',
Helvetica,
Arial,
sans-serif;
font-size: 0.9rem;
`
const linkStyles = css`
${styles}
text-decoration: none;
display: block;
&:hover {
color: #314351;
opacity: 1;
text-decoration: underline;
}
`
const ArchiveLink = styled((props) => <Link {...props} />)`
${linkStyles}
`
const RecentPostLink = styled((props) => <Link {...props} />)`
${linkStyles}
padding: 3px;
border-bottom: 3px solid #e1e1e1;
font-style: italic;
&:last-child {
border-bottom: none;
}
`
const SideBarSection = styled.div`
margin-bottom: 20px;
`
const StyledInput = styled.input`
border-color: #e1e1e1;
background-color: #f8f8f8;
color: #919191;
&:focus {
background-color: #f8f8f8;
box-shadow: none;
}
`
const StyledPara = styled.p`
${styles}
`
const Archives = ({ archives }) => (
<>
{Object.keys(archives)
.sort()
.reverse()
.map((year, index) => (
<ArchiveLink key={year} to={`/${year}/`}>
{year} ({archives[year]})
</ArchiveLink>
))}
</>
)
const Categories = ({ tags }) => (
<>
{tags.map((tag, i) => (
<ArchiveLink key={i} to={`/category/${kebabCase(tag.name)}/`}>
{tag.name} ({tag.totalCount})
</ArchiveLink>
))}
</>
)
const RecentPosts = ({ posts }) => (
<>
{posts.map((post) => (
<RecentPostLink key={post.slug} to={post.slug}>
{post.title}
</RecentPostLink>
))}
</>
)
export const SideBar = ({ pageContext, location, notFoundPage = false }) => {
const params = new URLSearchParams(location.search.substring(1))
const queryUrl = params?.get('s') || ''
const [query, setQuery] = useState(queryUrl)
useEffect(() => {
setQuery(queryUrl)
}, [queryUrl])
const handleSubmit = (event) => {
event.preventDefault()
navigate('/search?s=' + query)
}
const _handleKeyDown = (event) => {
if (event.key === 'Enter') {
handleSubmit(event)
}
}
return (
<>
<SideBarSection>
{notFoundPage && (
<>
<StyledPara>
<b>Nothing Found</b>
</StyledPara>
<StyledPara>
Sorry, the post you are looking for is not available. Maybe you
want to perform a search?
</StyledPara>
</>
)}
<div className="input-group mb-3">
<StyledInput
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => _handleKeyDown(e)}
className="form-control"
placeholder="Search"
aria-label="Search"
aria-describedby="button-addon2"
/>
<SolidButton theme="dark" onClick={handleSubmit}>
Search
</SolidButton>
<form onSubmit={handleSubmit}></form>
</div>
{notFoundPage && (
<>
<StyledPara>
For best search results, mind the following suggestions:
</StyledPara>
<StyledPara>
<ul>
<li>Always double check your spelling.</li>
<li>
Try similar keywords, for example: tablet instead of laptop.
</li>
<li>Try using more than one keyword.</li>
</ul>
</StyledPara>
</>
)}
<StyledHeading>
{notFoundPage
? 'Feel like browsing some posts instead?'
: 'Recent Posts'}
</StyledHeading>
<RecentPosts posts={pageContext.recentPosts} />
</SideBarSection>
{!notFoundPage && (
<>
<SideBarSection>
<StyledHeading>Archives</StyledHeading>
<Archives archives={pageContext.archives} />
</SideBarSection>
<SideBarSection>
<StyledHeading>Categories</StyledHeading>
<Categories tags={pageContext.tags} />
</SideBarSection>
</>
)}
</>
)
}