1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-12-23 01:29:41 +01:00

Merge pull request #65 from kremalicious/feature/tweaks

tweaks
This commit is contained in:
Matthias Kretschmann 2018-11-25 02:30:45 +01:00 committed by GitHub
commit 90bb4c65e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 272 additions and 254 deletions

View File

@ -42,10 +42,11 @@ The whole [portfolio](https://matthiaskretschmann.com) is a React-based Single P
### 💍 One data file to rule all pages
All content is powered by one YAML file where all the portfolio's projects are defined. The project description itself is transformed from Markdown written inside the YAML file.
All content is powered by one YAML file where all the portfolio's projects are defined. The project description itself is transformed from Markdown written inside the YAML file int HTML on build time.
Gatsby automatically creates pages from each item in that file utilizing the [`Project.jsx`](src/templates/Project.jsx) template.
- [`gatsby-node.js`](gatsby-node.js)
- [`data/projects.yml`](data/projects.yml)
- [`src/templates/Project.jsx`](src/templates/Project.jsx)

View File

@ -1,4 +1,36 @@
const path = require('path')
const remark = require('remark')
const markdown = require('remark-parse')
const html = require('remark-html')
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
// Projects YAML nodes
if (node.internal.type === 'ProjectsYaml') {
// Add transformed Markdown descriptions
const description = node.description
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
let descriptionHtml
remark()
.use(markdown, { gfm: true, commonmark: true, pedantic: true })
.use(html)
.process(descriptionWithLineBreaks, (err, file) => {
if (err) throw Error('Could not transform project description')
descriptionHtml = file.contents
return descriptionHtml
})
createNodeField({
node,
name: 'descriptionHtml',
value: descriptionHtml
})
}
}
//
// Create project pages from projects.yml

View File

@ -21,15 +21,15 @@
"new": "babel-node ./scripts/new.js"
},
"dependencies": {
"file-saver": "^2.0.0-rc.4",
"gatsby": "^2.0.50",
"file-saver": "^2.0.0",
"gatsby": "^2.0.55",
"gatsby-image": "^2.0.20",
"gatsby-plugin-favicon": "^3.1.4",
"gatsby-plugin-matomo": "^0.5.0",
"gatsby-plugin-offline": "^2.0.15",
"gatsby-plugin-matomo": "^0.5.1",
"gatsby-plugin-offline": "^2.0.17",
"gatsby-plugin-react-helmet": "^3.0.2",
"gatsby-plugin-sass": "^2.0.4",
"gatsby-plugin-sharp": "^2.0.12",
"gatsby-plugin-sharp": "^2.0.13",
"gatsby-plugin-sitemap": "^2.0.2",
"gatsby-plugin-svgr": "^2.0.1",
"gatsby-source-filesystem": "^2.0.8",
@ -44,8 +44,10 @@
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-helmet": "^5.2.0",
"react-markdown": "^4.0.3",
"react-pose": "^4.0.2",
"remark": "^10.0.1",
"remark-html": "^9.0.0",
"remark-parse": "^6.0.3",
"suncalc": "^1.8.0",
"vcf": "^2.0.1"
},
@ -53,23 +55,23 @@
"@babel/core": "^7.1.6",
"@babel/node": "^7.0.0",
"@babel/preset-env": "^7.1.6",
"@svgr/webpack": "^4.0.3",
"@svgr/webpack": "^4.1.0",
"ava": "^0.25.0",
"babel-eslint": "^10.0.0",
"babel-eslint": "^10.0.1",
"chrome-launcher": "^0.10.5",
"eslint": "^5.8.0",
"eslint-config-prettier": "^3.1.0",
"eslint-loader": "^2.1.0",
"eslint": "^5.9.0",
"eslint-config-prettier": "^3.3.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-graphql": "^3.0.1",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1",
"lighthouse": "^3.2.1",
"lighthouse": "^4.0.0-alpha.2-3.2.1",
"ora": "^3.0.0",
"prepend": "^1.0.2",
"prettier": "^1.15.1",
"prettier-stylelint": "^0.4.2",
"slugify": "^1.3.2",
"stylelint": "^9.7.1",
"slugify": "^1.3.3",
"stylelint": "^9.8.0",
"stylelint-config-css-modules": "^1.3.0",
"stylelint-config-standard": "^18.2.0",
"why-did-you-update": "^1.0.6"

View File

