1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-22 17:23:50 +01:00

realeted posts based on tags

This commit is contained in:
Matthias Kretschmann 2018-09-29 20:09:02 +02:00
parent 590279d62e
commit 6e655b6a6d
Signed by: m
GPG Key ID: 606EEEF3C479A91F
9 changed files with 241 additions and 18 deletions

View File

@ -26,6 +26,10 @@ The whole [blog](https://kremalicious.com) is a React-based Single Page App buil
...
### Related Posts
...
### 🏆 SEO component
Includes a SEO component which automatically switches all required `meta` tags for search engines, Twitter Cards, and Facebook OpenGraph tags based on the browsed route/page.

View File

@ -10,10 +10,10 @@ updated: 2018-07-11 00:52:46+02:00
featured: true
tags:
- goodies
- tutorial
- tor
- macos
- goodies
coinhive: true
---

View File

@ -29,4 +29,12 @@ export const imageSizeDefault = graphql`
}
`
export const imageSizeThumb = graphql`
fragment ImageFluidThumb on ImageSharp {
fluid(maxWidth: 200, maxHeight: 85, quality: 85, cropFocus: CENTER) {
...GatsbyImageSharpFluid_withWebp_noBase64
}
}
`
export default Image

View File

@ -27,15 +27,10 @@
@include ellipsis();
width: 100%;
text-align: center;
color: $text-color;
font-family: $font-family-base;
font-size: $font-size-small;
padding: ($spacer/4) 0;
max-width: 70%;
margin: -($spacer) auto $spacer auto;
@media (min-width: $screen-sm) {
max-width: 50%;
}
margin-top: -($spacer);
margin-bottom: $spacer;
}

View File

