mirror of
https://github.com/kremalicious/blog.git
synced 2025-01-07 04:04:19 +01:00
more refactor
This commit is contained in:
parent
369c1f1c27
commit
2d20a5a1b1
@ -19,11 +19,11 @@ module.exports = {
|
|||||||
rss: '/feed.xml',
|
rss: '/feed.xml',
|
||||||
jsonfeed: '/feed.json',
|
jsonfeed: '/feed.json',
|
||||||
typekitID: 'msu4qap',
|
typekitID: 'msu4qap',
|
||||||
itemsPerPage: 20,
|
itemsPerPage: 18,
|
||||||
repoContentPath: 'https://github.com/kremalicious/blog/tree/main/content',
|
repoContentPath: 'https://github.com/kremalicious/blog/tree/main/content',
|
||||||
menu: [
|
menu: [
|
||||||
{
|
{
|
||||||
title: 'Articles & Links',
|
title: 'Articles',
|
||||||
link: '/archive'
|
link: '/archive'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ title: Project Purple
|
|||||||
image: ../media/Teaser-Project-Purple.png
|
image: ../media/Teaser-Project-Purple.png
|
||||||
download: ../media/project-purple-kremalicious.zip
|
download: ../media/project-purple-kremalicious.zip
|
||||||
author: Matthias Kretschmann
|
author: Matthias Kretschmann
|
||||||
featured: true
|
|
||||||
|
|
||||||
date: 2012-08-07 13:15:44+00:00
|
date: 2012-08-07 13:15:44+00:00
|
||||||
|
|
||||||
|
@ -3,7 +3,9 @@ const { createExif } = require('./gatsby/createExif')
|
|||||||
const {
|
const {
|
||||||
generatePostPages,
|
generatePostPages,
|
||||||
generateTagPages,
|
generateTagPages,
|
||||||
generateRedirectPages
|
generateRedirectPages,
|
||||||
|
generateArchivePages,
|
||||||
|
generatePhotosPages
|
||||||
} = require('./gatsby/createPages')
|
} = require('./gatsby/createPages')
|
||||||
const { generateJsonFeed } = require('./gatsby/feeds')
|
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 { createPage, createRedirect } = actions
|
||||||
|
|
||||||
const result = await graphql(`
|
const result = await graphql(`
|
||||||
{
|
{
|
||||||
posts: allMarkdownRemark(sort: { order: DESC, fields: [fields___date] }) {
|
all: allMarkdownRemark(sort: { order: DESC, fields: [fields___date] }) {
|
||||||
edges {
|
edges {
|
||||||
next {
|
next {
|
||||||
fields {
|
fields {
|
||||||
@ -53,6 +55,16 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
photos: allMarkdownRemark(
|
||||||
|
filter: { frontmatter: { type: { eq: "photo" } } }
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
archive: allMarkdownRemark(
|
archive: allMarkdownRemark(
|
||||||
filter: { frontmatter: { type: { ne: "photo" } } }
|
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 archiveLength = result.data.archive.edges.length
|
||||||
const tags = result.data.tags.group
|
const tags = result.data.tags.group
|
||||||
|
|
||||||
// Generate posts & posts index
|
// Generate post pages
|
||||||
generatePostPages(createPage, posts, archiveLength)
|
generatePostPages(createPage, all)
|
||||||
|
|
||||||
|
// Generate archive pages
|
||||||
|
generateArchivePages(createPage, archiveLength)
|
||||||
|
|
||||||
|
// Generate archive pages
|
||||||
|
generatePhotosPages(createPage, photosLength)
|
||||||
|
|
||||||
// Generate tag pages
|
// Generate tag pages
|
||||||
generateTagPages(createPage, tags)
|
generateTagPages(createPage, tags)
|
||||||
|
@ -3,6 +3,7 @@ const { itemsPerPage } = require('../config')
|
|||||||
|
|
||||||
const postTemplate = path.resolve('src/components/templates/Post/index.tsx')
|
const postTemplate = path.resolve('src/components/templates/Post/index.tsx')
|
||||||
const archiveTemplate = path.resolve('src/components/templates/Archive.tsx')
|
const archiveTemplate = path.resolve('src/components/templates/Archive.tsx')
|
||||||
|
const photosTemplate = path.resolve('src/components/templates/Photos.tsx')
|
||||||
|
|
||||||
const redirects = [
|
const redirects = [
|
||||||
{ f: '/feed', t: '/feed.xml' },
|
{ f: '/feed', t: '/feed.xml' },
|
||||||
@ -25,7 +26,7 @@ function getPaginationData(i, numPages, slug) {
|
|||||||
return { prevPagePath, nextPagePath, path }
|
return { prevPagePath, nextPagePath, path }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generatePostPages = (createPage, posts, archiveLength) => {
|
exports.generatePostPages = (createPage, posts) => {
|
||||||
// Create Post pages
|
// Create Post pages
|
||||||
posts.forEach((post) => {
|
posts.forEach((post) => {
|
||||||
createPage({
|
createPage({
|
||||||
@ -44,26 +45,56 @@ exports.generatePostPages = (createPage, posts, archiveLength) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generateArchivePages = (createPage, archiveLength) => {
|
||||||
// Create paginated archive pages
|
// Create paginated archive pages
|
||||||
const numPages = Math.ceil(archiveLength / itemsPerPage)
|
const numPagesArchive = Math.ceil(archiveLength / itemsPerPage)
|
||||||
const slug = `/archive/`
|
const slugArchive = `/archive/`
|
||||||
|
|
||||||
Array.from({ length: numPages }).forEach((_, i) => {
|
Array.from({ length: numPagesArchive }).forEach((_, i) => {
|
||||||
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
||||||
i,
|
i,
|
||||||
numPages,
|
numPagesArchive,
|
||||||
slug
|
slugArchive
|
||||||
)
|
)
|
||||||
|
|
||||||
createPage({
|
createPage({
|
||||||
path,
|
path,
|
||||||
component: archiveTemplate,
|
component: archiveTemplate,
|
||||||
context: {
|
context: {
|
||||||
slug,
|
slug: slugArchive,
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
skip: i * 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,
|
currentPageNumber: i + 1,
|
||||||
prevPagePath,
|
prevPagePath,
|
||||||
nextPagePath
|
nextPagePath
|
||||||
|
9
src/@types/Post.d.ts
vendored
9
src/@types/Post.d.ts
vendored
@ -32,3 +32,12 @@ export interface Post {
|
|||||||
fileAbsolutePath?: string
|
fileAbsolutePath?: string
|
||||||
tableOfContents?: string
|
tableOfContents?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageContext {
|
||||||
|
tag?: string
|
||||||
|
slug: string
|
||||||
|
currentPageNumber: number
|
||||||
|
numPages: number
|
||||||
|
prevPagePath?: string
|
||||||
|
nextPagePath?: string
|
||||||
|
}
|
||||||
|
@ -33,7 +33,18 @@ export const imageSizeThumb = graphql`
|
|||||||
original {
|
original {
|
||||||
src
|
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
|
...GatsbyImageSharpFluid_withWebp_noBase64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,59 +2,11 @@
|
|||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
|
|
||||||
.featured {
|
.featured {
|
||||||
@include divider;
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: $spacer / 2;
|
gap: $spacer;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
padding-bottom: $spacer * $line-height;
|
|
||||||
margin-bottom: -($spacer / 2);
|
|
||||||
|
|
||||||
@media (min-width: $screen-md) {
|
@media (min-width: $screen-md) {
|
||||||
@include breakoutviewport;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link, graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import { Image } from '../atoms/Image'
|
|
||||||
import styles from './Featured.module.scss'
|
import styles from './Featured.module.scss'
|
||||||
import { Post } from '../../@types/Post'
|
import { Post } from '../../@types/Post'
|
||||||
|
import PostTeaser from './PostTeaser'
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query {
|
||||||
@ -21,21 +21,12 @@ const query = graphql`
|
|||||||
|
|
||||||
export default function Featured(): ReactElement {
|
export default function Featured(): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery(query)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.featured}>
|
<div className={styles.featured}>
|
||||||
{data.allMarkdownRemark.edges.map(({ node }: { node: Post }) => {
|
{data.allMarkdownRemark.edges.map(({ node }: { node: Post }) => (
|
||||||
const { title, image } = node.frontmatter
|
<PostTeaser key={node.id} post={node} />
|
||||||
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>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,47 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
@import 'mixins';
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
@include breakoutviewport;
|
||||||
margin-top: $spacer * 2;
|
|
||||||
|
margin-top: $spacer * 3;
|
||||||
margin-bottom: $spacer;
|
margin-bottom: $spacer;
|
||||||
justify-content: space-between;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
> div {
|
|
||||||
&:first-child {
|
|
||||||
margin-right: $spacer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-left: $spacer;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 2rem;
|
padding: $spacer / 6 $spacer / 2;
|
||||||
height: 2rem;
|
border: 1px solid $brand-grey-dimmed;
|
||||||
line-height: 1.7;
|
font-variant-numeric: lining-nums;
|
||||||
display: inline-block;
|
margin-left: -1px;
|
||||||
border-radius: 50%;
|
display: flex;
|
||||||
border: 1px solid transparent;
|
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,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border-color: darken($brand-grey-dimmed, 5%);
|
}
|
||||||
|
|
||||||
|
&: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;
|
composes: number;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
border: 1px solid darken($brand-grey-dimmed, 5%);
|
color: $brand-grey-dimmed;
|
||||||
color: $brand-grey-light;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
:global(.dark) & {
|
:global(.dark) & {
|
||||||
border-color: darken($brand-grey-light, 15%);
|
color: $brand-grey-light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import React, { ReactElement } from 'react'
|
|||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import styles from './Pagination.module.scss'
|
import styles from './Pagination.module.scss'
|
||||||
import shortid from 'shortid'
|
import shortid from 'shortid'
|
||||||
|
import { PageContext } from '../../@types/Post'
|
||||||
|
import Icon from '../atoms/Icon'
|
||||||
|
|
||||||
const PageNumber = ({
|
const PageNumber = ({
|
||||||
i,
|
i,
|
||||||
@ -34,8 +36,12 @@ function PrevNext({
|
|||||||
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
|
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link} rel={rel} title={title}>
|
<Link to={link} rel={rel} title={title} className={styles.number}>
|
||||||
{prevPagePath ? '←' : '→'}
|
{prevPagePath ? (
|
||||||
|
<Icon name="ChevronLeft" />
|
||||||
|
) : (
|
||||||
|
<Icon name="ChevronRight" />
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -43,13 +49,7 @@ function PrevNext({
|
|||||||
export default function Pagination({
|
export default function Pagination({
|
||||||
pageContext
|
pageContext
|
||||||
}: {
|
}: {
|
||||||
pageContext: {
|
pageContext: PageContext
|
||||||
slug: string
|
|
||||||
currentPageNumber: number
|
|
||||||
numPages: number
|
|
||||||
prevPagePath?: string
|
|
||||||
nextPagePath?: string
|
|
||||||
}
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const {
|
const {
|
||||||
slug,
|
slug,
|
||||||
@ -63,18 +63,16 @@ export default function Pagination({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.pagination}>
|
<div className={styles.pagination}>
|
||||||
<div>{!isFirst && <PrevNext prevPagePath={prevPagePath} />}</div>
|
{!isFirst && <PrevNext prevPagePath={prevPagePath} />}
|
||||||
<div>
|
{Array.from({ length: numPages }, (_, i) => (
|
||||||
{Array.from({ length: numPages }, (_, i) => (
|
<PageNumber
|
||||||
<PageNumber
|
key={shortid.generate()}
|
||||||
key={shortid.generate()}
|
i={i}
|
||||||
i={i}
|
slug={slug}
|
||||||
slug={slug}
|
current={currentPageNumber === i + 1}
|
||||||
current={currentPageNumber === i + 1}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
{!isLast && <PrevNext nextPagePath={nextPagePath} />}
|
||||||
</div>
|
|
||||||
<div>{!isLast && <PrevNext nextPagePath={nextPagePath} />}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,21 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
|
|
||||||
.post {
|
.title {
|
||||||
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;
|
|
||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
padding-right: 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;
|
transition: color 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: $screen-md) {
|
.post {
|
||||||
font-size: $font-size-base;
|
display: block;
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link, graphql } from 'gatsby'
|
import { Link, graphql } from 'gatsby'
|
||||||
import { Image } from '../atoms/Image'
|
import { Image } from '../atoms/Image'
|
||||||
import styles from './PostTeaser.module.scss'
|
|
||||||
import { Post } from '../../@types/Post'
|
import { Post } from '../../@types/Post'
|
||||||
|
import PostTitle from '../templates/Post/Title'
|
||||||
|
import styles from './PostTeaser.module.scss'
|
||||||
|
|
||||||
export const postTeaserQuery = graphql`
|
export const postTeaserQuery = graphql`
|
||||||
fragment PostTeaser on MarkdownRemark {
|
fragment PostTeaser on MarkdownRemark {
|
||||||
id
|
id
|
||||||
fileAbsolutePath
|
fileAbsolutePath
|
||||||
frontmatter {
|
frontmatter {
|
||||||
|
type
|
||||||
title
|
title
|
||||||
|
linkurl
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
...ImageFluidThumb
|
...ImageFluidThumb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tags
|
||||||
}
|
}
|
||||||
fields {
|
fields {
|
||||||
slug
|
slug
|
||||||
@ -29,7 +33,7 @@ export default function PostTeaser({
|
|||||||
post: Partial<Post>
|
post: Partial<Post>
|
||||||
toggleSearch?: () => void
|
toggleSearch?: () => void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { image, title } = post.frontmatter
|
const { image, title, type } = post.frontmatter
|
||||||
const { slug } = post.fields
|
const { slug } = post.fields
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -38,12 +42,16 @@ export default function PostTeaser({
|
|||||||
to={slug}
|
to={slug}
|
||||||
onClick={toggleSearch && toggleSearch}
|
onClick={toggleSearch && toggleSearch}
|
||||||
>
|
>
|
||||||
{image ? (
|
{image && (
|
||||||
<Image fluid={image.childImageSharp.fluid} alt={title} />
|
<Image
|
||||||
) : (
|
title={type === 'photo' ? title : null}
|
||||||
<div className={styles.empty} />
|
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>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -47,16 +47,16 @@ function photosWithDataFilter(posts: []) {
|
|||||||
|
|
||||||
export default function RelatedPosts({
|
export default function RelatedPosts({
|
||||||
tags,
|
tags,
|
||||||
photos
|
isPhotos
|
||||||
}: {
|
}: {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
photos?: boolean
|
isPhotos?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery(query)
|
||||||
const posts = data.allMarkdownRemark.edges
|
const posts = data.allMarkdownRemark.edges
|
||||||
|
|
||||||
function getPosts() {
|
function getPosts() {
|
||||||
return photos
|
return isPhotos
|
||||||
? photosWithDataFilter(posts)
|
? photosWithDataFilter(posts)
|
||||||
: tags && postsWithDataFilter(posts, 'tags', tags)
|
: tags && postsWithDataFilter(posts, 'tags', tags)
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ export default function RelatedPosts({
|
|||||||
return (
|
return (
|
||||||
<aside className={styles.relatedPosts}>
|
<aside className={styles.relatedPosts}>
|
||||||
<h1 className={styles.title}>
|
<h1 className={styles.title}>
|
||||||
Related {photos ? 'Photos' : 'Posts'}{' '}
|
Related {isPhotos ? 'Photos' : 'Posts'}{' '}
|
||||||
<button className={styles.button} onClick={refreshPosts}>
|
<button className={styles.button} onClick={refreshPosts}>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
|
@ -7,7 +7,7 @@ import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
|||||||
import styles from './Footer.module.scss'
|
import styles from './Footer.module.scss'
|
||||||
|
|
||||||
function Copyright() {
|
function Copyright() {
|
||||||
const { name, uri, bitcoin, github } = useSiteMetadata().author
|
const { name, uri, github } = useSiteMetadata().author
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,7 +24,7 @@ function Copyright() {
|
|||||||
</a>
|
</a>
|
||||||
<Link to="/thanks" className={styles.btc}>
|
<Link to="/thanks" className={styles.btc}>
|
||||||
<Icon name="Bitcoin" />
|
<Icon name="Bitcoin" />
|
||||||
<code>{bitcoin}</code>
|
Say Thanks
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@ -37,7 +37,6 @@ export default class Footer extends PureComponent {
|
|||||||
<footer role="contentinfo" className={styles.footer}>
|
<footer role="contentinfo" className={styles.footer}>
|
||||||
<Container>
|
<Container>
|
||||||
<Vcard />
|
<Vcard />
|
||||||
|
|
||||||
<Copyright />
|
<Copyright />
|
||||||
</Container>
|
</Container>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -1,39 +1,13 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
|
|
||||||
.hentry {
|
.posts {
|
||||||
@include divider;
|
display: grid;
|
||||||
|
gap: $spacer * 2;
|
||||||
|
|
||||||
margin-top: 0;
|
@media (min-width: $screen-sm) {
|
||||||
margin-bottom: 0;
|
@include breakoutviewport;
|
||||||
padding-top: $spacer * 2;
|
|
||||||
padding-bottom: $spacer * 2;
|
|
||||||
|
|
||||||
:global(.gatsby-image-wrapper) {
|
grid-template-columns: repeat(2, 1fr);
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $screen-md) {
|
|
||||||
padding-top: $spacer * 4;
|
|
||||||
padding-bottom: $spacer * 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
@ -1,84 +1,44 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link, graphql } from 'gatsby'
|
import { graphql } from 'gatsby'
|
||||||
import { Post } from '../../@types/Post'
|
import { Post, PageContext } from '../../@types/Post'
|
||||||
import Pagination from '../molecules/Pagination'
|
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 styles from './Archive.module.scss'
|
||||||
import { Image } from '../atoms/Image'
|
import PostTeaser from '../molecules/PostTeaser'
|
||||||
|
import Page from './Page'
|
||||||
|
|
||||||
export default function Archive({
|
export default function Archive({
|
||||||
data,
|
data,
|
||||||
pageContext
|
pageContext
|
||||||
}: {
|
}: {
|
||||||
data: any
|
data: any
|
||||||
pageContext: {
|
pageContext: PageContext
|
||||||
tag: string
|
|
||||||
slug: string
|
|
||||||
currentPageNumber: number
|
|
||||||
numPages: number
|
|
||||||
}
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const edges = data.allMarkdownRemark.edges
|
const edges = data.allMarkdownRemark.edges
|
||||||
const { tag, currentPageNumber, numPages } = pageContext
|
const { tag, currentPageNumber, numPages } = pageContext
|
||||||
|
|
||||||
const PostsList = edges.map(({ node }: { node: Post }) => {
|
const PostsList = edges.map(({ node }: { node: Post }) => (
|
||||||
const { type, linkurl, title, image } = node.frontmatter
|
<PostTeaser key={node.id} post={node} />
|
||||||
const { slug } = node.fields
|
))
|
||||||
|
|
||||||
return (
|
const title = tag ? `#${tag}` : 'Archive'
|
||||||
<article className={styles.hentry} key={node.id}>
|
|
||||||
<PostTitle type={type} slug={slug} linkurl={linkurl} title={title} />
|
|
||||||
|
|
||||||
{image && (
|
const paginationTitle =
|
||||||
<Link to={slug} title={title}>
|
numPages > 1 && currentPageNumber > 1
|
||||||
<Image
|
? `Page ${currentPageNumber} / ${numPages}`
|
||||||
title={type === 'photo' ? title : null}
|
: ''
|
||||||
fluid={image.childImageSharp.fluid}
|
|
||||||
alt={title}
|
|
||||||
original={image.childImageSharp.original}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{type === 'article' && (
|
const page = {
|
||||||
<>
|
frontmatter: {
|
||||||
<PostLead post={node} index />
|
title: `${title} ${paginationTitle}`,
|
||||||
<PostMore to={slug}>Continue Reading</PostMore>
|
description: 'All the articles & links.'
|
||||||
</>
|
}
|
||||||
)}
|
}
|
||||||
|
|
||||||
{type === 'link' && (
|
|
||||||
<>
|
|
||||||
<PostContent post={node} />
|
|
||||||
<PostLinkActions slug={slug} linkurl={linkurl} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Page title={page.frontmatter.title} post={page}>
|
||||||
<SEO />
|
<div className={styles.posts}>{PostsList}</div>
|
||||||
{tag && (
|
|
||||||
<h1 className={styles.archivetitle}>
|
|
||||||
<span>#</span>
|
|
||||||
{tag}
|
|
||||||
</h1>
|
|
||||||
)}
|
|
||||||
{numPages > 1 && currentPageNumber > 1 && (
|
|
||||||
<h2
|
|
||||||
className={styles.paginationTitle}
|
|
||||||
>{`Page ${currentPageNumber} / ${numPages}`}</h2>
|
|
||||||
)}
|
|
||||||
{PostsList}
|
|
||||||
{numPages > 1 && <Pagination pageContext={pageContext} />}
|
{numPages > 1 && <Pagination pageContext={pageContext} />}
|
||||||
</>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,21 +52,7 @@ export const archiveQuery = graphql`
|
|||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
...PostTeaser
|
||||||
html
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
type
|
|
||||||
linkurl
|
|
||||||
image {
|
|
||||||
childImageSharp {
|
|
||||||
...ImageFluid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
@import 'mixins';
|
||||||
|
|
||||||
.pageTitle {
|
.pagetitle {
|
||||||
composes: archivetitle from './Archive.module.scss';
|
@include divider;
|
||||||
margin-bottom: $spacer * 2;
|
|
||||||
|
@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;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ export default function Page({
|
|||||||
<Helmet title={title} />
|
<Helmet title={title} />
|
||||||
<SEO slug={pathname} postSEO post={post} />
|
<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}
|
{section ? <section className={section}>{children}</section> : children}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -2,16 +2,14 @@
|
|||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
|
|
||||||
.photos {
|
.photos {
|
||||||
@include breakoutviewport--full;
|
@include breakoutviewport;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: $spacer;
|
gap: $spacer;
|
||||||
padding-left: $spacer;
|
|
||||||
padding-right: $spacer;
|
|
||||||
|
|
||||||
@media (min-width: $screen-sm) {
|
@media (min-width: $screen-sm) {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
import Photos from '../photos'
|
import Photos from './Photos'
|
||||||
import data from '../../../jest/__fixtures__/photos.json'
|
import data from '../../../jest/__fixtures__/photos.json'
|
||||||
|
|
||||||
describe('/photos', () => {
|
describe('/photos', () => {
|
||||||
it('renders without crashing', () => {
|
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()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
87
src/components/templates/Photos.tsx
Normal file
87
src/components/templates/Photos.tsx
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@ -10,7 +10,7 @@ const PostContent = ({ post }: { post: Post }): ReactElement => {
|
|||||||
|
|
||||||
let content = post.html
|
let content = post.html
|
||||||
|
|
||||||
if (post.frontmatter.type === 'post') {
|
if (post.frontmatter.type === 'article') {
|
||||||
if (content.includes(separator)) {
|
if (content.includes(separator)) {
|
||||||
content = content.split(separator)[1]
|
content = content.split(separator)[1]
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,10 +6,10 @@ import { Post } from '../../../@types/Post'
|
|||||||
// Grab everything before more tag, or just first paragraph
|
// Grab everything before more tag, or just first paragraph
|
||||||
const PostLead = ({
|
const PostLead = ({
|
||||||
post,
|
post,
|
||||||
index
|
className
|
||||||
}: {
|
}: {
|
||||||
post: Post
|
post: Partial<Post>
|
||||||
index?: boolean
|
className?: string
|
||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
let lead
|
let lead
|
||||||
const content = post.html
|
const content = post.html
|
||||||
@ -23,7 +23,7 @@ const PostLead = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={index ? styles.index : styles.lead}
|
className={`${styles.lead} ${className && className}`}
|
||||||
dangerouslySetInnerHTML={{ __html: lead }}
|
dangerouslySetInnerHTML={{ __html: lead }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -38,7 +38,7 @@ export default function PostMeta({ post }: { post: Post }): ReactElement {
|
|||||||
{tags && (
|
{tags && (
|
||||||
<div className={styles.tags}>
|
<div className={styles.tags}>
|
||||||
{tags.map((tag: string) => {
|
{tags.map((tag: string) => {
|
||||||
const url = `/tags/${slugify(tag)}/`
|
const url = `/archive/${slugify(tag)}/`
|
||||||
return <Tag key={shortid.generate()} name={tag} url={url} />
|
return <Tag key={shortid.generate()} name={tag} url={url} />
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link } from 'gatsby'
|
|
||||||
import styles from './Title.module.scss'
|
import styles from './Title.module.scss'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
import Time from '../../atoms/Time'
|
import Time from '../../atoms/Time'
|
||||||
|
|
||||||
export default function PostTitle({
|
export default function PostTitle({
|
||||||
type,
|
|
||||||
slug,
|
slug,
|
||||||
linkurl,
|
linkurl,
|
||||||
title,
|
title,
|
||||||
date,
|
date,
|
||||||
updated
|
updated,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
type?: string
|
|
||||||
slug?: string
|
slug?: string
|
||||||
linkurl?: string
|
linkurl?: string
|
||||||
title: string
|
title: string
|
||||||
date?: string
|
date?: string
|
||||||
updated?: string
|
updated?: string
|
||||||
|
className?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const linkHostname = linkurl ? new URL(linkurl).hostname : null
|
const linkHostname = linkurl ? new URL(linkurl).hostname : null
|
||||||
|
|
||||||
return type === 'link' ? (
|
return linkurl ? (
|
||||||
<>
|
<>
|
||||||
<h1
|
<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}`}>
|
<a href={linkurl} title={`Go to source: ${linkurl}`}>
|
||||||
{title} <Icon name="ExternalLink" />
|
{title} <Icon name="ExternalLink" />
|
||||||
@ -33,12 +34,14 @@ export default function PostTitle({
|
|||||||
<div className={styles.linkurl}>{linkHostname}</div>
|
<div className={styles.linkurl}>{linkHostname}</div>
|
||||||
</>
|
</>
|
||||||
) : slug ? (
|
) : slug ? (
|
||||||
<h1 className={styles.hentry__title}>
|
<h1 className={`${styles.hentry__title} ${className && className}`}>
|
||||||
<Link to={slug}>{title}</Link>
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h1 className={styles.hentry__title}>{title}</h1>
|
<h1 className={`${styles.hentry__title} ${className && className}`}>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
{date && (
|
{date && (
|
||||||
<div className={styles.time}>
|
<div className={styles.time}>
|
||||||
{updated && 'published '}
|
{updated && 'published '}
|
||||||
|
@ -40,35 +40,38 @@ export default function Post({
|
|||||||
<article className={styles.hentry}>
|
<article className={styles.hentry}>
|
||||||
<header>
|
<header>
|
||||||
<PostTitle
|
<PostTitle
|
||||||
type={type}
|
|
||||||
linkurl={linkurl}
|
linkurl={linkurl}
|
||||||
title={title}
|
title={title}
|
||||||
date={date}
|
date={date}
|
||||||
updated={updated}
|
updated={updated}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{type === 'article' && <PostLead post={post} />}
|
{type === 'article' && <PostLead post={post} />}
|
||||||
|
|
||||||
|
{image && (
|
||||||
|
<Image
|
||||||
|
fluid={image.childImageSharp.fluid}
|
||||||
|
alt={title}
|
||||||
|
original={image.childImageSharp.original}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{type === 'photo' && <PostContent post={post} />}
|
{type === 'photo' ? (
|
||||||
{image && (
|
<>
|
||||||
<Image
|
<PostContent post={post} />
|
||||||
fluid={image.childImageSharp.fluid}
|
{image && image.fields && <Exif exif={image.fields.exif} />}
|
||||||
alt={title}
|
</>
|
||||||
original={image.childImageSharp.original}
|
) : (
|
||||||
/>
|
<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} />}
|
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
|
||||||
<PostMeta post={post} />
|
<PostMeta post={post} />
|
||||||
<PostActions slug={slug} githubLink={githubLink} />
|
<PostActions slug={slug} githubLink={githubLink} />
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<RelatedPosts photos={type === 'photo'} tags={tags} />
|
<RelatedPosts isPhotos={type === 'photo'} tags={tags} />
|
||||||
|
|
||||||
<PrevNext prev={prev} next={next} />
|
<PrevNext prev={prev} next={next} />
|
||||||
</>
|
</>
|
||||||
|
@ -1,4 +1,44 @@
|
|||||||
@import 'variables';
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,61 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { graphql, Link, PageProps } from 'gatsby'
|
import { graphql, PageProps } from 'gatsby'
|
||||||
import Page from '../components/templates/Page'
|
|
||||||
import { Post } from '../@types/Post'
|
import { Post } from '../@types/Post'
|
||||||
import { Image } from '../components/atoms/Image'
|
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
import Featured from '../components/molecules/Featured'
|
import Featured from '../components/molecules/Featured'
|
||||||
|
import { PhotoThumb } from '../components/templates/Photos'
|
||||||
const page = {
|
import PostTeaser from '../components/molecules/PostTeaser'
|
||||||
frontmatter: {
|
import PostMore from '../components/templates/Post/More'
|
||||||
title: 'home',
|
|
||||||
description: 'Blog of designer & developer Matthias Kretschmann.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home(props: PageProps): ReactElement {
|
export default function Home(props: PageProps): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Page title={page.frontmatter.title} post={page} section={styles.home}>
|
<>
|
||||||
<Featured />
|
<section className={styles.section}>
|
||||||
Latest Articles & Links
|
<h2 className={styles.title}>Featured</h2>
|
||||||
<br />
|
<Featured />
|
||||||
Latest Photos
|
</section>
|
||||||
</Page>
|
|
||||||
|
<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`
|
export const homeQuery = graphql`
|
||||||
query {
|
query {
|
||||||
latestArticles: allMarkdownRemark(
|
latestArticles: allMarkdownRemark(
|
||||||
filter: { frontmatter: { type: { eq: "article" } } }
|
filter: { frontmatter: { type: { ne: "photo" } } }
|
||||||
sort: { order: DESC, fields: [fields___date] }
|
sort: { order: DESC, fields: [fields___date] }
|
||||||
limit: 5
|
limit: 6
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
frontmatter {
|
...PostTeaser
|
||||||
title
|
|
||||||
type
|
|
||||||
image {
|
|
||||||
childImageSharp {
|
|
||||||
fluid(
|
|
||||||
maxWidth: 400
|
|
||||||
maxHeight: 400
|
|
||||||
quality: 85
|
|
||||||
cropFocus: CENTER
|
|
||||||
) {
|
|
||||||
...GatsbyImageSharpFluid_withWebp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +63,7 @@ export const homeQuery = graphql`
|
|||||||
latestPhotos: allMarkdownRemark(
|
latestPhotos: allMarkdownRemark(
|
||||||
filter: { frontmatter: { type: { eq: "photo" } } }
|
filter: { frontmatter: { type: { eq: "photo" } } }
|
||||||
sort: { order: DESC, fields: [fields___date] }
|
sort: { order: DESC, fields: [fields___date] }
|
||||||
limit: 10
|
limit: 15
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -69,14 +73,7 @@ export const homeQuery = graphql`
|
|||||||
type
|
type
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
fluid(
|
...PhotoFluidThumb
|
||||||
maxWidth: 400
|
|
||||||
maxHeight: 400
|
|
||||||
quality: 85
|
|
||||||
cropFocus: CENTER
|
|
||||||
) {
|
|
||||||
...GatsbyImageSharpFluid_withWebp
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
Loading…
Reference in New Issue
Block a user