mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 01:03:20 +01:00
documentation, switch to Travis
This commit is contained in:
parent
efbc1846d2
commit
2e45c29330
@ -1,29 +0,0 @@
|
|||||||
cache:
|
|
||||||
paths:
|
|
||||||
- node_modules/
|
|
||||||
- public/
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
build:
|
|
||||||
image: node:latest
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- npm i -g gatsby-cli
|
|
||||||
- export PATH="$PATH:/usr/local/bin/gatsby"
|
|
||||||
- npm i
|
|
||||||
- npm test
|
|
||||||
- npm run build
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
image: garland/aws-cli-docker:latest
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- aws s3 sync --delete --acl public-read ./public s3://matthiaskretschmann.com
|
|
||||||
only:
|
|
||||||
- master
|
|
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: node
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- public
|
||||||
|
|
||||||
|
install:
|
||||||
|
- pip install --user awscli
|
||||||
|
- export PATH=$PATH:$HOME/.local/bin
|
||||||
|
- npm i -g gatsby-cli
|
||||||
|
- export PATH="$PATH:/usr/local/bin/gatsby"
|
||||||
|
- npm i
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm test
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- npm run deploy
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2018 Matthias Kretschmann
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
141
README.md
141
README.md
@ -1,23 +1,152 @@
|
|||||||
# matthiaskretschmann.com
|
# [matthiaskretschmann.com](https://matthiaskretschmann.com)
|
||||||
> Portfolio of Matthias Kretschmann
|
|
||||||
|
|
||||||
[![pipeline status](https://git.berlin/m/portfolio/badges/master/pipeline.svg)](https://git.berlin/m/portfolio/commits/master)
|
[![image](src/images/twitter-card.png)](https://matthiaskretschmann.com)
|
||||||
|
|
||||||
|
> 👔 Portfolio thingy, built with [Gatsby](https://www.gatsbyjs.org).
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.com/kremalicious/portfolio.svg?branch=master)](https://travis-ci.com/kremalicious/portfolio)
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
* [Features](#features)
|
||||||
|
* [One data file to rule all pages](#one-data-file-to-rule-all-pages)
|
||||||
|
* [Theme switcher](#theme-switcher)
|
||||||
|
* [SEO component](#seo-component)
|
||||||
|
* [Client-side vCard creation](#client-side-vcard-creation)
|
||||||
|
* [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
|
||||||
|
* [Project images](#project-images)
|
||||||
|
* [Importing SVG assets](#importing-svg-assets)
|
||||||
|
* [Development](#development)
|
||||||
|
* [Linting](#linting)
|
||||||
|
* [Add a new project](#add-a-new-project)
|
||||||
|
* [Deployment](#deployment)
|
||||||
|
* [Licenses](#licenses)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
The whole [portfolio](https://matthiaskretschmann.com) is a React-based Single Page App built with [Gatsby](https://www.gatsbyjs.org).
|
||||||
|
|
||||||
|
Despite being built with React, and despite all the project images making it a very image-heavy portfolio, Gatsby makes the final site super fast. So fast, it's almost unreal and feels like magic. And makes it work without JavaScript by server-side rendering all routes. [And so much more](https://www.gatsbyjs.org/features/).
|
||||||
|
|
||||||
|
### One data file to rule all pages
|
||||||
|
|
||||||
|
All content is powered by one YAML file, [`data/projects.yml`](data/projects.yml) where all the portfolio's projects are defined. The project description itself is transformed from Markdown.
|
||||||
|
|
||||||
|
Gatsby automatically creates pages from each item in that file utilizing the [`src/templates/Project.jsx`](src/templates/Project.jsx) template.
|
||||||
|
|
||||||
|
### 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 time of day.
|
||||||
|
|
||||||
|
If you want to know how, have a look at the respective component under [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
If you want to know how, have a look at the respective component under [`src/components/atoms/SEO.jsx`](src/components/atoms/SEO.jsx)
|
||||||
|
|
||||||
|
### Client-side vCard creation
|
||||||
|
|
||||||
|
The _Add to addressbook_ link in the footer automatically creates a downloadable vCard file on the client-side, based on data defined in `data/meta.yml`.
|
||||||
|
|
||||||
|
If you want to know how, have a look at the respective component under [`src/components/atoms/Vcard.jsx`](src/components/atoms/Vcard.jsx)
|
||||||
|
|
||||||
|
### Matomo (formerly Piwik) analytics tracking
|
||||||
|
|
||||||
|
Site sends usage statistics to my own [Matomo](https://matomo.org) installation. To make this work in Gatsby, I created and open sourced a plugin, [gatsby-plugin-matomo](https://github.com/kremalicious/gatsby-plugin-matomo), which is in use on this site.
|
||||||
|
|
||||||
|
### Project images
|
||||||
|
|
||||||
|
All project images live under `src/images` and are automatically attached to each project based on the inclusion of the project's `slug` in their filenames.
|
||||||
|
|
||||||
|
_(TODO: automatically add the inital image to each project node, so it doesn't have to be defined in the projects.yml file.)_
|
||||||
|
|
||||||
|
All project images make use of the excellent [gatsby-image](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-image) plugin, working in tandem with [gatsby-plugin-sharp](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-sharp) and [gatsby-transformer-sharp](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-sharp).
|
||||||
|
|
||||||
|
All together, Gatsby automatically generates all required image sizes for delivering responsible, responsive images to visitors, including lazy loading of all images. Also includes the [intersection-observer polyfill](https://github.com/w3c/IntersectionObserver) to make lazy loading work properly in Safari.
|
||||||
|
|
||||||
|
All project images use one single component defined in [`src/components/atoms/ProjectImage.jsx`](src/components/atoms/ProjectImage.jsx). In there, one main GraphQL query fragment is defined, which then gets used throughout other GraphQL queries.
|
||||||
|
|
||||||
|
### Importing SVG assets
|
||||||
|
|
||||||
|
Makes use of `gatsby-plugin-svgr` so SVG assets can be imported like so:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ReactComponent as Logo } from '../images/logo.svg'
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git clone git@github.com:kremalicious/portfolio.git
|
||||||
|
cd portfolio/
|
||||||
|
|
||||||
npm i
|
npm i
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Get SVG images
|
### Linting
|
||||||
|
|
||||||
```js
|
ESlint, Prettier, and Stylelint are setup for all linting purposes:
|
||||||
import { ReactComponent as Logo } from '../../images/logo.svg'
|
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
To automatically format all code files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run format
|
||||||
|
npm run format:css
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a new project
|
||||||
|
|
||||||
|
To add a new project, run the following command. This adds a new item to the top of the `projects.yml` file, creating the title & slug from the argument:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run new -- "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then continue modifying the new entry in [`data/projects.yml`](data/projects.yml).
|
||||||
|
|
||||||
|
Finally, add as many images as needed with the file name format and put into `src/images/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
portfolio-SLUG-01.png
|
||||||
|
portfolio-SLUG-02.png
|
||||||
|
portfolio-SLUG-03.png
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
Automatic deployments are triggered upon successful tests & builds on Travis:
|
||||||
|
|
||||||
|
* push to `master` initiates a live deployment
|
||||||
|
* any Pull Request, and subsequent pushes to it, initiates a beta deployment
|
||||||
|
|
||||||
|
The deploy command simply calls the [`scripts/deploy.sh`](scripts/deploy.sh) script, syncing the contents of the `public/` folder to S3:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run deploy
|
npm run deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The deploymeng script can be used locally too, the branch checks are only happening for Travis builds, allowing to deploy any branch from local machine.
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
All images and projects are plain ol' copyright:
|
||||||
|
|
||||||
|
**© Copyright 2018 Matthias Kretschmann**
|
||||||
|
|
||||||
|
Most displayed projects are subject to the copyright of their respective owners.
|
||||||
|
|
||||||
|
All the rest, like all code and documentation, is under:
|
||||||
|
|
||||||
|
**The MIT License**
|
||||||
|
|
||||||
|
[Full MIT license text](LICENSE)
|
||||||
|
14
deploy.sh
14
deploy.sh
@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e;
|
|
||||||
|
|
||||||
aws s3 sync \
|
|
||||||
--delete \
|
|
||||||
--acl public-read \
|
|
||||||
./public s3://matthiaskretschmann.com
|
|
||||||
|
|
||||||
echo "---------------------------------------------"
|
|
||||||
echo " ✓ done deployment "
|
|
||||||
echo "---------------------------------------------"
|
|
||||||
|
|
||||||
exit;
|
|
@ -1,5 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
// Intersection Observer polyfill
|
||||||
|
// requires `npm install intersection-observer`
|
||||||
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
|
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
|
||||||
exports.modifyWebpackConfig = ({ config, stage }) => {
|
exports.modifyWebpackConfig = ({ config, stage }) => {
|
||||||
if (stage === 'build-html') {
|
if (stage === 'build-html') {
|
||||||
|
10
package.json
10
package.json
@ -11,7 +11,8 @@
|
|||||||
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
||||||
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
||||||
"test": "npm run lint",
|
"test": "npm run lint",
|
||||||
"deploy": "./deploy.sh"
|
"deploy": "./scripts/deploy.sh",
|
||||||
|
"new": "node ./scripts/new-project.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"camel-case": "^3.0.0",
|
"camel-case": "^3.0.0",
|
||||||
@ -50,12 +51,13 @@
|
|||||||
"eslint-plugin-graphql": "^2.1.1",
|
"eslint-plugin-graphql": "^2.1.1",
|
||||||
"eslint-plugin-prettier": "^2.6.0",
|
"eslint-plugin-prettier": "^2.6.0",
|
||||||
"eslint-plugin-react": "^7.8.2",
|
"eslint-plugin-react": "^7.8.2",
|
||||||
|
"ora": "^2.1.0",
|
||||||
|
"prepend": "^1.0.2",
|
||||||
"prettier": "^1.12.1",
|
"prettier": "^1.12.1",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
|
"slugify": "^1.3.0",
|
||||||
"stylelint": "^9.2.0",
|
"stylelint": "^9.2.0",
|
||||||
"stylelint-config-standard": "^18.2.0"
|
"stylelint-config-standard": "^18.2.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": ["defaults"]
|
||||||
"defaults"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
43
scripts/deploy.sh
Executable file
43
scripts/deploy.sh
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# required environment variables:
|
||||||
|
# AWS_ACCESS_KEY_ID
|
||||||
|
# AWS_SECRET_ACCESS_KEY
|
||||||
|
# AWS_DEFAULT_REGION
|
||||||
|
# AWS_S3_BUCKET
|
||||||
|
#
|
||||||
|
set -e;
|
||||||
|
|
||||||
|
##
|
||||||
|
## check for pull request against master
|
||||||
|
##
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then
|
||||||
|
|
||||||
|
aws s3 sync \
|
||||||
|
--delete \
|
||||||
|
--acl public-read \
|
||||||
|
./public s3://"$AWS_S3_BUCKET_BETA"
|
||||||
|
|
||||||
|
##
|
||||||
|
## check for master push which is no pull request
|
||||||
|
##
|
||||||
|
elif [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] || [ "$TRAVIS" != true ]; then
|
||||||
|
|
||||||
|
aws s3 sync \
|
||||||
|
--delete \
|
||||||
|
--acl public-read \
|
||||||
|
./public s3://"$AWS_S3_BUCKET"
|
||||||
|
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " ✓ done deployment "
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
|
||||||
|
exit;
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " nothing to deploy "
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
|
||||||
|
fi
|
20
scripts/new-project.js
Normal file
20
scripts/new-project.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const prepend = require('prepend')
|
||||||
|
const slugify = require('slugify')
|
||||||
|
const ora = require('ora')
|
||||||
|
|
||||||
|
const templatePath = path.join(__dirname, 'new-project.yml')
|
||||||
|
const template = fs.readFileSync(templatePath).toString()
|
||||||
|
|
||||||
|
const title = process.argv[2]
|
||||||
|
const titleSlug = slugify(title)
|
||||||
|
const spinner = ora(`Adding '${title}'.`).start()
|
||||||
|
|
||||||
|
const newContents = template.replace('TITLE', title).replace('SLUG', titleSlug)
|
||||||
|
const projects = path.join(__dirname, '..', 'data', 'projects.yml')
|
||||||
|
|
||||||
|
prepend(projects, newContents, error => {
|
||||||
|
if (error) throw error
|
||||||
|
spinner.succeed(`Added '${title}' to top of projects.yml file.`)
|
||||||
|
})
|
12
scripts/new-project.yml
Normal file
12
scripts/new-project.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-
|
||||||
|
|
||||||
|
title: TITLE
|
||||||
|
slug: "/SLUG/"
|
||||||
|
img: "../src/images/portfolio-SLUG-01.png"
|
||||||
|
description: >
|
||||||
|
Markdown can be used here.
|
||||||
|
links:
|
||||||
|
- title: Link
|
||||||
|
url: https://url
|
||||||
|
techstack:
|
||||||
|
- Tech used
|
@ -8,11 +8,15 @@ class Vcard extends PureComponent {
|
|||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create base64 string from avatar image
|
||||||
|
// without the need to read image file from file system
|
||||||
toDataURL(src, callback, outputFormat) {
|
toDataURL(src, callback, outputFormat) {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.crossOrigin = 'Anonymous'
|
img.crossOrigin = 'Anonymous'
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = function() {
|
||||||
|
// yeah, we're gonna create a fake canvas to render the image
|
||||||
|
// and then create a base64 string from the rendered result
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
let dataURL
|
let dataURL
|
||||||
@ -37,11 +41,13 @@ class Vcard extends PureComponent {
|
|||||||
const contact = new vCard()
|
const contact = new vCard()
|
||||||
const photoSrc = meta.avatar.childImageSharp.original.src
|
const photoSrc = meta.avatar.childImageSharp.original.src
|
||||||
|
|
||||||
|
// first, convert the avatar to base64,
|
||||||
|
// then construct all vCard elements
|
||||||
this.toDataURL(
|
this.toDataURL(
|
||||||
photoSrc,
|
photoSrc,
|
||||||
dataUrl => {
|
dataUrl => {
|
||||||
// stripping this data out of base64 string
|
// stripping this data out of base64 string is required
|
||||||
// is required for vcard for whatever reason
|
// for vcard to actually display the image for whatever reason
|
||||||
const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
|
const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
|
||||||
contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
|
contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
|
||||||
contact.set('fn', meta.title)
|
contact.set('fn', meta.title)
|
||||||
@ -61,6 +67,8 @@ class Vcard extends PureComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Construct the download from a blob of the just constructed vCard,
|
||||||
|
// and save it to user's file system
|
||||||
downloadVcard(vcard) {
|
downloadVcard(vcard) {
|
||||||
const name = this.props.meta.addressbook.split('/').join('')
|
const name = this.props.meta.addressbook.split('/').join('')
|
||||||
const blob = new Blob([vcard], { type: 'text/x-vcard' })
|
const blob = new Blob([vcard], { type: 'text/x-vcard' })
|
||||||
@ -75,6 +83,8 @@ class Vcard extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
// href is kinda fake, only there for usability
|
||||||
|
// so user knows what to expect when hovering the link before clicking
|
||||||
href={this.props.meta.addressbook}
|
href={this.props.meta.addressbook}
|
||||||
onClick={this.handleAddressbookClick}
|
onClick={this.handleAddressbookClick}
|
||||||
>
|
>
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.8 KiB |
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
/* SITE */
|
/* SITE */
|
||||||
Typography : Brandon Grotesque, FF Tisa Sans Web Pro
|
Typography : Brandon Grotesque, FF Tisa Sans Web Pro
|
||||||
Software : Gatsby.js, React, VS Code, macOS
|
Software : Gatsby.js, React, VS Code, Sketch, macOS
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
Loading…
Reference in New Issue
Block a user