mirror of
https://github.com/kremalicious/blog.git
synced 2024-12-22 17:23:50 +01:00
Merge pull request #183 from kremalicious/feature/pagination
pagination refactor
This commit is contained in:
commit
6723265523
@ -28,7 +28,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Goodies',
|
title: 'Goodies',
|
||||||
link: '/goodies'
|
link: '/tags/goodies'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ const {
|
|||||||
generateRedirectPages
|
generateRedirectPages
|
||||||
} = require('./gatsby/createPages')
|
} = require('./gatsby/createPages')
|
||||||
const { generateJsonFeed } = require('./gatsby/feeds')
|
const { generateJsonFeed } = require('./gatsby/feeds')
|
||||||
const { itemsPerPage } = require('./config')
|
|
||||||
|
|
||||||
exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
|
exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
|
||||||
// Markdown files
|
// Markdown files
|
||||||
@ -26,7 +25,7 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||||||
|
|
||||||
const result = await graphql(`
|
const result = await graphql(`
|
||||||
{
|
{
|
||||||
allMarkdownRemark {
|
posts: allMarkdownRemark {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
fields {
|
fields {
|
||||||
@ -38,19 +37,26 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags: allMarkdownRemark {
|
||||||
|
group(field: frontmatter___tags) {
|
||||||
|
tag: fieldValue
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if (result.errors) throw result.errors
|
if (result.errors) throw result.errors
|
||||||
|
|
||||||
const posts = result.data.allMarkdownRemark.edges
|
const posts = result.data.posts.edges
|
||||||
const numPages = Math.ceil(posts.length / itemsPerPage)
|
const tags = result.data.tags.group
|
||||||
|
|
||||||
// Generate posts & posts index
|
// Generate posts & posts index
|
||||||
generatePostPages(createPage, posts, numPages)
|
generatePostPages(createPage, posts)
|
||||||
|
|
||||||
// Generate tag pages
|
// Generate tag pages
|
||||||
generateTagPages(createPage, posts, numPages)
|
generateTagPages(createPage, tags)
|
||||||
|
|
||||||
// Create manual redirects
|
// Create manual redirects
|
||||||
generateRedirectPages(createRedirect)
|
generateRedirectPages(createRedirect)
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const postsTemplate = path.resolve('src/templates/Posts.tsx')
|
const postsTemplate = path.resolve('src/templates/Posts.tsx')
|
||||||
|
const { itemsPerPage } = require('../config')
|
||||||
|
|
||||||
const redirects = [
|
const redirects = [
|
||||||
{ f: '/feed', t: '/feed.xml' },
|
{ f: '/feed', t: '/feed.xml' },
|
||||||
{ f: '/feed/', t: '/feed.xml' }
|
{ f: '/feed/', t: '/feed.xml' },
|
||||||
|
{ f: '/goodies/', t: '/tags/goodies/' }
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.generatePostPages = (createPage, posts, numPages) => {
|
function getPaginationData(i, numPages, slug) {
|
||||||
|
const currentPage = i + 1
|
||||||
|
const prevPageNumber = currentPage <= 1 ? null : currentPage - 1
|
||||||
|
const nextPageNumber = currentPage + 1 > numPages ? null : currentPage + 1
|
||||||
|
const prevPagePath = prevPageNumber
|
||||||
|
? prevPageNumber === 1
|
||||||
|
? slug
|
||||||
|
: `${slug}page/${prevPageNumber}/`
|
||||||
|
: null
|
||||||
|
const nextPagePath = nextPageNumber ? `${slug}page/${nextPageNumber}/` : null
|
||||||
|
const path = i === 0 ? slug : `${slug}page/${i + 1}`
|
||||||
|
|
||||||
|
return { prevPagePath, nextPagePath, path }
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.generatePostPages = (createPage, posts) => {
|
||||||
const postTemplate = path.resolve('src/templates/Post/index.tsx')
|
const postTemplate = path.resolve('src/templates/Post/index.tsx')
|
||||||
|
|
||||||
// Create Post pages
|
// Create Post pages
|
||||||
@ -21,33 +38,59 @@ exports.generatePostPages = (createPage, posts, numPages) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create paginated Blog index pages
|
// Create paginated Blog index pages
|
||||||
|
const numPages = Math.ceil(posts.length / itemsPerPage)
|
||||||
|
const slug = `/`
|
||||||
|
|
||||||
Array.from({ length: numPages }).forEach((_, i) => {
|
Array.from({ length: numPages }).forEach((_, i) => {
|
||||||
|
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
||||||
|
i,
|
||||||
|
numPages,
|
||||||
|
slug
|
||||||
|
)
|
||||||
|
|
||||||
createPage({
|
createPage({
|
||||||
path: i === 0 ? '/' : `/page/${i + 1}`,
|
path,
|
||||||
component: postsTemplate,
|
component: postsTemplate,
|
||||||
context: {
|
context: {
|
||||||
limit: numPages,
|
slug,
|
||||||
skip: i * numPages,
|
limit: itemsPerPage,
|
||||||
|
skip: i * itemsPerPage,
|
||||||
numPages,
|
numPages,
|
||||||
currentPageNumber: i + 1,
|
currentPageNumber: i + 1,
|
||||||
prevPage: i - 1,
|
prevPagePath,
|
||||||
nextPage: i + 2
|
nextPagePath
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateTagPages = (createPage, posts) => {
|
exports.generateTagPages = (createPage, tags) => {
|
||||||
const tagList = arrayReducer(posts, 'tags')
|
tags.forEach(({ tag, totalCount }) => {
|
||||||
|
// Create paginated tag pages
|
||||||
|
const numPages = Math.ceil(totalCount / itemsPerPage)
|
||||||
|
const slug = `/tags/${tag}/`
|
||||||
|
|
||||||
tagList.forEach(tag => {
|
Array.from({ length: numPages }).forEach((_, i) => {
|
||||||
if (tag === 'goodies') return
|
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
||||||
|
i,
|
||||||
|
numPages,
|
||||||
|
slug
|
||||||
|
)
|
||||||
|
|
||||||
// Create tag pages
|
createPage({
|
||||||
createPage({
|
path,
|
||||||
path: `/tags/${tag}/`,
|
component: postsTemplate,
|
||||||
component: postsTemplate,
|
context: {
|
||||||
context: { tag }
|
tag,
|
||||||
|
slug,
|
||||||
|
limit: itemsPerPage,
|
||||||
|
skip: i * itemsPerPage,
|
||||||
|
numPages,
|
||||||
|
currentPageNumber: i + 1,
|
||||||
|
prevPagePath,
|
||||||
|
nextPagePath
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -61,21 +104,3 @@ exports.generateRedirectPages = createRedirect => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// ----------------------
|
|
||||||
//
|
|
||||||
// https://www.adamjberkowitz.com/tags-and-categories-in-gatsby-js/
|
|
||||||
const arrayReducer = (postsArray, type) => {
|
|
||||||
return (postsArray = postsArray
|
|
||||||
.map(({ node }) => {
|
|
||||||
return node.frontmatter[type]
|
|
||||||
})
|
|
||||||
.reduce((a, b) => {
|
|
||||||
return a.concat(b)
|
|
||||||
}, [])
|
|
||||||
.filter((type, index, array) => {
|
|
||||||
return array.indexOf(type) === index
|
|
||||||
})
|
|
||||||
.sort())
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-top: $spacer * 2;
|
margin-top: $spacer * 2;
|
||||||
margin-bottom: $spacer;
|
margin-bottom: $spacer;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
@ -40,4 +40,8 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
border: 1px solid darken($brand-grey-dimmed, 5%);
|
border: 1px solid darken($brand-grey-dimmed, 5%);
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
|
|
||||||
|
:global(.dark) & {
|
||||||
|
border-color: darken($brand-grey-light, 15%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import shortid from 'shortid'
|
|
||||||
import styles from './Pagination.module.scss'
|
import styles from './Pagination.module.scss'
|
||||||
|
|
||||||
function PageNumber({ i, current }: { i: number; current?: boolean }) {
|
const PageNumber = ({
|
||||||
|
i,
|
||||||
|
slug,
|
||||||
|
current
|
||||||
|
}: {
|
||||||
|
i: number
|
||||||
|
slug: string
|
||||||
|
current?: boolean
|
||||||
|
}) => {
|
||||||
const classes = current ? styles.current : styles.number
|
const classes = current ? styles.current : styles.number
|
||||||
const link = i === 0 ? '' : `/page/${i + 1}`
|
const link = i === 0 ? slug : `${slug}page/${i + 1}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link className={classes} to={link}>
|
<Link className={classes} to={link}>
|
||||||
@ -36,28 +43,29 @@ export default function Pagination({
|
|||||||
pageContext
|
pageContext
|
||||||
}: {
|
}: {
|
||||||
pageContext: {
|
pageContext: {
|
||||||
|
slug: string
|
||||||
currentPageNumber: number
|
currentPageNumber: number
|
||||||
numPages: number
|
numPages: number
|
||||||
prevPage?: number
|
prevPagePath?: string
|
||||||
nextPage?: number
|
nextPagePath?: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const { currentPageNumber, numPages, prevPage, nextPage } = pageContext
|
const {
|
||||||
|
slug,
|
||||||
|
currentPageNumber,
|
||||||
|
numPages,
|
||||||
|
prevPagePath,
|
||||||
|
nextPagePath
|
||||||
|
} = pageContext
|
||||||
const isFirst = currentPageNumber === 1
|
const isFirst = currentPageNumber === 1
|
||||||
const isLast = currentPageNumber === numPages
|
const isLast = currentPageNumber === numPages
|
||||||
const prevPagePath = currentPageNumber === 2 ? '/' : `/page/${prevPage}`
|
|
||||||
const nextPagePath = `/page/${nextPage}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.pagination}>
|
<div className={styles.pagination}>
|
||||||
<div>{!isFirst && <PrevNext prevPagePath={prevPagePath} />}</div>
|
<div>{!isFirst && <PrevNext prevPagePath={prevPagePath} />}</div>
|
||||||
<div>
|
<div>
|
||||||
{Array.from({ length: numPages }, (_, i) => (
|
{Array.from({ length: numPages }, (_, i) => (
|
||||||
<PageNumber
|
<PageNumber i={i} slug={slug} current={currentPageNumber === i + 1} />
|
||||||
key={shortid.generate()}
|
|
||||||
i={i}
|
|
||||||
current={currentPageNumber === i + 1}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>{!isLast && <PrevNext nextPagePath={nextPagePath} />}</div>
|
<div>{!isLast && <PrevNext nextPagePath={nextPagePath} />}</div>
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
@import 'mixins';
|
|
||||||
|
|
||||||
.image {
|
|
||||||
@include breakoutviewport;
|
|
||||||
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goodie {
|
|
||||||
@include divider;
|
|
||||||
|
|
||||||
padding-top: $spacer;
|
|
||||||
padding-bottom: $spacer * 2;
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: $spacer;
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { graphql, Link } from 'gatsby'
|
|
||||||
import PostImage from '../templates/Post/PostImage'
|
|
||||||
import Page from '../templates/Page'
|
|
||||||
import styles from './goodies.module.scss'
|
|
||||||
import { Post } from '../@types/Post'
|
|
||||||
|
|
||||||
const page = {
|
|
||||||
frontmatter: {
|
|
||||||
title: 'Goodies',
|
|
||||||
description:
|
|
||||||
'Goodies released by designer & developer Matthias Kretschmann.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const GoodiesThumb = ({ post }: { post: Post }) => {
|
|
||||||
const { title, image } = post.frontmatter
|
|
||||||
const { slug } = post.fields
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article className={styles.goodie}>
|
|
||||||
{image && (
|
|
||||||
<Link to={slug}>
|
|
||||||
<h1 className={styles.title}>{title}</h1>
|
|
||||||
<PostImage fluid={image.childImageSharp.fluid} alt={title} />
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Goodies({
|
|
||||||
data,
|
|
||||||
location
|
|
||||||
}: {
|
|
||||||
data: { goodies: { edges: [{ node: Post }] } }
|
|
||||||
location: Location
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Page title={page.frontmatter.title} post={page} location={location}>
|
|
||||||
{data.goodies.edges.map(({ node }) => (
|
|
||||||
<GoodiesThumb key={node.id} post={node} />
|
|
||||||
))}
|
|
||||||
</Page>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const goodiesQuery = graphql`
|
|
||||||
query {
|
|
||||||
goodies: allMarkdownRemark(
|
|
||||||
filter: { frontmatter: { tags: { eq: "goodies" } } }
|
|
||||||
sort: { order: DESC, fields: [fields___date] }
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
image {
|
|
||||||
childImageSharp {
|
|
||||||
...ImageFluid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
@ -70,7 +70,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
color: $text-color;
|
color: $brand-grey-light;
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
margin-right: $spacer / 2;
|
margin-right: $spacer / 2;
|
||||||
margin-bottom: $spacer / 2;
|
margin-bottom: $spacer / 2;
|
||||||
@ -78,16 +78,8 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
color: $brand-grey-light;
|
|
||||||
content: '#';
|
content: '#';
|
||||||
margin-right: 1px;
|
margin-right: 1px;
|
||||||
}
|
opacity: 0.65;
|
||||||
|
|
||||||
:global(.dark) & {
|
|
||||||
color: $brand-grey-light;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
color: $brand-grey;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.archiveTitle {
|
.archiveTitle {
|
||||||
|
@include divider;
|
||||||
|
|
||||||
font-size: $font-size-h3;
|
font-size: $font-size-h3;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 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;
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,13 @@ export default function Posts({
|
|||||||
location: Location
|
location: Location
|
||||||
pageContext: {
|
pageContext: {
|
||||||
tag: string
|
tag: string
|
||||||
|
slug: string
|
||||||
currentPageNumber: number
|
currentPageNumber: number
|
||||||
numPages: number
|
numPages: number
|
||||||
nextPage: number
|
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const edges = data.allMarkdownRemark.edges
|
const edges = data.allMarkdownRemark.edges
|
||||||
const { tag, currentPageNumber, numPages, nextPage } = 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
|
const { type, linkurl, title, image } = node.frontmatter
|
||||||
@ -75,19 +75,24 @@ export default function Posts({
|
|||||||
<Layout location={location}>
|
<Layout location={location}>
|
||||||
<SEO />
|
<SEO />
|
||||||
{location.pathname === '/' && <Featured />}
|
{location.pathname === '/' && <Featured />}
|
||||||
{tag && <h1 className={styles.archiveTitle}>#{tag}</h1>}
|
{tag && (
|
||||||
{currentPageNumber > 1 && (
|
<h1 className={styles.archiveTitle}>
|
||||||
<h1
|
<span>#</span>
|
||||||
className={styles.archiveTitle}
|
{tag}
|
||||||
>{`Page ${currentPageNumber} / ${numPages}`}</h1>
|
</h1>
|
||||||
|
)}
|
||||||
|
{numPages > 1 && currentPageNumber > 1 && (
|
||||||
|
<h2
|
||||||
|
className={styles.paginationTitle}
|
||||||
|
>{`Page ${currentPageNumber} / ${numPages}`}</h2>
|
||||||
)}
|
)}
|
||||||
{PostsList}
|
{PostsList}
|
||||||
{nextPage && nextPage > 1 && <Pagination pageContext={pageContext} />}
|
{numPages > 1 && <Pagination pageContext={pageContext} />}
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const indexQuery = graphql`
|
export const postsQuery = graphql`
|
||||||
query($tag: String, $skip: Int, $limit: Int) {
|
query($tag: String, $skip: Int, $limit: Int) {
|
||||||
allMarkdownRemark(
|
allMarkdownRemark(
|
||||||
filter: { frontmatter: { tags: { eq: $tag } } }
|
filter: { frontmatter: { tags: { eq: $tag } } }
|
||||||
|
Loading…
Reference in New Issue
Block a user