init
This commit is contained in:
118
src/templates/blog-list.tsx
Normal file
118
src/templates/blog-list.tsx
Normal 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 Controller’s 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
|
63
src/templates/blog-post.tsx
Normal file
63
src/templates/blog-post.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
88
src/templates/blog-search.tsx
Normal file
88
src/templates/blog-search.tsx
Normal 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 Controller’s 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 "{query}"
|
||||
</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
|
159
src/templates/post/index.tsx
Normal file
159
src/templates/post/index.tsx
Normal 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
|
54
src/templates/postpreview/index.tsx
Normal file
54
src/templates/postpreview/index.tsx
Normal 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
|
13
src/templates/shared/getDescription.ts
Normal file
13
src/templates/shared/getDescription.ts
Normal 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
181
src/templates/sidebar.tsx
Normal 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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user