From 8d5e2d6a7e6c3ed356e7024edfb3448af86dcca1 Mon Sep 17 00:00:00 2001
From: Matthias Kretschmann
Date: Sun, 26 May 2019 16:55:56 +0200
Subject: [PATCH 1/4] open source section
---
.travis.yml | 3 +-
content/meta.yml | 56 +++++++++---------
content/repos.yml | 11 ++++
gatsby-config.js | 2 +-
jest/__fixtures__/meta.json | 2 +-
package.json | 1 +
src/components/Layout.jsx | 4 +-
src/components/atoms/Button.module.scss | 2 +-
src/components/atoms/SEO.jsx | 4 +-
src/components/atoms/Typekit.jsx | 4 +-
src/components/atoms/Vcard.jsx | 4 +-
src/components/atoms/Vcard.test.jsx | 4 +-
.../molecules/Availability.module.scss | 2 +-
.../molecules/Availability.test.jsx | 4 +-
src/components/molecules/LogoUnit.jsx | 4 +-
src/components/molecules/LogoUnit.test.jsx | 2 +-
src/components/molecules/Networks.test.jsx | 2 +-
.../molecules/ProjectImage.module.scss | 2 +-
.../molecules/ProjectTechstack.module.scss | 2 +-
src/components/molecules/Repository.jsx | 46 +++++++++++++++
.../molecules/Repository.module.scss | 59 +++++++++++++++++++
src/components/organisms/Footer.jsx | 4 +-
src/components/organisms/Header.jsx | 4 +-
src/components/organisms/Repositories.jsx | 47 +++++++++++++++
.../organisms/Repositories.module.scss | 29 +++++++++
src/hooks/use-meta.js | 6 +-
src/images/star.svg | 3 +
src/pages/index.jsx | 8 +++
src/store/AppProvider.jsx | 21 +++----
src/styles/_variables.scss | 1 +
30 files changed, 272 insertions(+), 71 deletions(-)
create mode 100644 content/repos.yml
create mode 100644 src/components/molecules/Repository.jsx
create mode 100644 src/components/molecules/Repository.module.scss
create mode 100644 src/components/organisms/Repositories.jsx
create mode 100644 src/components/organisms/Repositories.module.scss
create mode 100644 src/images/star.svg
diff --git a/.travis.yml b/.travis.yml
index a1469fc..ebec289 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
dist: xenial
language: node_js
-node_js:
- - '11'
+node_js: node
cache:
directories:
diff --git a/content/meta.yml b/content/meta.yml
index 46647ff..8f02107 100644
--- a/content/meta.yml
+++ b/content/meta.yml
@@ -1,34 +1,34 @@
-title: Matthias Kretschmann
-tagline: Designer & Developer
-description: Portfolio of web & ui designer/developer hybrid Matthias Kretschmann.
-url: https://matthiaskretschmann.com
-email: m@kretschmann.io
-avatar: ../src/images/avatar.jpg
-img: ../src/images/twitter-card.png
+- title: Matthias Kretschmann
+ tagline: Designer & Developer
+ description: Portfolio of web & ui designer/developer hybrid Matthias Kretschmann.
+ url: https://matthiaskretschmann.com
+ email: m@kretschmann.io
+ avatar: ../src/images/avatar.jpg
+ img: ../src/images/twitter-card.png
-social:
- Email: mailto:m@kretschmann.io
- Blog: https://kremalicious.com
- Twitter: https://twitter.com/kremalicious
- GitHub: https://github.com/kremalicious
- Dribbble: https://dribbble.com/kremalicious
+ social:
+ Email: mailto:m@kretschmann.io
+ Blog: https://kremalicious.com
+ Twitter: https://twitter.com/kremalicious
+ GitHub: https://github.com/kremalicious
+ Dribbble: https://dribbble.com/kremalicious
-availability:
- status: false
- available: 'π Available for new projects. Letβs talk!'
- unavailable: Not available for new projects.
+ availability:
+ status: false
+ available: 'π Available for new projects. Letβs talk!'
+ unavailable: Not available for new projects.
-# Footer actions
-gpg: https://kretschmann.io/pub.gpg
-addressbook: /matthias-kretschmann.vcf
+ # Footer actions
+ gpg: https://kretschmann.io/pub.gpg
+ addressbook: /matthias-kretschmann.vcf
-typekitID: dtg3zui
+ typekitID: dtg3zui
-# Analytics tools
-matomoUrl: https://analytics.kremalicious.com
-matomoSite: 2
+ # Analytics tools
+ matomoUrl: https://analytics.kremalicious.com
+ matomoSite: 2
-allowedHosts:
- - matthiaskretschmann.com
- - beta.matthiaskretschmann.com
- - localhost
+ allowedHosts:
+ - matthiaskretschmann.com
+ - beta.matthiaskretschmann.com
+ - localhost
diff --git a/content/repos.yml b/content/repos.yml
new file mode 100644
index 0000000..a982972
--- /dev/null
+++ b/content/repos.yml
@@ -0,0 +1,11 @@
+- user: kremalicious
+ repos:
+ - portfolio
+ - blog
+ - blowfish
+ - gatsby-plugin-matomo
+ - gatsby-redirect-from
+ - hyper-mac-pro
+ - appstorebadges
+ - kbdfun
+ - wp-icons-template
diff --git a/gatsby-config.js b/gatsby-config.js
index b3702fa..71cb387 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -2,7 +2,7 @@ const path = require('path')
const fs = require('fs')
const yaml = require('js-yaml')
const meta = yaml.load(fs.readFileSync('./content/meta.yml', 'utf8'))
-const { title, description, url, matomoSite, matomoUrl } = meta
+const { title, description, url, matomoSite, matomoUrl } = meta[0]
module.exports = {
siteMetadata: {
diff --git a/jest/__fixtures__/meta.json b/jest/__fixtures__/meta.json
index c4704af..5320c95 100644
--- a/jest/__fixtures__/meta.json
+++ b/jest/__fixtures__/meta.json
@@ -1,5 +1,5 @@
{
- "contentYaml": {
+ "metaYaml": {
"title": "Matthias Kretschmann",
"tagline": "Designer & Developer",
"description": "Portfolio of web & ui designer/developer hybrid Matthias Kretschmann.",
diff --git a/package.json b/package.json
index 17495ff..66d5054 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"new": "babel-node ./scripts/new.js"
},
"dependencies": {
+ "axios": "^0.18.0",
"classnames": "^2.2.6",
"file-saver": "^2.0.1",
"gatsby": "^2.7.1",
diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx
index ef17191..b696d87 100644
--- a/src/components/Layout.jsx
+++ b/src/components/Layout.jsx
@@ -16,7 +16,7 @@ import styles from './Layout.module.scss'
const query = graphql`
query {
- contentYaml {
+ metaYaml {
allowedHosts
}
}
@@ -41,7 +41,7 @@ export default class Layout extends PureComponent {
{
- const { allowedHosts } = data.contentYaml
+ const { allowedHosts } = data.metaYaml
return (
<>
diff --git a/src/components/atoms/Button.module.scss b/src/components/atoms/Button.module.scss
index edb727d..59e1cfb 100644
--- a/src/components/atoms/Button.module.scss
+++ b/src/components/atoms/Button.module.scss
@@ -5,7 +5,7 @@
width: 100%;
color: $brand-cyan;
text-align: center;
- border-radius: .25rem;
+ border-radius: $border-radius;
padding: $spacer / 4 $spacer / 2;
transition-property: all;
background: rgba(#fff, .15);
diff --git a/src/components/atoms/SEO.jsx b/src/components/atoms/SEO.jsx
index 0081220..b67d986 100644
--- a/src/components/atoms/SEO.jsx
+++ b/src/components/atoms/SEO.jsx
@@ -5,7 +5,7 @@ import { StaticQuery, graphql } from 'gatsby'
const query = graphql`
query {
- contentYaml {
+ metaYaml {
title
tagline
description
@@ -75,7 +75,7 @@ export default class SEO extends PureComponent {
query={query}
render={data => {
const { project } = this.props
- const meta = data.contentYaml
+ const meta = data.metaYaml
const title = (project && project.title) || null
const description =
(project && project.fields.excerpt) || meta.description
diff --git a/src/components/atoms/Typekit.jsx b/src/components/atoms/Typekit.jsx
index 29e6a28..f69fec0 100644
--- a/src/components/atoms/Typekit.jsx
+++ b/src/components/atoms/Typekit.jsx
@@ -19,7 +19,7 @@ const TypekitScript = typekitID => (
const query = graphql`
query {
- contentYaml {
+ metaYaml {
typekitID
}
}
@@ -29,7 +29,7 @@ const Typekit = () => (
{
- const { typekitID } = data.contentYaml
+ const { typekitID } = data.metaYaml
return (
typekitID && (
diff --git a/src/components/atoms/Vcard.jsx b/src/components/atoms/Vcard.jsx
index e6b25a8..9795578 100644
--- a/src/components/atoms/Vcard.jsx
+++ b/src/components/atoms/Vcard.jsx
@@ -5,7 +5,7 @@ import vCard from 'vcf'
const query = graphql`
query {
- contentYaml {
+ metaYaml {
title
tagline
description
@@ -37,7 +37,7 @@ export default class Vcard extends PureComponent {
{
- const meta = data.contentYaml
+ const meta = data.metaYaml
const handleAddressbookClick = e => {
e.preventDefault()
diff --git a/src/components/atoms/Vcard.test.jsx b/src/components/atoms/Vcard.test.jsx
index 39bd2b4..aac10df 100644
--- a/src/components/atoms/Vcard.test.jsx
+++ b/src/components/atoms/Vcard.test.jsx
@@ -24,14 +24,14 @@ describe('Vcard', () => {
})
it('combined vCard download process finishes', async () => {
- await init(data.contentYaml)
+ await init(data.metaYaml)
expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1)
})
it('vCard can be constructed', async () => {
const vcard = await constructVcard(
'data:image/jpeg;base64,00',
- data.contentYaml
+ data.metaYaml
)
expect(vcard).toBeDefined()
})
diff --git a/src/components/molecules/Availability.module.scss b/src/components/molecules/Availability.module.scss
index 7898fca..b7ab80d 100644
--- a/src/components/molecules/Availability.module.scss
+++ b/src/components/molecules/Availability.module.scss
@@ -1,7 +1,7 @@
@import 'variables';
.availability {
- border-radius: .25rem;
+ border-radius: $border-radius;
color: $text-color-light;
z-index: 2;
padding: $spacer / 2;
diff --git a/src/components/molecules/Availability.test.jsx b/src/components/molecules/Availability.test.jsx
index d656d2a..ace6a2b 100644
--- a/src/components/molecules/Availability.test.jsx
+++ b/src/components/molecules/Availability.test.jsx
@@ -14,7 +14,7 @@ describe('Availability', () => {
it('renders correctly when status: true', () => {
useStaticQuery.mockImplementationOnce(() => {
return {
- contentYaml: {
+ metaYaml: {
availability: {
status: true,
available: 'I am available.',
@@ -32,7 +32,7 @@ describe('Availability', () => {
it('renders correctly when status: false', () => {
useStaticQuery.mockImplementationOnce(() => {
return {
- contentYaml: {
+ metaYaml: {
availability: {
status: false,
available: 'I am available.',
diff --git a/src/components/molecules/LogoUnit.jsx b/src/components/molecules/LogoUnit.jsx
index 1f8c67b..f6f656a 100644
--- a/src/components/molecules/LogoUnit.jsx
+++ b/src/components/molecules/LogoUnit.jsx
@@ -9,7 +9,7 @@ import styles from './LogoUnit.module.scss'
const query = graphql`
query {
- contentYaml {
+ metaYaml {
title
tagline
}
@@ -35,7 +35,7 @@ export default class LogoUnit extends PureComponent {
{
- const { title, tagline } = data.contentYaml
+ const { title, tagline } = data.metaYaml
return (
diff --git a/src/components/molecules/LogoUnit.test.jsx b/src/components/molecules/LogoUnit.test.jsx
index 190f710..81b3142 100644
--- a/src/components/molecules/LogoUnit.test.jsx
+++ b/src/components/molecules/LogoUnit.test.jsx
@@ -10,7 +10,7 @@ beforeEach(() => {
describe('LogoUnit', () => {
it('renders correctly from data file values', () => {
- const { title, tagline } = data.contentYaml
+ const { title, tagline } = data.metaYaml
const { container, getByTestId } = render(
)
expect(container.firstChild).toBeInTheDocument()
diff --git a/src/components/molecules/Networks.test.jsx b/src/components/molecules/Networks.test.jsx
index 921e86f..b65d5b5 100644
--- a/src/components/molecules/Networks.test.jsx
+++ b/src/components/molecules/Networks.test.jsx
@@ -10,7 +10,7 @@ beforeEach(() => {
describe('Networks', () => {
it('renders correctly from data file values', () => {
- const { social } = data.contentYaml
+ const { social } = data.metaYaml
const { container, getByTestId } = render(
)
expect(container.firstChild).toBeInTheDocument()
diff --git a/src/components/molecules/ProjectImage.module.scss b/src/components/molecules/ProjectImage.module.scss
index 1284614..b4524d4 100644
--- a/src/components/molecules/ProjectImage.module.scss
+++ b/src/components/molecules/ProjectImage.module.scss
@@ -9,7 +9,7 @@
@media (min-width: $projectImageMaxWidth) {
max-width: $projectImageMaxWidth;
- border-radius: .25rem;
+ border-radius: $border-radius;
overflow: hidden;
}
diff --git a/src/components/molecules/ProjectTechstack.module.scss b/src/components/molecules/ProjectTechstack.module.scss
index 200fd80..08f9f47 100644
--- a/src/components/molecules/ProjectTechstack.module.scss
+++ b/src/components/molecules/ProjectTechstack.module.scss
@@ -15,7 +15,7 @@
padding: $spacer / 4;
text-align: center;
background: rgba(#fff, .15);
- border-radius: .25rem;
+ border-radius: $border-radius;
border: .05rem solid transparent;
color: $brand-grey-light;
font-size: $font-size-small;
diff --git a/src/components/molecules/Repository.jsx b/src/components/molecules/Repository.jsx
new file mode 100644
index 0000000..9982930
--- /dev/null
+++ b/src/components/molecules/Repository.jsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { ReactComponent as Star } from '../../images/star.svg'
+import styles from './Repository.module.scss'
+import LinkIcon from '../atoms/LinkIcon'
+
+const Repository = ({ repo }) => {
+ const { name, description, html_url, homepage, stargazers_count } = repo
+
+ // for blog & portfolio and if there's no homepage, use github url
+ // else use homepage field
+ const repoLink =
+ name === 'blog' || name === 'portfolio' || !homepage ? html_url : homepage
+
+ return (
+
+ )
+}
+
+Repository.propTypes = {
+ repo: PropTypes.object.isRequired
+}
+
+export default Repository
diff --git a/src/components/molecules/Repository.module.scss b/src/components/molecules/Repository.module.scss
new file mode 100644
index 0000000..5d335ed
--- /dev/null
+++ b/src/components/molecules/Repository.module.scss
@@ -0,0 +1,59 @@
+@import 'variables';
+
+.repo {
+ padding: $spacer;
+ border-radius: $border-radius;
+ background: rgba(#fff, .15);
+ box-shadow: 0 3px 5px rgba($brand-main, .1),
+ 0 5px 16px rgba($brand-main, .1);
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+
+ :global(.dark) & {
+ background: darken($body-background-color--dark, 1%);
+ box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), .1),
+ 0 5px 16px rgba(darken($brand-main, 20%), .1);
+ }
+
+ > * {
+ width: 100%;
+ }
+
+ p {
+ font-size: $font-size-small;
+ }
+
+ p:last-child {
+ margin: 0;
+ }
+}
+
+.repoTitle {
+ font-size: $font-size-h4;
+ margin-bottom: $spacer / 2;
+}
+
+.meta {
+ font-size: $font-size-small;
+ align-self: flex-end;
+ display: flex;
+ justify-content: space-between;
+
+ a {
+ display: inline-block;
+ color: $brand-grey-light;
+ font-variant-numeric: lining-nums;
+
+ &:hover,
+ &:focus {
+ color: $brand-cyan;
+ }
+ }
+
+ svg {
+ fill: currentColor;
+ width: $font-size-mini;
+ height: $font-size-mini;
+ }
+}
diff --git a/src/components/organisms/Footer.jsx b/src/components/organisms/Footer.jsx
index cd100b0..154e7fa 100644
--- a/src/components/organisms/Footer.jsx
+++ b/src/components/organisms/Footer.jsx
@@ -14,7 +14,7 @@ const query = graphql`
bugs
}
- contentYaml {
+ metaYaml {
title
url
gpg
@@ -68,7 +68,7 @@ export default class Footer extends PureComponent {
query={query}
render={data => {
const pkg = data.portfolioJson
- const meta = data.contentYaml
+ const meta = data.metaYaml
return
}}
diff --git a/src/components/organisms/Header.jsx b/src/components/organisms/Header.jsx
index 4de942b..4e9aa56 100644
--- a/src/components/organisms/Header.jsx
+++ b/src/components/organisms/Header.jsx
@@ -10,7 +10,7 @@ import styles from './Header.module.scss'
const query = graphql`
query {
- contentYaml {
+ metaYaml {
availability {
status
}
@@ -30,7 +30,7 @@ export default class Header extends PureComponent {
{
- const meta = data.contentYaml
+ const meta = data.metaYaml
let headerClasses = classNames([styles.header], {
[styles.minimal]: minimal
diff --git a/src/components/organisms/Repositories.jsx b/src/components/organisms/Repositories.jsx
new file mode 100644
index 0000000..55e7c82
--- /dev/null
+++ b/src/components/organisms/Repositories.jsx
@@ -0,0 +1,47 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import axios from 'axios'
+import Repository from '../molecules/Repository'
+import styles from './Repositories.module.scss'
+
+export default class Repositories extends PureComponent {
+ static propTypes = {
+ user: PropTypes.string.isRequired,
+ repos: PropTypes.array.isRequired
+ }
+
+ state = { repos: [] }
+
+ async componentDidMount() {
+ try {
+ const repos = await this.getGithubRepos(this.props.user)
+ this.setState({ repos })
+ } catch (error) {
+ console.error(error.message) // eslint-disable-line
+ }
+ }
+
+ async getGithubRepos(user) {
+ const allRepos = await axios.get(
+ `https://api.github.com/users/${user}/repos?per_page=100`
+ )
+ const repos = allRepos.data
+ .filter(({ name }) => this.props.repos.includes(name))
+ .sort((a, b) => b.pushed_at.localeCompare(a.pushed_at)) // sort by pushed to, newest first
+
+ return repos
+ }
+
+ render() {
+ return (
+
+ Open Source Projects
+
+ {this.state.repos.map(repo => (
+
+ ))}
+
+
+ )
+ }
+}
diff --git a/src/components/organisms/Repositories.module.scss b/src/components/organisms/Repositories.module.scss
new file mode 100644
index 0000000..e733182
--- /dev/null
+++ b/src/components/organisms/Repositories.module.scss
@@ -0,0 +1,29 @@
+@import 'variables';
+
+.section {
+ max-width: calc(#{$projectImageMaxWidth} - #{$spacer * 2});
+ margin: $spacer * 3 auto 0 auto;
+ padding-left: $spacer;
+ padding-right: $spacer;
+}
+
+.sectionTitle {
+ font-size: $font-size-h3;
+ margin-bottom: $spacer * 2;
+ text-align: center;
+}
+
+.repos {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-gap: $spacer * 2;
+
+ @media (min-width: $screen-sm) {
+ grid-template-columns: 1fr 1fr;
+ grid-gap: $spacer * 2;
+ }
+
+ @media (min-width: $screen-md) {
+ grid-gap: $spacer * 3;
+ }
+}
diff --git a/src/hooks/use-meta.js b/src/hooks/use-meta.js
index 47b0d9a..ca41e21 100644
--- a/src/hooks/use-meta.js
+++ b/src/hooks/use-meta.js
@@ -2,7 +2,7 @@ import { useStaticQuery, graphql } from 'gatsby'
const query = graphql`
query Meta {
- contentYaml {
+ metaYaml {
title
tagline
description
@@ -31,6 +31,6 @@ const query = graphql`
`
export const useMeta = () => {
- const { contentYaml } = useStaticQuery(query)
- return contentYaml
+ const { metaYaml } = useStaticQuery(query)
+ return metaYaml
}
diff --git a/src/images/star.svg b/src/images/star.svg
new file mode 100644
index 0000000..4fbb9b6
--- /dev/null
+++ b/src/images/star.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index a3ae107..0c85b1d 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -5,6 +5,7 @@ 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'
+import Repositories from '../components/organisms/Repositories'
function getImageCount(images, slug) {
let array = []
@@ -56,6 +57,8 @@ export default class Home extends PureComponent {
)
})}
+
+
>
)
}
@@ -88,5 +91,10 @@ export const IndexQuery = graphql`
}
}
}
+
+ reposYaml {
+ user
+ repos
+ }
}
`
diff --git a/src/store/AppProvider.jsx b/src/store/AppProvider.jsx
index c6f2dab..eae4c63 100644
--- a/src/store/AppProvider.jsx
+++ b/src/store/AppProvider.jsx
@@ -5,22 +5,23 @@ import { getLocationTimes } from '../utils/getLocationTimes'
import { getCountry } from '../utils/getCountry'
export default class AppProvider extends PureComponent {
+ static propTypes = {
+ children: PropTypes.any.isRequired
+ }
+
state = {
dark: false,
toggleDark: () => this.toggleDark(),
geolocation: null
}
- static propTypes = {
- children: PropTypes.any.isRequired
- }
-
store = typeof localStorage === 'undefined' ? null : localStorage
mounted = false
async componentDidMount() {
this.mounted = true
+
const geolocation = await getCountry()
this.setState({ geolocation })
this.checkDark()
@@ -30,16 +31,12 @@ export default class AppProvider extends PureComponent {
this.mounted = false
}
- setDark() {
- this.mounted && this.setState({ dark: true })
- }
-
- setLight() {
- this.mounted && this.setState({ dark: false })
+ setDark(dark) {
+ this.mounted && this.setState({ dark })
}
darkLocalStorageMode(darkLocalStorage) {
- darkLocalStorage === 'true' ? this.setDark() : this.setLight()
+ darkLocalStorage === 'true' ? this.setDark(true) : this.setDark(false)
}
//
@@ -51,7 +48,7 @@ export default class AppProvider extends PureComponent {
const now = new Date().getHours()
const weWantItDarkTimes = now >= sunset || now <= sunrise
- !dark && weWantItDarkTimes ? this.setDark() : this.setLight()
+ !dark && weWantItDarkTimes ? this.setDark(true) : this.setDark(false)
}
async checkDark() {
diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss
index 52a4acc..00e1ef2 100644
--- a/src/styles/_variables.scss
+++ b/src/styles/_variables.scss
@@ -67,6 +67,7 @@ $color-headings--dark: $brand-main-light;
/////////////////////////////////////
$spacer: ($font-size-base * $line-height);
+$border-radius: .25rem;
// Responsive breakpoints
/////////////////////////////////////
From c008007d74d0a18a13e2a5bc19884e77f480fd80 Mon Sep 17 00:00:00 2001
From: Matthias Kretschmann
Date: Sun, 26 May 2019 17:50:19 +0200
Subject: [PATCH 2/4] fetch repos on build time
---
gatsby-node.js | 44 +++++++++++++++++++++++
src/components/organisms/Repositories.jsx | 27 ++------------
src/pages/index.jsx | 14 +++-----
3 files changed, 51 insertions(+), 34 deletions(-)
diff --git a/gatsby-node.js b/gatsby-node.js
index 5fd1526..c42b4ef 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -1,7 +1,13 @@
+/* eslint-disable no-console */
+
const path = require('path')
const remark = require('remark')
const markdown = require('remark-parse')
const html = require('remark-html')
+const axios = require('axios')
+const fs = require('fs')
+const yaml = require('js-yaml')
+const reposYaml = yaml.load(fs.readFileSync('./content/repos.yml', 'utf8'))
function truncate(n, useWordBoundary) {
if (this.length <= n) {
@@ -15,6 +21,30 @@ function truncate(n, useWordBoundary) {
)
}
+async function getGithubRepos(data) {
+ const allRepos = await axios.get(
+ `https://api.github.com/users/${data.user}/repos?per_page=100`
+ )
+ const repos = allRepos.data
+ // filter by what's defined in content/repos.yml
+ .filter(({ name }) => data.repos.includes(name))
+ // sort by pushed to, newest first
+ .sort((a, b) => b.pushed_at.localeCompare(a.pushed_at))
+
+ return repos
+}
+
+let repos
+
+exports.onPreBootstrap = async () => {
+ try {
+ repos = await getGithubRepos(reposYaml[0])
+ console.log(`success getGithubRepos: ${repos.length} repos`)
+ } catch (error) {
+ console.error(error.message)
+ }
+}
+
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
@@ -53,6 +83,20 @@ exports.onCreateNode = ({ node, actions }) => {
}
}
+exports.onCreatePage = async ({ page, actions }) => {
+ const { createPage } = actions
+
+ // Add repos to front page's context
+ if (page.path === '/')
+ createPage({
+ ...page,
+ context: {
+ ...page.context,
+ repos
+ }
+ })
+}
+
//
// Create project pages from projects.yml
//
diff --git a/src/components/organisms/Repositories.jsx b/src/components/organisms/Repositories.jsx
index 55e7c82..3535ffd 100644
--- a/src/components/organisms/Repositories.jsx
+++ b/src/components/organisms/Repositories.jsx
@@ -1,43 +1,20 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import axios from 'axios'
+
import Repository from '../molecules/Repository'
import styles from './Repositories.module.scss'
export default class Repositories extends PureComponent {
static propTypes = {
- user: PropTypes.string.isRequired,
repos: PropTypes.array.isRequired
}
- state = { repos: [] }
-
- async componentDidMount() {
- try {
- const repos = await this.getGithubRepos(this.props.user)
- this.setState({ repos })
- } catch (error) {
- console.error(error.message) // eslint-disable-line
- }
- }
-
- async getGithubRepos(user) {
- const allRepos = await axios.get(
- `https://api.github.com/users/${user}/repos?per_page=100`
- )
- const repos = allRepos.data
- .filter(({ name }) => this.props.repos.includes(name))
- .sort((a, b) => b.pushed_at.localeCompare(a.pushed_at)) // sort by pushed to, newest first
-
- return repos
- }
-
render() {
return (
Open Source Projects
- {this.state.repos.map(repo => (
+ {this.props.repos.map(repo => (
))}
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 0c85b1d..598b016 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -20,12 +20,13 @@ function getImageCount(images, slug) {
export default class Home extends PureComponent {
static propTypes = {
- data: PropTypes.object,
- location: PropTypes.object
+ data: PropTypes.object.isRequired,
+ pageContext: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired
}
render() {
- const { data } = this.props
+ const { data, pageContext } = this.props
const projects = data.allProjectsYaml.edges
const images = data.projectImageFiles.edges
@@ -58,7 +59,7 @@ export default class Home extends PureComponent {
})}
-
+
>
)
}
@@ -91,10 +92,5 @@ export const IndexQuery = graphql`
}
}
}
-
- reposYaml {
- user
- repos
- }
}
`
From 05b1115533ad722f467cdde98257ef2e72dd0f33 Mon Sep 17 00:00:00 2001
From: Matthias Kretschmann
Date: Sun, 26 May 2019 22:20:16 +0200
Subject: [PATCH 3/4] add tests
---
gatsby-node.js | 13 ++-
jest/__fixtures__/repos.json | 102 ++++++++++++++++++
package.json | 1 +
src/components/atoms/LinkIcon.jsx | 3 +
src/components/molecules/Repository.jsx | 5 +-
src/components/molecules/Repository.test.jsx | 40 +++++++
.../organisms/Repositories.test.jsx | 11 ++
src/pages/__tests__/index.test.jsx | 10 +-
src/pages/index.jsx | 3 +-
9 files changed, 180 insertions(+), 8 deletions(-)
create mode 100644 jest/__fixtures__/repos.json
create mode 100644 src/components/molecules/Repository.test.jsx
create mode 100644 src/components/organisms/Repositories.test.jsx
diff --git a/gatsby-node.js b/gatsby-node.js
index c42b4ef..a5dbc8e 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -8,6 +8,8 @@ const axios = require('axios')
const fs = require('fs')
const yaml = require('js-yaml')
const reposYaml = yaml.load(fs.readFileSync('./content/repos.yml', 'utf8'))
+const { performance } = require('perf_hooks')
+const chalk = require('chalk')
function truncate(n, useWordBoundary) {
if (this.length <= n) {
@@ -37,11 +39,18 @@ async function getGithubRepos(data) {
let repos
exports.onPreBootstrap = async () => {
+ const t0 = performance.now()
+
try {
repos = await getGithubRepos(reposYaml[0])
- console.log(`success getGithubRepos: ${repos.length} repos`)
+ const t1 = performance.now()
+ const ms = t1 - t0
+ const s = ((ms / 1000) % 60).toFixed(3)
+ console.log(
+ chalk.green('success ') + `getGithubRepos: ${repos.length} repos - ${s} s`
+ )
} catch (error) {
- console.error(error.message)
+ throw Error(error.message)
}
}
diff --git a/jest/__fixtures__/repos.json b/jest/__fixtures__/repos.json
new file mode 100644
index 0000000..609c8f7
--- /dev/null
+++ b/jest/__fixtures__/repos.json
@@ -0,0 +1,102 @@
+[
+ {
+ "id": 133283555,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMzMyODM1NTU=",
+ "name": "portfolio",
+ "full_name": "kremalicious/portfolio",
+ "private": false,
+ "owner": {
+ "login": "kremalicious",
+ "id": 90316,
+ "node_id": "MDQ6VXNlcjkwMzE2",
+ "avatar_url": "https://avatars1.githubusercontent.com/u/90316?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/kremalicious",
+ "html_url": "https://github.com/kremalicious",
+ "followers_url": "https://api.github.com/users/kremalicious/followers",
+ "following_url": "https://api.github.com/users/kremalicious/following{/other_user}",
+ "gists_url": "https://api.github.com/users/kremalicious/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/kremalicious/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/kremalicious/subscriptions",
+ "organizations_url": "https://api.github.com/users/kremalicious/orgs",
+ "repos_url": "https://api.github.com/users/kremalicious/repos",
+ "events_url": "https://api.github.com/users/kremalicious/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/kremalicious/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "html_url": "https://github.com/kremalicious/portfolio",
+ "description": "π Portfolio thingy, built with Gatsby",
+ "fork": false,
+ "url": "https://api.github.com/repos/kremalicious/portfolio",
+ "forks_url": "https://api.github.com/repos/kremalicious/portfolio/forks",
+ "keys_url": "https://api.github.com/repos/kremalicious/portfolio/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/kremalicious/portfolio/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/kremalicious/portfolio/teams",
+ "hooks_url": "https://api.github.com/repos/kremalicious/portfolio/hooks",
+ "issue_events_url": "https://api.github.com/repos/kremalicious/portfolio/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/kremalicious/portfolio/events",
+ "assignees_url": "https://api.github.com/repos/kremalicious/portfolio/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/kremalicious/portfolio/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/kremalicious/portfolio/tags",
+ "blobs_url": "https://api.github.com/repos/kremalicious/portfolio/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/kremalicious/portfolio/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/kremalicious/portfolio/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/kremalicious/portfolio/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/kremalicious/portfolio/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/kremalicious/portfolio/languages",
+ "stargazers_url": "https://api.github.com/repos/kremalicious/portfolio/stargazers",
+ "contributors_url": "https://api.github.com/repos/kremalicious/portfolio/contributors",
+ "subscribers_url": "https://api.github.com/repos/kremalicious/portfolio/subscribers",
+ "subscription_url": "https://api.github.com/repos/kremalicious/portfolio/subscription",
+ "commits_url": "https://api.github.com/repos/kremalicious/portfolio/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/kremalicious/portfolio/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/kremalicious/portfolio/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/kremalicious/portfolio/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/kremalicious/portfolio/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/kremalicious/portfolio/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/kremalicious/portfolio/merges",
+ "archive_url": "https://api.github.com/repos/kremalicious/portfolio/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/kremalicious/portfolio/downloads",
+ "issues_url": "https://api.github.com/repos/kremalicious/portfolio/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/kremalicious/portfolio/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/kremalicious/portfolio/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/kremalicious/portfolio/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/kremalicious/portfolio/labels{/name}",
+ "releases_url": "https://api.github.com/repos/kremalicious/portfolio/releases{/id}",
+ "deployments_url": "https://api.github.com/repos/kremalicious/portfolio/deployments",
+ "created_at": "2018-05-13T23:53:31Z",
+ "updated_at": "2019-05-26T19:34:49Z",
+ "pushed_at": "2019-05-26T19:35:10Z",
+ "git_url": "git://github.com/kremalicious/portfolio.git",
+ "ssh_url": "git@github.com:kremalicious/portfolio.git",
+ "clone_url": "https://github.com/kremalicious/portfolio.git",
+ "svn_url": "https://github.com/kremalicious/portfolio",
+ "homepage": "https://matthiaskretschmann.com",
+ "size": 135135,
+ "stargazers_count": 55,
+ "watchers_count": 55,
+ "language": "JavaScript",
+ "has_issues": true,
+ "has_projects": false,
+ "has_downloads": true,
+ "has_wiki": false,
+ "has_pages": false,
+ "forks_count": 5,
+ "mirror_url": null,
+ "archived": false,
+ "disabled": false,
+ "open_issues_count": 1,
+ "license": {
+ "key": "mit",
+ "name": "MIT License",
+ "spdx_id": "MIT",
+ "url": "https://api.github.com/licenses/mit",
+ "node_id": "MDc6TGljZW5zZTEz"
+ },
+ "forks": 5,
+ "open_issues": 1,
+ "watchers": 55,
+ "default_branch": "master"
+ }
+]
diff --git a/package.json b/package.json
index 66d5054..1781454 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"babel-eslint": "^10.0.1",
"babel-jest": "^24.7.1",
"babel-preset-gatsby": "^0.1.11",
+ "chalk": "^2.4.2",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.2.0",
"eslint-loader": "^2.1.2",
diff --git a/src/components/atoms/LinkIcon.jsx b/src/components/atoms/LinkIcon.jsx
index 9acfad8..cf2c9bc 100644
--- a/src/components/atoms/LinkIcon.jsx
+++ b/src/components/atoms/LinkIcon.jsx
@@ -10,6 +10,7 @@ import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
import { ReactComponent as Email } from '../../images/email.svg'
import { ReactComponent as Blog } from '../../images/blog.svg'
import { ReactComponent as Twitter } from '../../images/twitter.svg'
+import { ReactComponent as Star } from '../../images/star.svg'
const LinkIcon = ({ title, type, ...props }) => {
let typeOrTitle = type ? type : title
@@ -39,6 +40,8 @@ const LinkIcon = ({ title, type, ...props }) => {
return
case 'Twitter':
return
+ case 'star':
+ return
default:
return null
}
diff --git a/src/components/molecules/Repository.jsx b/src/components/molecules/Repository.jsx
index 9982930..ecb6ebc 100644
--- a/src/components/molecules/Repository.jsx
+++ b/src/components/molecules/Repository.jsx
@@ -1,8 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { ReactComponent as Star } from '../../images/star.svg'
-import styles from './Repository.module.scss'
import LinkIcon from '../atoms/LinkIcon'
+import styles from './Repository.module.scss'
const Repository = ({ repo }) => {
const { name, description, html_url, homepage, stargazers_count } = repo
@@ -32,7 +31,7 @@ const Repository = ({ repo }) => {
- {stargazers_count}
+ {stargazers_count}
diff --git a/src/components/molecules/Repository.test.jsx b/src/components/molecules/Repository.test.jsx
new file mode 100644
index 0000000..ee256c2
--- /dev/null
+++ b/src/components/molecules/Repository.test.jsx
@@ -0,0 +1,40 @@
+import React from 'react'
+import { render } from 'react-testing-library'
+import Repository from './Repository'
+import repos from '../../../jest/__fixtures__/repos.json'
+
+describe('Repository', () => {
+ it('renders correctly', () => {
+ const { container } = render()
+ expect(container.firstChild).toBeInTheDocument()
+ })
+
+ it('uses html_url as main link for portfolio & blog', () => {
+ const repo1 = {
+ name: 'portfolio',
+ html_url: 'html_url'
+ }
+
+ const { container } = render()
+ expect(container.querySelector('h1 > a').getAttribute('href')).toBe(
+ repo1.html_url
+ )
+ })
+
+ it('renders homepage link when provided', () => {
+ const repo = {
+ name: 'Hello',
+ homepage: 'hello'
+ }
+
+ const { container } = render()
+ expect(container.querySelectorAll('p:last-child a').length).toBe(3)
+ })
+
+ it('renders no link without homepage', () => {
+ const repo = { name: 'Hello' }
+
+ const { container } = render()
+ expect(container.querySelectorAll('p:last-child a').length).toBe(2)
+ })
+})
diff --git a/src/components/organisms/Repositories.test.jsx b/src/components/organisms/Repositories.test.jsx
new file mode 100644
index 0000000..916cd35
--- /dev/null
+++ b/src/components/organisms/Repositories.test.jsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { render } from 'react-testing-library'
+import Repositories from './Repositories'
+import repos from '../../../jest/__fixtures__/repos.json'
+
+describe('Repositories', () => {
+ it('renders correctly', () => {
+ const { container } = render()
+ expect(container.firstChild).toBeInTheDocument()
+ })
+})
diff --git a/src/pages/__tests__/index.test.jsx b/src/pages/__tests__/index.test.jsx
index a08e405..1230658 100644
--- a/src/pages/__tests__/index.test.jsx
+++ b/src/pages/__tests__/index.test.jsx
@@ -16,8 +16,16 @@ describe('Home', () => {
...projectImageFiles
}
+ const pageContext = {
+ repos: [
+ {
+ name: 'Hello'
+ }
+ ]
+ }
+
it('renders correctly from data file values', () => {
- const { container } = render()
+ const { container } = render()
expect(container.firstChild).toBeInTheDocument()
})
})
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 598b016..a93844e 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -21,8 +21,7 @@ function getImageCount(images, slug) {
export default class Home extends PureComponent {
static propTypes = {
data: PropTypes.object.isRequired,
- pageContext: PropTypes.object.isRequired,
- location: PropTypes.object.isRequired
+ pageContext: PropTypes.object.isRequired
}
render() {
From e30bb68cb292ad0e806a95e4b3fbe47a66760282 Mon Sep 17 00:00:00 2001
From: Matthias Kretschmann
Date: Sun, 26 May 2019 22:55:28 +0200
Subject: [PATCH 4/4] documentation
---
README.md | 13 +++++++++++++
gatsby-node.js | 36 ++++++++++++++++++++++--------------
2 files changed, 35 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index 7e648eb..14361f1 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@
- [π Features](#-features)
- [β΅οΈ Lighthouse score](#οΈ-lighthouse-score)
- [π One data file to rule all pages](#-one-data-file-to-rule-all-pages)
+ - [π± GitHub repositories](#-github-repositories)
- [π
Theme switcher](#-theme-switcher)
- [π SEO component](#-seo-component)
- [π Client-side vCard creation](#-client-side-vcard-creation)
@@ -52,6 +53,18 @@ Gatsby automatically creates pages from each item in that file utilizing the [`P
- [`content/projects.yml`](content/projects.yml)
- [`src/templates/Project.jsx`](src/templates/Project.jsx)
+### π± GitHub repositories
+
+The open source section at the bottom of the front page shows selected GitHub repositories, sourced from GitHub.
+
+On build time, all my public repositories are fetched from GitHub, then filtered against the ones defined in `content/repos.yml`, sorted by the last push date, and provided via the page context of the front page.
+
+If you want to know how, have a look at the respective components:
+
+- [`gatsby-node.js`](gatsby-node.js)
+- [`content/repos.yml`](content/repos.yml)
+- [`src/components/molecules/Repository.jsx`](src/components/molecules/Repository.jsx)
+
### π
Theme switcher
Includes a theme switcher which allows user to toggle between a light and a dark theme. Switching between them also happens automatically based on user's local sunset and sunrise times. Uses Cloudflare's geo location HTTP header functionality.
diff --git a/gatsby-node.js b/gatsby-node.js
index a5dbc8e..fb13b3f 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -23,6 +23,9 @@ function truncate(n, useWordBoundary) {
)
}
+//
+// Get GitHub repos
+//
async function getGithubRepos(data) {
const allRepos = await axios.get(
`https://api.github.com/users/${data.user}/repos?per_page=100`
@@ -36,6 +39,9 @@ async function getGithubRepos(data) {
return repos
}
+//
+// Get GitHub repos once and store for later build stages
+//
let repos
exports.onPreBootstrap = async () => {
@@ -54,6 +60,22 @@ exports.onPreBootstrap = async () => {
}
}
+//
+// Add repos to front page's context
+//
+exports.onCreatePage = async ({ page, actions }) => {
+ const { createPage } = actions
+
+ if (page.path === '/')
+ createPage({
+ ...page,
+ context: {
+ ...page.context,
+ repos
+ }
+ })
+}
+
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
@@ -92,20 +114,6 @@ exports.onCreateNode = ({ node, actions }) => {
}
}
-exports.onCreatePage = async ({ page, actions }) => {
- const { createPage } = actions
-
- // Add repos to front page's context
- if (page.path === '/')
- createPage({
- ...page,
- context: {
- ...page.context,
- repos
- }
- })
-}
-
//
// Create project pages from projects.yml
//