@ -16,9 +16,7 @@ const query = graphql`
title
image {
childImageSharp {
fluid(maxWidth: 300, maxHeight: 130, cropFocus: CENTER) {
...GatsbyImageSharpFluid_withWebp_noBase64
}
...ImageFluidThumb
}
}
}

View File

@ -3,17 +3,14 @@ import PropTypes from 'prop-types'
import { Link } from 'gatsby'
import Time from 'react-time'
import slugify from 'slugify'
import PostLinkActions from '../atoms/PostLinkActions'
import styles from './PostMeta.module.scss'
const PostMeta = ({ post, meta }) => {
const { author, updated, tags, type, linkurl } = post.frontmatter
const { date, slug } = post.fields
const { author, updated, tags, type } = post.frontmatter
const { date } = post.fields
return (
<footer className={styles.entryMeta}>
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
<div className={styles.byline}>
<span className={styles.by}>by</span>
<a className="fn" rel="author" href={meta.author.uri}>
@ -45,7 +42,7 @@ const PostMeta = ({ post, meta }) => {
{tags && (
<div className={styles.tags}>
{tags.map(tag => {
const to = tag === 'goodies' ? '/goodies' : `/tag/${slugify(tag)}/`
const to = tag === 'goodies' ? '/goodies' : `/tags/${slugify(tag)}/`
return (
<Link key={tag} className={styles.tag} to={to}>

View File

@ -0,0 +1,120 @@
import React, { Fragment, PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Link, graphql, StaticQuery } from 'gatsby'
import Image from '../atoms/Image'
import styles from './RelatedPosts.module.scss'
const query = graphql`
query {
allMarkdownRemark(sort: { order: DESC, fields: [fields___date] }) {
edges {
node {
id
frontmatter {
title
type
linkurl
tags
image {
childImageSharp {
...ImageFluidThumb
}
}
}
fields {
slug
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
const postsWithDataFilter = (postsArray, key, valuesToFind) => {
const newArray = postsArray.filter(post => {
const frontmatterKey = post.node.frontmatter[key]
if (
frontmatterKey !== null &&
frontmatterKey.some(r => valuesToFind.includes(r))
) {
return post
}
})
return newArray
}
const PostItem = ({ post }) => {
return (
<li>
<Link to={post.node.fields.slug}>
{post.node.frontmatter.image ? (
<Fragment>
<Image
fluid={post.node.frontmatter.image.childImageSharp.fluid}
alt={post.node.frontmatter.title}
/>
<h4 className={styles.postTitle}>{post.node.frontmatter.title}</h4>
</Fragment>
) : (
<div className={styles.empty}>
<h4 className={styles.postTitle}>{post.node.frontmatter.title}</h4>
</div>
)}
</Link>
</li>
)
}
PostItem.propTypes = {
post: PropTypes.object.isRequired
}
class RelatedPosts extends PureComponent {
shufflePosts = () => {
this.forceUpdate()
}
render() {
return (
<StaticQuery
query={query}
render={data => {
const posts = data.allMarkdownRemark.edges
const filteredPosts = postsWithDataFilter(
posts,
'tags',
this.props.tags
)
return (
<aside className={styles.relatedPosts}>
<h1 className={styles.title}>Related Posts</h1>
<ul>
{filteredPosts
.sort(() => 0.5 - Math.random())
.slice(0, 6)
.map(post => (
<PostItem key={post.node.id} post={post} />
))}
</ul>
<button
className={`${styles.button} btn`}
onClick={this.shufflePosts}
>
More Related Posts
</button>
</aside>
)
}}
/>
)
}
}
RelatedPosts.propTypes = {
tags: PropTypes.array.isRequired
}
export default RelatedPosts

View File

@ -0,0 +1,88 @@
@import 'variables';
@import 'mixins';
.empty {
height: 100%;
min-height: 80px;
display: flex;
align-items: center;
padding: $spacer / 4;
h4 {
margin-top: 0;
}
}
.relatedPosts {
margin-top: -($spacer * 2);
margin-bottom: $spacer;
ul {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 0;
margin: 0;
}
li {
display: block;
flex: 0 0 48%;
margin-bottom: $spacer;
@media (min-width: $screen-sm) {
flex-basis: 31%;
}
&::before {
display: none;
}
}
a {
display: block;
> div {
@include media-frame;
}
&:hover,
&:focus {
> div {
border-color: $link-color;
}
h4 {
color: $link-color;
}
}
}
}
.title {
@include heading-band;
font-size: $font-size-h3;
}
.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: .2rem;
padding-right: .2rem;
transition: color .2s ease-out;
@media (min-width: $screen-md) {
font-size: $font-size-base;
}
}
.button {
margin: auto;
display: block;
margin-top: $spacer / 2;
}

View File

@ -8,16 +8,26 @@ import PostTitle from '../components/atoms/PostTitle'
import PostLead from '../components/atoms/PostLead'
import PostContent from '../components/atoms/PostContent'
import PostActions from '../components/atoms/PostActions'
import PostLinkActions from '../components/atoms/PostLinkActions'
import SEO from '../components/atoms/SEO'
import Coinhive from '../components/atoms/Coinhive'
import PostMeta from '../components/molecules/PostMeta'
import Exif from '../components/atoms/Exif'
import RelatedPosts from '../components/molecules/RelatedPosts'
import styles from './Post.module.scss'
const Post = ({ data, location }) => {
const { markdownRemark: post } = data
const { contentYaml: meta } = data
const { title, image, type, linkurl, style, coinhive } = post.frontmatter
const {
title,
image,
type,
linkurl,
style,
coinhive,
tags
} = post.frontmatter
const { slug } = post.fields
return (
@ -37,9 +47,12 @@ const Post = ({ data, location }) => {
)}
{image && image.fields && <Exif exif={image.fields.exif} />}
<PostContent post={post} />
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
<PostActions slug={slug} url={meta.url} />
<PostMeta post={post} meta={meta} />
</article>
{type === 'post' && <RelatedPosts tags={tags} />}
</Layout>
{coinhive && <Coinhive />}
</Fragment>