1
0
Fork 0

more refactor

This commit is contained in:
Matthias Kretschmann 2020-07-19 13:31:27 +02:00
parent 369c1f1c27
commit 2d20a5a1b1
Signed by: m
GPG Key ID: 606EEEF3C479A91F
29 changed files with 451 additions and 428 deletions

View File

@ -19,11 +19,11 @@ module.exports = {
rss: '/feed.xml',
jsonfeed: '/feed.json',
typekitID: 'msu4qap',
itemsPerPage: 20,
itemsPerPage: 18,
repoContentPath: 'https://github.com/kremalicious/blog/tree/main/content',
menu: [
{
title: 'Articles & Links',
title: 'Articles',
link: '/archive'
},
{

View File

@ -5,7 +5,6 @@ title: Project Purple
image: ../media/Teaser-Project-Purple.png
download: ../media/project-purple-kremalicious.zip
author: Matthias Kretschmann
featured: true
date: 2012-08-07 13:15:44+00:00

View File

@ -3,7 +3,9 @@ const { createExif } = require('./gatsby/createExif')
const {
generatePostPages,
generateTagPages,
generateRedirectPages
generateRedirectPages,
generateArchivePages,
generatePhotosPages
} = require('./gatsby/createPages')
const { generateJsonFeed } = require('./gatsby/feeds')
@ -19,12 +21,12 @@ exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
}
}
exports.createPages = async ({ graphql, actions }) => {
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage, createRedirect } = actions
const result = await graphql(`
{
posts: allMarkdownRemark(sort: { order: DESC, fields: [fields___date] }) {
all: allMarkdownRemark(sort: { order: DESC, fields: [fields___date] }) {
edges {
next {
fields {
@ -53,6 +55,16 @@ exports.createPages = async ({ graphql, actions }) => {
}
}
photos: allMarkdownRemark(
filter: { frontmatter: { type: { eq: "photo" } } }
) {
edges {
node {
id
}
}
}
archive: allMarkdownRemark(
filter: { frontmatter: { type: { ne: "photo" } } }
) {
@ -72,14 +84,24 @@ exports.createPages = async ({ graphql, actions }) => {
}
`)
if (result.errors) throw result.errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const posts = result.data.posts.edges
const all = result.data.all.edges
const photosLength = result.data.photos.edges.length
const archiveLength = result.data.archive.edges.length
const tags = result.data.tags.group
// Generate posts & posts index
generatePostPages(createPage, posts, archiveLength)
// Generate post pages
generatePostPages(createPage, all)
// Generate archive pages
generateArchivePages(createPage, archiveLength)
// Generate archive pages
generatePhotosPages(createPage, photosLength)
// Generate tag pages
generateTagPages(createPage, tags)

View File

@ -3,6 +3,7 @@ const { itemsPerPage } = require('../config')
const postTemplate = path.resolve('src/components/templates/Post/index.tsx')
const archiveTemplate = path.resolve('src/components/templates/Archive.tsx')
const photosTemplate = path.resolve('src/components/templates/Photos.tsx')
const redirects = [
{ f: '/feed', t: '/feed.xml' },
@ -25,7 +26,7 @@ function getPaginationData(i, numPages, slug) {
return { prevPagePath, nextPagePath, path }
}
exports.generatePostPages = (createPage, posts, archiveLength) => {
exports.generatePostPages = (createPage, posts) => {
// Create Post pages
posts.forEach((post) => {
createPage({
@ -44,26 +45,56 @@ exports.generatePostPages = (createPage, posts, archiveLength) => {
}
})
})
}
exports.generateArchivePages = (createPage, archiveLength) => {
// Create paginated archive pages
const numPages = Math.ceil(archiveLength / itemsPerPage)
const slug = `/archive/`
const numPagesArchive = Math.ceil(archiveLength / itemsPerPage)
const slugArchive = `/archive/`
Array.from({ length: numPages }).forEach((_, i) => {
Array.from({ length: numPagesArchive }).forEach((_, i) => {
const { prevPagePath, nextPagePath, path } = getPaginationData(
i,
numPages,
slug
numPagesArchive,
slugArchive
)
createPage({
path,
component: archiveTemplate,
context: {
slug,
slug: slugArchive,
limit: itemsPerPage,
skip: i * itemsPerPage,
numPages,
numPages: numPagesArchive,
currentPageNumber: i + 1,
prevPagePath,
nextPagePath
}
})
})
}
exports.generatePhotosPages = (createPage, photosLength) => {
// Create paginated photos pages
const numPagesPhotos = Math.ceil(photosLength / itemsPerPage)
const slugPhotos = `/photos/`
Array.from({ length: numPagesPhotos }).forEach((_, i) => {
const { prevPagePath, nextPagePath, path } = getPaginationData(
i,
numPagesPhotos,
slugPhotos
)
createPage({
path,
component: photosTemplate,
context: {
slug: slugPhotos,
limit: itemsPerPage,
skip: i * itemsPerPage,
numPages: numPagesPhotos,
currentPageNumber: i + 1,
prevPagePath,
nextPagePath

View File

@ -32,3 +32,12 @@ export interface Post {
fileAbsolutePath?: string
tableOfContents?: string
}
export interface PageContext {
tag?: string
slug: string
currentPageNumber: number
numPages: number
prevPagePath?: string
nextPagePath?: string
}

View File

@ -33,7 +33,18 @@ export const imageSizeThumb = graphql`
original {
src
}
fluid(maxWidth: 400, maxHeight: 170, quality: 85, cropFocus: CENTER) {
fluid(maxWidth: 420, maxHeight: 140, quality: 85, cropFocus: CENTER) {
...GatsbyImageSharpFluid_withWebp_noBase64
}
}
`
export const photoSizeThumb = graphql`
fragment PhotoFluidThumb on ImageSharp {
original {
src
}
fluid(maxWidth: 300, maxHeight: 300, quality: 85, cropFocus: CENTER) {
...GatsbyImageSharpFluid_withWebp_noBase64
}
}

View File

@ -2,59 +2,11 @@
@import 'mixins';
.featured {
@include divider;
display: grid;
gap: $spacer / 2;
grid-template-columns: 1fr 1fr;
padding-bottom: $spacer * $line-height;
margin-bottom: -($spacer / 2);
gap: $spacer;
grid-template-columns: repeat(2, 1fr);
@media (min-width: $screen-md) {
@include breakoutviewport;
gap: $spacer;
padding-bottom: $spacer * 3;
}
}
.featuredTitle {
transition: 0.1s ease-out;
font-size: $font-size-base;
margin: 0;
position: absolute;
top: 25%;
min-width: 45%;
text-align: right;
padding: $spacer / 3;
background: rgba($link-color, 0.85);
color: #fff !important;
text-shadow: 0 1px 0 #000;
left: 0;
opacity: 0;
transform: translate3d(0, -20px, 0);
}
.featuredItem {
position: relative;
a {
display: block;
&:hover,
&:focus {
.featuredTitle {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
}
figure {
margin: 0;
}
:global(.gatsby-image-wrapper) {
margin-bottom: 0;
grid-template-columns: repeat(3, 1fr);
}
}

View File

@ -1,8 +1,8 @@
import React, { ReactElement } from 'react'
import { Link, graphql, useStaticQuery } from 'gatsby'
import { Image } from '../atoms/Image'
import { graphql, useStaticQuery } from 'gatsby'
import styles from './Featured.module.scss'
import { Post } from '../../@types/Post'
import PostTeaser from './PostTeaser'
const query = graphql`
query {
@ -21,21 +21,12 @@ const query = graphql`
export default function Featured(): ReactElement {
const data = useStaticQuery(query)
return (
<div className={styles.featured}>
{data.allMarkdownRemark.edges.map(({ node }: { node: Post }) => {
const { title, image } = node.frontmatter
const { slug } = node.fields
return (
<article className={styles.featuredItem} key={node.id}>
<Link to={slug}>
<Image fluid={image.childImageSharp.fluid} alt={title} />
<h1 className={styles.featuredTitle}>{title}</h1>
</Link>
</article>
)
})}
{data.allMarkdownRemark.edges.map(({ node }: { node: Post }) => (
<PostTeaser key={node.id} post={node} />
))}
</div>
)
}

View File

@ -1,36 +1,47 @@
@import 'variables';
@import 'mixins';
.pagination {
display: flex;
margin-top: $spacer * 2;
@include breakoutviewport;
margin-top: $spacer * 3;
margin-bottom: $spacer;
justify-content: space-between;
> div {
&:first-child {
margin-right: $spacer;
}
&:last-child {
margin-left: $spacer;
text-align: right;
}
}
display: flex;
justify-content: center;
}
.number {
text-align: center;
width: 2rem;
height: 2rem;
line-height: 1.7;
display: inline-block;
border-radius: 50%;
border: 1px solid transparent;
padding: $spacer / 6 $spacer / 2;
border: 1px solid $brand-grey-dimmed;
font-variant-numeric: lining-nums;
margin-left: -1px;
display: flex;
align-items: center;
justify-content: center;
min-width: 2.5rem;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
color: $brand-grey;
:global(.dark) & {
color: $brand-grey-dimmed;
border-color: darken($body-background-color--dark, 5%);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
}
&:hover,
&:focus {
background: rgba(255, 255, 255, 0.3);
border-color: darken($brand-grey-dimmed, 5%);
background: rgba(255, 255, 255, 0.1);
}
&:first-child {
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
}
&:last-child {
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
}
}
@ -38,10 +49,10 @@
composes: number;
cursor: default;
pointer-events: none;
border: 1px solid darken($brand-grey-dimmed, 5%);
color: $brand-grey-light;
color: $brand-grey-dimmed;
background: rgba(255, 255, 255, 0.1);
:global(.dark) & {
border-color: darken($brand-grey-light, 15%);
color: $brand-grey-light;
}
}

View File

@ -2,6 +2,8 @@ import React, { ReactElement } from 'react'
import { Link } from 'gatsby'
import styles from './Pagination.module.scss'
import shortid from 'shortid'
import { PageContext } from '../../@types/Post'
import Icon from '../atoms/Icon'
const PageNumber = ({
i,
@ -34,8 +36,12 @@ function PrevNext({
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
return (
<Link to={link} rel={rel} title={title}>
{prevPagePath ? '←' : '→'}
<Link to={link} rel={rel} title={title} className={styles.number}>
{prevPagePath ? (
<Icon name="ChevronLeft" />
) : (
<Icon name="ChevronRight" />
)}
</Link>
)
}
@ -43,13 +49,7 @@ function PrevNext({
export default function Pagination({
pageContext
}: {
pageContext: {
slug: string
currentPageNumber: number
numPages: number
prevPagePath?: string
nextPagePath?: string
}
pageContext: PageContext
}): ReactElement {
const {
slug,
@ -63,18 +63,16 @@ export default function Pagination({
return (
<div className={styles.pagination}>
<div>{!isFirst && <PrevNext prevPagePath={prevPagePath} />}</div>
<div>
{Array.from({ length: numPages }, (_, i) => (
<PageNumber
key={shortid.generate()}
i={i}
slug={slug}
current={currentPageNumber === i + 1}
/>
))}
</div>
<div>{!isLast && <PrevNext nextPagePath={nextPagePath} />}</div>
{!isFirst && <PrevNext prevPagePath={prevPagePath} />}
{Array.from({ length: numPages }, (_, i) => (
<PageNumber
key={shortid.generate()}
i={i}
slug={slug}
current={currentPageNumber === i + 1}
/>
))}
{!isLast && <PrevNext nextPagePath={nextPagePath} />}
</div>
)
}

View File

@ -1,25 +1,21 @@
@import 'variables';
@import 'mixins';
.post {
figure {
margin: 0;
}
}
.postTitle {
display: inline-block;
margin-top: $spacer / 4;
margin-bottom: 0;
font-size: $font-size-small;
line-height: $line-height-small;
color: $brand-grey-light;
.title {
padding-left: 0.2rem;
padding-right: 0.2rem;
color: $brand-grey-light;
margin-top: $spacer / 2;
margin-bottom: 0;
font-size: $font-size-base;
transition: color 0.2s ease-out;
}
@media (min-width: $screen-md) {
font-size: $font-size-base;
.post {
display: block;
figure {
margin: 0;
}
}

View File

@ -1,20 +1,24 @@
import React, { ReactElement } from 'react'
import { Link, graphql } from 'gatsby'
import { Image } from '../atoms/Image'
import styles from './PostTeaser.module.scss'
import { Post } from '../../@types/Post'
import PostTitle from '../templates/Post/Title'
import styles from './PostTeaser.module.scss'
export const postTeaserQuery = graphql`
fragment PostTeaser on MarkdownRemark {
id
fileAbsolutePath
frontmatter {
type
title
linkurl
image {
childImageSharp {
...ImageFluidThumb
}
}
tags
}
fields {
slug
@ -29,7 +33,7 @@ export default function PostTeaser({
post: Partial<Post>
toggleSearch?: () => void
}): ReactElement {
const { image, title } = post.frontmatter
const { image, title, type } = post.frontmatter
const { slug } = post.fields
return (
@ -38,12 +42,16 @@ export default function PostTeaser({
to={slug}
onClick={toggleSearch && toggleSearch}
>
{image ? (
<Image fluid={image.childImageSharp.fluid} alt={title} />
) : (
<div className={styles.empty} />
{image && (
<Image
title={type === 'photo' ? title : null}
fluid={image.childImageSharp.fluid}
alt={title}
original={image.childImageSharp.original}
/>
)}
<h4 className={styles.postTitle}>{title}</h4>
<PostTitle slug={slug} title={title} className={styles.title} />
</Link>
)
}

View File

@ -47,16 +47,16 @@ function photosWithDataFilter(posts: []) {
export default function RelatedPosts({
tags,
photos
isPhotos
}: {
tags: string[]
photos?: boolean
isPhotos?: boolean
}): ReactElement {
const data = useStaticQuery(query)
const posts = data.allMarkdownRemark.edges
function getPosts() {
return photos
return isPhotos
? photosWithDataFilter(posts)
: tags && postsWithDataFilter(posts, 'tags', tags)
}
@ -71,7 +71,7 @@ export default function RelatedPosts({
return (
<aside className={styles.relatedPosts}>
<h1 className={styles.title}>
Related {photos ? 'Photos' : 'Posts'}{' '}
Related {isPhotos ? 'Photos' : 'Posts'}{' '}
<button className={styles.button} onClick={refreshPosts}>
Refresh
</button>

View File

@ -7,7 +7,7 @@ import { useSiteMetadata } from '../../hooks/use-site-metadata'
import styles from './Footer.module.scss'
function Copyright() {
const { name, uri, bitcoin, github } = useSiteMetadata().author
const { name, uri, github } = useSiteMetadata().author
const year = new Date().getFullYear()
return (
@ -24,7 +24,7 @@ function Copyright() {
</a>
<Link to="/thanks" className={styles.btc}>
<Icon name="Bitcoin" />
<code>{bitcoin}</code>
Say Thanks
</Link>
</p>
</section>
@ -37,7 +37,6 @@ export default class Footer extends PureComponent {
<footer role="contentinfo" className={styles.footer}>
<Container>
<Vcard />
<Copyright />
</Container>
</footer>

View File

@ -1,39 +1,13 @@
@import 'variables';
@import 'mixins';
.hentry {
@include divider;
.posts {
display: grid;
gap: $spacer * 2;
margin-top: 0;
margin-bottom: 0;
padding-top: $spacer * 2;
padding-bottom: $spacer * 2;
@media (min-width: $screen-sm) {
@include breakoutviewport;
:global(.gatsby-image-wrapper) {
max-height: 100vh;
}
@media (min-width: $screen-md) {
padding-top: $spacer * 4;
padding-bottom: $spacer * 4;
grid-template-columns: repeat(2, 1fr);
}
}
.archivetitle {
@include divider;
font-size: $font-size-h3;
margin-top: 0;
margin-bottom: 0;
padding-bottom: $spacer * $line-height;
span {
color: $brand-grey-light;
padding-right: $spacer / 12;
font-size: $font-size-base;
}
}
.paginationTitle {
composes: archivetitle;
}

View File

@ -1,84 +1,44 @@
import React, { ReactElement } from 'react'
import { Link, graphql } from 'gatsby'
import { Post } from '../../@types/Post'
import { graphql } from 'gatsby'
import { Post, PageContext } from '../../@types/Post'
import Pagination from '../molecules/Pagination'
import PostTitle from './Post/Title'
import PostLead from './Post/Lead'
import PostContent from './Post/Content'
import PostMore from './Post/More'
import PostLinkActions from './Post/LinkActions'
import SEO from '../atoms/SEO'
import styles from './Archive.module.scss'
import { Image } from '../atoms/Image'
import PostTeaser from '../molecules/PostTeaser'
import Page from './Page'
export default function Archive({
data,
pageContext
}: {
data: any
pageContext: {
tag: string
slug: string
currentPageNumber: number
numPages: number
}
pageContext: PageContext
}): ReactElement {
const edges = data.allMarkdownRemark.edges
const { tag, currentPageNumber, numPages } = pageContext
const PostsList = edges.map(({ node }: { node: Post }) => {
const { type, linkurl, title, image } = node.frontmatter
const { slug } = node.fields
const PostsList = edges.map(({ node }: { node: Post }) => (
<PostTeaser key={node.id} post={node} />
))
return (
<article className={styles.hentry} key={node.id}>
<PostTitle type={type} slug={slug} linkurl={linkurl} title={title} />
const title = tag ? `#${tag}` : 'Archive'
{image && (
<Link to={slug} title={title}>
<Image
title={type === 'photo' ? title : null}
fluid={image.childImageSharp.fluid}
alt={title}
original={image.childImageSharp.original}
/>
</Link>
)}
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
{type === 'article' && (
<>
<PostLead post={node} index />
<PostMore to={slug}>Continue Reading</PostMore>
</>
)}
{type === 'link' && (
<>
<PostContent post={node} />
<PostLinkActions slug={slug} linkurl={linkurl} />
</>
)}
</article>
)
})
const page = {
frontmatter: {
title: `${title} ${paginationTitle}`,
description: 'All the articles & links.'
}
}
return (
<>
<SEO />
{tag && (
<h1 className={styles.archivetitle}>
<span>#</span>
{tag}
</h1>
)}
{numPages > 1 && currentPageNumber > 1 && (
<h2
className={styles.paginationTitle}
>{`Page ${currentPageNumber} / ${numPages}`}</h2>
)}
{PostsList}
<Page title={page.frontmatter.title} post={page}>
<div className={styles.posts}>{PostsList}</div>
{numPages > 1 && <Pagination pageContext={pageContext} />}
</>
</Page>
)
}
@ -92,21 +52,7 @@ export const archiveQuery = graphql`
) {
edges {
node {
id
html
frontmatter {
title
type
linkurl
image {
childImageSharp {
...ImageFluid
}
}
}
fields {
slug
}
...PostTeaser
}
}
}

View File

@ -1,6 +1,19 @@
@import 'variables';
@import 'mixins';
.pageTitle {
composes: archivetitle from './Archive.module.scss';
margin-bottom: $spacer * 2;
.pagetitle {
@include divider;
@media (min-width: $screen-md) {
@include breakoutviewport;
}
font-size: $font-size-h3;
margin-top: 0;
margin-bottom: $spacer * $line-height;
padding-bottom: $spacer / $line-height;
color: $brand-grey-light;
display: flex;
justify-content: space-between;
align-items: flex-end;
}

View File

@ -23,7 +23,7 @@ export default function Page({
<Helmet title={title} />
<SEO slug={pathname} postSEO post={post} />
<h1 className={styles.pageTitle}>{title}</h1>
<h1 className={styles.pagetitle}>{title}</h1>
{section ? <section className={section}>{children}</section> : children}
</>
)

View File

@ -2,16 +2,14 @@
@import 'mixins';
.photos {
@include breakoutviewport--full;
@include breakoutviewport;
display: grid;
grid-template-columns: 1fr 1fr;
gap: $spacer;
padding-left: $spacer;
padding-right: $spacer;
@media (min-width: $screen-sm) {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
}

View File

@ -1,12 +1,20 @@
import React from 'react'
import { render } from '@testing-library/react'
import Photos from '../photos'
import Photos from './Photos'
import data from '../../../jest/__fixtures__/photos.json'
describe('/photos', () => {
it('renders without crashing', () => {
const { container } = render(<Photos data={data} />)
const pageContext = {
slug: '/photos',
currentPageNumber: 2,
numPages: 20
}
const { container } = render(
<Photos data={data} pageContext={pageContext} />
)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -0,0 +1,87 @@
import React, { ReactElement } from 'react'
import { graphql, Link } from 'gatsby'
import Page from './Page'
import { Post, PageContext } from '../../@types/Post'
import { Image } from '../atoms/Image'
import styles from './Photos.module.scss'
import Pagination from '../molecules/Pagination'
export const PhotoThumb = ({ photo }: { photo: Post }): ReactElement => {
const { title, image } = photo.frontmatter
const { slug } = photo.fields
const { fluid } = image.childImageSharp
return (
<article className={styles.photo}>
{image && (
<Link to={slug}>
<Image title={title} fluid={fluid} alt={title} />
</Link>
)}
</article>
)
}
export default function Photos({
data,
pageContext
}: {
data: any
pageContext: PageContext
}): ReactElement {
const photos = data.allMarkdownRemark.edges
const { currentPageNumber, numPages } = pageContext
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
const page = {
frontmatter: {
title: `Photos ${paginationTitle}`,
description:
'Personal photos of designer & developer Matthias Kretschmann.'
}
}
return (
<Page title={page.frontmatter.title} post={page}>
<section className={styles.photos}>
{photos.map(({ node }: { node: Post }) => (
<PhotoThumb key={node.id} photo={node} />
))}
</section>
{numPages > 1 && <Pagination pageContext={pageContext} />}
</Page>
)
}
export const photosQuery = graphql`
query($skip: Int, $limit: Int) {
allMarkdownRemark(
filter: { frontmatter: { type: { eq: "photo" } } }
sort: { order: DESC, fields: [fields___date] }
skip: $skip
limit: $limit
) {
edges {
node {
id
frontmatter {
title
image {
childImageSharp {
...PhotoFluidThumb
}
}
}
fields {
slug
}
}
}
}
}
`

View File

@ -10,7 +10,7 @@ const PostContent = ({ post }: { post: Post }): ReactElement => {
let content = post.html
if (post.frontmatter.type === 'post') {
if (post.frontmatter.type === 'article') {
if (content.includes(separator)) {
content = content.split(separator)[1]
} else {

View File

@ -6,10 +6,10 @@ import { Post } from '../../../@types/Post'
// Grab everything before more tag, or just first paragraph
const PostLead = ({
post,
index
className
}: {
post: Post
index?: boolean
post: Partial<Post>
className?: string
}): ReactElement => {
let lead
const content = post.html
@ -23,7 +23,7 @@ const PostLead = ({
return (
<div
className={index ? styles.index : styles.lead}
className={`${styles.lead} ${className && className}`}
dangerouslySetInnerHTML={{ __html: lead }}
/>
)

View File

@ -38,7 +38,7 @@ export default function PostMeta({ post }: { post: Post }): ReactElement {
{tags && (
<div className={styles.tags}>
{tags.map((tag: string) => {
const url = `/tags/${slugify(tag)}/`
const url = `/archive/${slugify(tag)}/`
return <Tag key={shortid.generate()} name={tag} url={url} />
})}
</div>

View File

@ -1,30 +1,31 @@
import React, { ReactElement } from 'react'
import { Link } from 'gatsby'
import styles from './Title.module.scss'
import Icon from '../../atoms/Icon'
import Time from '../../atoms/Time'
export default function PostTitle({
type,
slug,
linkurl,
title,
date,
updated
updated,
className
}: {
type?: string
slug?: string
linkurl?: string
title: string
date?: string
updated?: string
className?: string
}): ReactElement {
const linkHostname = linkurl ? new URL(linkurl).hostname : null
return type === 'link' ? (
return linkurl ? (
<>
<h1
className={[styles.hentry__title, styles.hentry__title__link].join(' ')}
className={`${styles.hentry__title} ${styles.hentry__title__link} ${
className && className
}`}
>
<a href={linkurl} title={`Go to source: ${linkurl}`}>
{title} <Icon name="ExternalLink" />
@ -33,12 +34,14 @@ export default function PostTitle({
<div className={styles.linkurl}>{linkHostname}</div>
</>
) : slug ? (
<h1 className={styles.hentry__title}>
<Link to={slug}>{title}</Link>
<h1 className={`${styles.hentry__title} ${className && className}`}>
{title}
</h1>
) : (
<>
<h1 className={styles.hentry__title}>{title}</h1>
<h1 className={`${styles.hentry__title} ${className && className}`}>
{title}
</h1>
{date && (
<div className={styles.time}>
{updated && 'published '}

View File

@ -40,35 +40,38 @@ export default function Post({
<article className={styles.hentry}>
<header>
<PostTitle
type={type}
linkurl={linkurl}
title={title}
date={date}
updated={updated}
/>
{type === 'article' && <PostLead post={post} />}
{image && (
<Image
fluid={image.childImageSharp.fluid}
alt={title}
original={image.childImageSharp.original}
/>
)}
</header>
{type === 'photo' && <PostContent post={post} />}
{image && (
<Image
fluid={image.childImageSharp.fluid}
alt={title}
original={image.childImageSharp.original}
/>
{type === 'photo' ? (
<>
<PostContent post={post} />
{image && image.fields && <Exif exif={image.fields.exif} />}
</>
) : (
<PostContent post={post} />
)}
{type === 'photo' && image && image.fields && (
<Exif exif={image.fields.exif} />
)}
{type !== 'photo' && <PostContent post={post} />}
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
<PostMeta post={post} />
<PostActions slug={slug} githubLink={githubLink} />
</article>
<RelatedPosts photos={type === 'photo'} tags={tags} />
<RelatedPosts isPhotos={type === 'photo'} tags={tags} />
<PrevNext prev={prev} next={next} />
</>

View File

@ -1,4 +1,44 @@
@import 'variables';
@import 'mixins';
.home {
.section {
@media (min-width: $screen-md) {
@include breakoutviewport;
}
}
.section:not(:first-child) {
margin-top: $spacer * 3;
}
.articles,
.photos {
display: grid;
gap: $spacer;
}
.articles {
@media (min-width: $screen-sm) {
gap: $spacer * 2;
grid-template-columns: repeat(2, 1fr);
}
}
.photos {
grid-template-columns: repeat(2, 1fr);
@media (min-width: $screen-sm) {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
}
.title {
composes: pagetitle from '../components/templates/Page.module.scss';
margin-left: 0;
margin-right: 0;
a {
margin: 0;
color: $text-color-light;
}
}

View File

@ -1,57 +1,61 @@
import React, { ReactElement } from 'react'
import { graphql, Link, PageProps } from 'gatsby'
import Page from '../components/templates/Page'
import { graphql, PageProps } from 'gatsby'
import { Post } from '../@types/Post'
import { Image } from '../components/atoms/Image'
import styles from './index.module.scss'
import Featured from '../components/molecules/Featured'
const page = {
frontmatter: {
title: 'home',
description: 'Blog of designer & developer Matthias Kretschmann.'
}
}
import { PhotoThumb } from '../components/templates/Photos'
import PostTeaser from '../components/molecules/PostTeaser'
import PostMore from '../components/templates/Post/More'
export default function Home(props: PageProps): ReactElement {
return (
<Page title={page.frontmatter.title} post={page} section={styles.home}>
<Featured />
Latest Articles & Links
<br />
Latest Photos
</Page>
<>
<section className={styles.section}>
<h2 className={styles.title}>Featured</h2>
<Featured />
</section>
<section className={styles.section}>
<h2 className={styles.title}>
Latest Articles <PostMore to="/archive">All Articles</PostMore>
</h2>
<div className={styles.articles}>
{(props.data as any).latestArticles.edges.map(
({ node }: { node: Post }) => (
<PostTeaser key={node.id} post={node} />
)
)}
</div>
</section>
<section className={styles.section}>
<h2 className={styles.title}>
Latest Photos <PostMore to="/photos">All Photos</PostMore>
</h2>
<div className={styles.photos}>
{(props.data as any).latestPhotos.edges.map(
({ node }: { node: Post }) => (
<PhotoThumb key={node.id} photo={node} />
)
)}
</div>
</section>
</>
)
}
export const homeQuery = graphql`
query {
latestArticles: allMarkdownRemark(
filter: { frontmatter: { type: { eq: "article" } } }
filter: { frontmatter: { type: { ne: "photo" } } }
sort: { order: DESC, fields: [fields___date] }
limit: 5
limit: 6
) {
edges {
node {
frontmatter {
title
type
image {
childImageSharp {
fluid(
maxWidth: 400
maxHeight: 400
quality: 85
cropFocus: CENTER
) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
fields {
slug
}
...PostTeaser
}
}
}
@ -59,7 +63,7 @@ export const homeQuery = graphql`
latestPhotos: allMarkdownRemark(
filter: { frontmatter: { type: { eq: "photo" } } }
sort: { order: DESC, fields: [fields___date] }
limit: 10
limit: 15
) {
edges {
node {
@ -69,14 +73,7 @@ export const homeQuery = graphql`
type
image {
childImageSharp {
fluid(
maxWidth: 400
maxHeight: 400
quality: 85
cropFocus: CENTER
) {
...GatsbyImageSharpFluid_withWebp
}
...PhotoFluidThumb
}
}
}

View File

@ -1,73 +0,0 @@
import React, { ReactElement } from 'react'
import { graphql, Link, PageProps } from 'gatsby'
import Page from '../components/templates/Page'
import { Post } from '../@types/Post'
import { Image } from '../components/atoms/Image'
import styles from './photos.module.scss'
const page = {
frontmatter: {
title: 'Photos',
description: 'Personal photos of designer & developer Matthias Kretschmann.'
}
}
const PhotoThumb = ({ photo }: { photo: Post }): ReactElement => {
const { title, image } = photo.frontmatter
const { slug } = photo.fields
const { fluid } = image.childImageSharp
return (
<article className={styles.photo}>
{image && (
<Link to={slug}>
<Image title={title} fluid={fluid} alt={title} />
</Link>
)}
</article>
)
}
export default function Photos({ data }: { data: any }): ReactElement {
return (
<Page title={page.frontmatter.title} post={page} section={styles.photos}>
{data.photos.edges.map(({ node }: { node: Post }) => (
<PhotoThumb key={node.id} photo={node} />
))}
</Page>
)
}
export const photosQuery = graphql`
query {
photos: allMarkdownRemark(
filter: { frontmatter: { type: { eq: "photo" } } }
sort: { order: DESC, fields: [fields___date] }
) {
edges {
node {
id
frontmatter {
title
type
image {
childImageSharp {
fluid(
maxWidth: 400
maxHeight: 400
quality: 85
cropFocus: CENTER
) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
fields {
slug
}
}
}
}
}
`