@ -1,8 +1,8 @@
import React, { PureComponent, Fragment } from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import posed, { PoseGroup } from 'react-pose'
import { fadeIn } from './atoms/Transitions'
import Head from './molecules/Head'
import Typekit from './atoms/Typekit'
import Header from './organisms/Header'
import Footer from './organisms/Footer'
import styles from './Layout.module.scss'
@ -27,8 +27,9 @@ export default class Layout extends PureComponent {
const RoutesContainer = posed.div(fadeIn)
return (
<Fragment>
<Head />
<>
<Typekit />
<PoseGroup animateOnMount={true}>
<RoutesContainer
key={location.pathname}
@ -39,8 +40,9 @@ export default class Layout extends PureComponent {
<main className={styles.screen}>{children}</main>
</RoutesContainer>
</PoseGroup>
<Footer />
</Fragment>
</>
)
}
}

View File

@ -49,7 +49,8 @@ const SEO = ({ project }) => (
render={data => {
const meta = data.dataYaml
const title = project.title ? project.title : meta.title
const title = project.title || meta.title
const tagline = meta.tagline
const description = project.description
? truncate.apply(project.description, [320, true])
: truncate.apply(meta.description, [320, true])
@ -59,9 +60,14 @@ const SEO = ({ project }) => (
const url = project.slug ? `${meta.url}${project.slug}` : meta.url
return (
<Helmet>
<Helmet
defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
>
<html lang="en" />
<title>{title}</title>
{/* General tags */}
<meta name="description" content={description} />
<meta name="image" content={`${meta.url}${image}`} />

View File

@ -34,7 +34,6 @@ const Typekit = () => (
return (
typekitID && (
<Helmet>
<link rel="dns-prefetch" href="https://use.typekit.net/" />
<link rel="dns-prefetch" href="https://p.typekit.net/" />
{TypekitScript(typekitID)}

View File

@ -1,43 +0,0 @@
import React, { Fragment } from 'react'
import Helmet from 'react-helmet'
import { StaticQuery, graphql } from 'gatsby'
import SEO from '../atoms/SEO'
import Typekit from '../atoms/Typekit'
const query = graphql`
query {
dataYaml {
title
tagline
}
}
`
const Head = () => (
<StaticQuery
query={query}
render={data => {
const { title, tagline } = data.dataYaml
return (
<Fragment>
<Helmet
defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
>
<meta
name="apple-mobile-web-app-title"
content={title.toLowerCase()}
/>
</Helmet>
<Typekit />
<SEO />
</Fragment>
)
}}
/>
)
export default Head

View File

@ -51,7 +51,6 @@
transform-origin: top center;
transform-box: border-box;
// stylelint-disable no-descending-specificity
.logounit__title,
.logounit__description {
color: $text-color-light;
@ -60,7 +59,6 @@
color: $text-color-light--dark;
}
}
// stylelint-enable no-descending-specificity
.logounit__logo {
margin-bottom: $spacer / 3;

View File

@ -27,20 +27,22 @@ const query = graphql`
}
`
const NetworkIcon = props => {
switch (props.title) {
case 'Email':
return <Email {...props} />
case 'Blog':
return <Blog {...props} />
case 'Twitter':
return <Twitter {...props} />
case 'GitHub':
return <GitHub {...props} />
case 'Dribbble':
return <Dribbble {...props} />
default:
return null
class NetworkIcon extends PureComponent {
render() {
switch (this.props.title) {
case 'Email':
return <Email {...this.props} />
case 'Blog':
return <Blog {...this.props} />
case 'Twitter':
return <Twitter {...this.props} />
case 'GitHub':
return <GitHub {...this.props} />
case 'Dribbble':
return <Dribbble {...this.props} />
default:
return null
}
}
}

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Button from '../atoms/Button'
@ -50,31 +50,33 @@ const LinkIcon = ({ title, type, ...props }) => {
}
}
const ProjectLinks = ({ links }) => (
<div className={styles.projectLinks}>
<h3 className={styles.title}>
Links <span>Learn more on the interwebz.</span>
</h3>
export default class ProjectLinks extends PureComponent {
static propTypes = {
links: PropTypes.array
}
<ul>
{links.map(link => {
const { title, url, type } = link
render() {
return (
<div className={styles.projectLinks}>
<h3 className={styles.title}>
Links <span>Learn more on the interwebz.</span>
</h3>
return (
<li key={title}>
<Button href={url}>
<LinkIcon title={title} type={type} className={icons.icon} />
{title}
</Button>
</li>
)
})}
</ul>
</div>
)
<ul>
{this.props.links.map(link => {
const { title, url, type } = link
ProjectLinks.propTypes = {
links: PropTypes.array
return (
<li key={title}>
<Button href={url}>
<LinkIcon title={title} type={type} className={icons.icon} />
{title}
</Button>
</li>
)
})}
</ul>
</div>
)
}
}
export default ProjectLinks

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Link, graphql, StaticQuery } from 'gatsby'
import Img from 'gatsby-image'
@ -24,18 +24,24 @@ const query = graphql`
}
`
const ProjectLink = ({ node }) => (
<Link className={styles.link} to={node.slug}>
<Img
className={styles.image}
fluid={node.img.childImageSharp.fluid}
alt={node.title}
/>
<h1 className={styles.title}>{node.title}</h1>
</Link>
)
class ProjectLink extends PureComponent {
render() {
const { node } = this.props
export default class ProjectNav extends Component {
return (
<Link className={styles.link} to={node.slug}>
<Img
className={styles.image}
fluid={node.img.childImageSharp.fluid}
alt={node.title}
/>
<h1 className={styles.title}>{node.title}</h1>
</Link>
)
}
}
export default class ProjectNav extends PureComponent {
state = {
scrolledToCurrent: false
}

View File

@ -1,6 +1,5 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import { Link, StaticQuery, graphql } from 'gatsby'
import Vcard from '../atoms/Vcard'
import LogoUnit from '../molecules/LogoUnit'
import Networks from '../molecules/Networks'
@ -23,33 +22,30 @@ const query = graphql`
}
`
const FooterMarkup = ({ meta, pkg, year }) => (
<footer className={styles.footer}>
<LogoUnit minimal />
<Networks minimal />
<p className={styles.footer__actions}>
<Vcard />
<a href={meta.gpg}>PGP/GPG key</a>
<a href={pkg.bugs}>Found a bug?</a>
</p>
<p className={styles.footer__copyright}>
<small>
&copy; {year} {meta.title} &mdash; All Rights Reserved
</small>
</p>
</footer>
)
FooterMarkup.propTypes = {
meta: PropTypes.object,
pkg: PropTypes.object,
year: PropTypes.number
}
export default class Footer extends PureComponent {
state = { year: new Date().getFullYear() }
FooterMarkup = ({ meta, pkg, year }) => (
<footer className={styles.footer}>
<Link to={'/'}>
<LogoUnit minimal />
</Link>
<Networks minimal />
<p className={styles.footer__actions}>
<Vcard />
<a href={meta.gpg}>PGP/GPG key</a>
<a href={pkg.bugs}>Found a bug?</a>
</p>
<p className={styles.footer__copyright}>
<small>
&copy; {year} {meta.title} &mdash; All Rights Reserved
</small>
</p>
</footer>
)
render() {
return (
<StaticQuery
@ -58,7 +54,9 @@ export default class Footer extends PureComponent {
const pkg = data.portfolioJson
const meta = data.dataYaml
return <FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
return (
<this.FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
)
}}
/>
)

View File

@ -18,6 +18,10 @@ const query = graphql`
`
export default class Header extends PureComponent {
static propTypes = {
minimal: PropTypes.bool
}
state = { isMinimal: this.props.minimal }
checkMinimal = () => {
@ -61,7 +65,3 @@ export default class Header extends PureComponent {
)
}
}
Header.propTypes = {
minimal: PropTypes.bool
}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Link } from 'gatsby'
import giphyAPI from 'giphy-js-sdk-core'
import SEO from '../components/atoms/SEO'
import Button from '../components/atoms/Button'
import styles from './404.module.scss'
@ -38,22 +39,26 @@ export default class NotFound extends Component {
render() {
return (
<article className={styles.content}>
<h1>Shenanigans, page not found.</h1>
<p>
You might want to check the url, or{' '}
<Link to={'/'}>go back to the homepage</Link>. Or just check out some{' '}
{tag} gifs, entirely your choice.
</p>
<>
<SEO />
<video className="gif" src={this.state.gif} autoPlay loop />
<article className={styles.content}>
<h1>Shenanigans, page not found.</h1>
<p>
You might want to check the url, or{' '}
<Link to={'/'}>go back to the homepage</Link>. Or just check out
some {tag} gifs, entirely your choice.
</p>
<div>
<Button
onClick={this.handleClick}
>{`Get another '${tag}' gif`}</Button>
</div>
</article>
<video className="gif" src={this.state.gif} autoPlay loop />
<div>
<Button
onClick={this.handleClick}
>{`Get another '${tag}' gif`}</Button>
</div>
</article>
</>
)
}
}

View File

@ -1,62 +1,66 @@
import React from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Link, graphql } from 'gatsby'
import SEO from '../components/atoms/SEO'
import ProjectImage from '../components/molecules/ProjectImage'
import { ReactComponent as Images } from '../images/images.svg'
import styles from './index.module.scss'
const getImageCount = (images, slug) => {
function getImageCount(images, slug) {
let array = []
let slugWithoutSlashes = slug.replace(/\//g, '')
images.map(({ node }) => {
if (node.name.includes(slugWithoutSlashes)) {
array.push(node)
}
})
images.map(
({ node }) => node.name.includes(slugWithoutSlashes) && array.push(node)
)
return array.length
}
const Home = ({ data }) => {
const projects = data.allProjectsYaml.edges
const images = data.projectImageFiles.edges
export default class Home extends PureComponent {
static propTypes = {
data: PropTypes.object,
location: PropTypes.object
}
return (
<div className={styles.projects}>
{projects.map(({ node }) => {
const { slug, title, img } = node
const imageCount = getImageCount(images, slug)
render() {
const { data } = this.props
const projects = data.allProjectsYaml.edges
const images = data.projectImageFiles.edges
return (
<article className={styles.project} key={slug}>
<Link to={slug}>
<h1 className={styles.title}>{title}</h1>
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
return (
<>
<SEO />
{imageCount > 1 && (
<small
className={styles.imageCount}
title={`${imageCount} project images`}
>
<Images /> {imageCount}
</small>
)}
</Link>
</article>
)
})}
</div>
)
<div className={styles.projects}>
{projects.map(({ node }) => {
const { slug, title, img } = node
const imageCount = getImageCount(images, slug)
return (
<article className={styles.project} key={slug}>
<Link to={slug}>
<h1 className={styles.title}>{title}</h1>
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
{imageCount > 1 && (
<small
className={styles.imageCount}
title={`${imageCount} project images`}
>
<Images /> {imageCount}
</small>
)}
</Link>
</article>
)
})}
</div>
</>
)
}
}
Home.propTypes = {
data: PropTypes.object,
location: PropTypes.object
}
export default Home
export const IndexQuery = graphql`
query {
allProjectsYaml {

View File

@ -1,7 +1,5 @@
import React, { Fragment } from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import ReactMarkdown from 'react-markdown'
import { graphql } from 'gatsby'
import FullWidth from '../components/atoms/FullWidth'
import ProjectImage from '../components/molecules/ProjectImage'
@ -11,12 +9,23 @@ import ProjectNav from '../components/molecules/ProjectNav'
import SEO from '../components/atoms/SEO'
import styles from './Project.module.scss'
const ProjectMeta = ({ links, techstack }) => (
<footer className={styles.meta}>
{!!links && <ProjectLinks links={links} />}
{!!techstack && <ProjectTechstack techstack={techstack} />}
</footer>
)
class ProjectMeta extends PureComponent {
static propTypes = {
links: PropTypes.array,
techstack: PropTypes.array
}
render() {
const { links, techstack } = this.props
return (
<footer className={styles.meta}>
{!!links && <ProjectLinks links={links} />}
{!!techstack && <ProjectTechstack techstack={techstack} />}
</footer>
)
}
}
const ProjectImages = ({ projectImages, title }) => (
<FullWidth>
@ -28,52 +37,44 @@ const ProjectImages = ({ projectImages, title }) => (
</FullWidth>
)
const Project = ({ data }) => {
const project = data.projectsYaml
const projectImages = data.projectImages.edges
const { title, links, techstack } = project
const description = data.projectsYaml.description
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
return (
<Fragment>
<Helmet title={title} />
<SEO project={project} />
<article>
<header>
<h1 className={styles.title}>{title}</h1>
</header>
<ReactMarkdown
source={descriptionWithLineBreaks}
className={styles.description}
/>
<ProjectImages projectImages={projectImages} title={title} />
<ProjectMeta links={links} techstack={techstack} />
</article>
<ProjectNav slug={project.slug} />
</Fragment>
)
}
ProjectMeta.propTypes = {
links: PropTypes.array,
techstack: PropTypes.array
}
ProjectImages.propTypes = {
projectImages: PropTypes.array,
title: PropTypes.string
}
Project.propTypes = {
data: PropTypes.object.isRequired,
location: PropTypes.object.isRequired
}
export default class Project extends PureComponent {
static propTypes = {
data: PropTypes.object.isRequired,
location: PropTypes.object.isRequired
}
export default Project
render() {
const project = this.props.data.projectsYaml
const projectImages = this.props.data.projectImages.edges
const descriptionHtml = this.props.data.projectsYaml.fields.descriptionHtml
const { title, links, techstack } = project
return (
<>
<SEO project={project} />
<article>
<header>
<h1 className={styles.title}>{title}</h1>
</header>
<div
className={styles.description}
dangerouslySetInnerHTML={{ __html: descriptionHtml }}
/>
<ProjectImages projectImages={projectImages} title={title} />
<ProjectMeta links={links} techstack={techstack} />
</article>
<ProjectNav slug={project.slug} />
</>
)
}
}
export const projectAndProjectsQuery = graphql`
query($slug: String!) {
@ -81,6 +82,9 @@ export const projectAndProjectsQuery = graphql`
title
slug
description
fields {
descriptionHtml
}
links {
title
url