Compare commits

..

10 Commits
1.3 ... master

Author SHA1 Message Date
Stephan Dörfler 957e2f95be fixed typos
continuous-integration/drone/push Build is passing Details
2020-12-07 07:24:30 +01:00
Stephan Dörfler 3a3dee879f switched drone docker authentication to secrets
continuous-integration/drone/push Build is passing Details
2020-09-23 12:50:02 +02:00
Stephan Dörfler a85e429f4d added comments and post about comments
continuous-integration/drone/push Build is passing Details
2020-09-05 19:15:40 +02:00
Stephan Dörfler 5689ce6332 added eslint 2020-06-10 15:41:43 +02:00
Stephan Dörfler ccdb038817 minor adjustments to dockerfile and version increment
continuous-integration/drone/push Build is passing Details
2020-06-03 12:37:20 +02:00
Stephan Dörfler 60f1ea9339 simplified docker build
continuous-integration/drone/push Build is failing Details
2020-06-03 09:08:46 +02:00
Stephan Dörfler a3ba77aa38 new logo
continuous-integration/drone/push Build is failing Details
2020-06-03 08:05:17 +02:00
Stephan Dörfler d42ea61ec4 yarn upgrade dependencies 2020-06-03 08:05:09 +02:00
Stephan Dörfler 61ca707300 updated build to intermediate docker build container 2020-06-03 08:03:09 +02:00
Stephan Dörfler 123d7a9fe4 updated dependencies and tooling
continuous-integration/drone/push Build is failing Details
2020-06-02 19:22:41 +02:00
24 changed files with 16640 additions and 18275 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
.cache/
node_modules/
public/

View File

@ -3,19 +3,15 @@ type: docker
name: Blog build and release name: Blog build and release
steps: steps:
- name: build
image: node:alpine
commands:
- npm install
- npx gatsby build
- name: docker - name: docker
image: plugins/docker image: plugins/docker
settings: settings:
username: stephan username:
password: eRFJ1R7UNo5zv1FFvLzv from_secret: docker_username
password:
from_secret: docker_password
repo: registry.while-false.de/blog repo: registry.while-false.de/blog
registry: registry.while-false.de registry: registry.while-false.de
tags: tags:
- 'latest' - 'latest'
- '1.3.0' - '1.4.0'

27
.eslintrc.json Normal file
View File

@ -0,0 +1,27 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"plugin:react/recommended",
"airbnb"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"comma-dangle": ["error", "never"]
}
}

View File

@ -1 +1,10 @@
FROM gatsbyjs/gatsby:onbuild FROM node:14.3 as build
WORKDIR /app
COPY . ./
RUN yarn install -s --no-progress --prod
RUN yarn global add gatsby-cli
RUN npx gatsby build
FROM gatsbyjs/gatsby
COPY --from=build /app/public /pub

BIN
content/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -8,11 +8,11 @@ description: Initial commit - Introduction
So I finally got around to make a blog. Now what? So I finally got around to make a blog. Now what?
Honestly, I don't even know myself. For the last few years as a developer I consumed whatever the great, wide internet presented me with. I am a huge fan of just going out there and do what you think is good. So now I try to do just that and begin to share what goes through my mind as a developer, DevOps-guy, team-lead and nerd. Maybe someone else can make use of some of it, maybe this is just a glorified public diary. I won't be posting regularly, maybe this project might also just die in a few days, weeks, month or any other point in time. Let's just see. Honestly, I don't even know myself. For the last few years as a developer I consumed whatever the great, wide internet presented me with. I am a huge fan of just going out there and doing what I think is good. So now I try to do just that and begin to share what goes through my mind as a developer, DevOps-guy, team-lead and nerd. Maybe someone else can make use of some of it, maybe this is just a glorified public diary. I won't be posting regularly, maybe this project might also just die in a few days, weeks, month or any other point in time. Let's just see.
# Who am I? # Who am I?
I am Stephan, 28 (at the time of writing), and I try to build cool stuff with computers. Currently, I work in a company called [DEVDEER](https://devdeer.com/). We are a few enthusiasts (9, soon to be 10) helping businesses with consuting, developing, operating, migrating and integrating all around Microsoft's tools and technologies. When I'm not at work, I do stuff like this (tinkering with code), go out hiking, photograph or start cool DIY-projects I mostly never get around to finish. I am Stephan, 28 (at the time of writing), and I try to build cool stuff with computers. Currently, I work in a company called [DEVDEER](https://devdeer.com/). We are a few enthusiasts (9, soon to be 10) helping businesses with consulting, developing, operating, migrating and integrating all around Microsoft's tools and technologies. When I'm not at work, I do stuff like this (tinkering with code), go out hiking, photograph or start cool DIY-projects I mostly never get around to finish.
# Contents # Contents

View File

@ -10,7 +10,7 @@ It took some time, but I figured as I only got around to deploy the blog just no
First, some thoughts for my requirements: First, some thoughts for my requirements:
* I'm a developer. I experienced too much pain with wordpress, typo3 and similar CMSs in the past, so whatever I used for a blog had to be closer to what I feel confortable using. * I'm a developer. I experienced too much pain with wordpress, typo3 and similar CMSs in the past, so whatever I used for a blog had to be closer to what I feel confortable using.
* I like to write [`markdown`](https://daringfireball.net/projects/markdown/). It's a nice, human readable syntax that can be easily converted even nicer `HTML` content. * I like to write [`markdown`](https://daringfireball.net/projects/markdown/). It's a nice, human readable syntax that can be easily converted to even nicer `HTML` content.
* For WebApps, I like to use [`React`](https://reactjs.org/). It's a well maintained UI-framework with clean code structure and great extensibility. * For WebApps, I like to use [`React`](https://reactjs.org/). It's a well maintained UI-framework with clean code structure and great extensibility.
* I am kind of cheap. I run a small virtual server with limited resources. To still have reasonable performance and a clean environment I run nothing but [`Docker`](https://www.docker.com/) on it. * I am kind of cheap. I run a small virtual server with limited resources. To still have reasonable performance and a clean environment I run nothing but [`Docker`](https://www.docker.com/) on it.
@ -19,16 +19,18 @@ Some time ago I listened to [an episode of the podcast .Net rocks](https://dotne
# How this is built # How this is built
Gatsby has a [template for blogs](https://github.com/gatsbyjs/gatsby-starter-blog). Using Gatsby has a [template for blogs](https://github.com/gatsbyjs/gatsby-starter-blog). Using
```bash
npx gatsby new blog https://github.com/gatsbyjs/gatsby-starter-blog npx gatsby new blog https://github.com/gatsbyjs/gatsby-starter-blog
```
I let gatsby create an instance of the blog template for me. From this template I got going with `npx gatsby develop` and started off with deleting a lot of files I didn't need. I also did some changes to the style. I have absolutely no background in anything even remotely related to making things look good, so I just went with what I had in my mind at that very moment (any feedback and suggestions are very welcome). The `gatsby new` command did also initialize a [`git`]() repository so I just had to commit my new changes. For better availability I then pushed the repository to [my self-hosted git server](https://code.while-false.de/stephan/blog). I let gatsby create an instance of the blog template for me. From this template I got going with `npx gatsby develop` and started off with deleting a lot of files I didn't need. I also did some changes to the style. I have absolutely no background in anything even remotely related to making things look good, so I just went with what I had in my mind at that very moment (any feedback and suggestions are very welcome). The `gatsby new` command did also initialize a [`git`]() repository so I just had to commit my new changes. For better availability I then pushed the repository to [my self-hosted git server](https://code.while-false.de/stephan/blog).
# How this is run # How this is run
I mentioned `docker` before. [It seems to be officially supported](https://github.com/gatsbyjs/gatsby-docker). I just went with the documentation and tried to get it to run. First, I created a `Dockerfile` in the project with just one line of content: I mentioned `docker` before. [It seems to be officially supported](https://github.com/gatsbyjs/gatsby-docker). I just went with the documentation and tried to get it to run. First, I created a `Dockerfile` in the project with just one line of content:
```docker
FROM gatsbyjs/gatsby:onbuild FROM gatsbyjs/gatsby:onbuild
```
Then, I could build first the gatsby project with `npx gatsby build` and use the optimized output from that to build a docker image with `docker build -t while-false/blog .` from the context of my project root. Next, I started a container from the newly created image with `docker run -d --name blog -p 8080:80 while-false/blog`. It worked on my laptop for `localhost:8080`, I saw the blog I just built. Nice! Then, I could build first the gatsby project with `npx gatsby build` and use the optimized output from that to build a docker image with `docker build -t while-false/blog .` from the context of my project root. Next, I started a container from the newly created image with `docker run -d --name blog -p 8080:80 while-false/blog`. It worked on my laptop for `localhost:8080`, I saw the blog I just built. Nice!

View File

@ -26,7 +26,7 @@ All steps on the server are handled from the terminal and typing the same comman
Whenever you do the same thing often, automation comes to mind. So the steps on the server are to be automated. I have some ideas for that. Whenever you do the same thing often, automation comes to mind. So the steps on the server are to be automated. I have some ideas for that.
The relevant event I want to react on is the change in the blog. I consider a change as relevant, when a new commit happens on the `master` branch of the `git` repository of the blog. Luckily, `git` has a builtin concept for reacting on events on the server, called "server-side hooks". In my case the `git` server is an instance of `gitea`, so I looked up server side hooks in [the gitea documentation](https://docs.gitea.io/en-us/webhooks/). I quickly found the hook I needed: The relevant event I want to react on is the change in the blog. I consider a change as relevant, when a new commit happens on the `master` branch of the `git` repository of the blog. Luckily, `git` has a builtin concept for reacting on events on the server, called "server-side hooks". In my case the `git` server is an instance of `gitea`, so I looked up server side hooks in [the gitea documentation](https://docs.gitea.io/en-us/webhooks/). I quickly found the hook I needed:
So I can make `git` notify some other component of each change. Now I need something to listen to these notifications and then execute the update-steps automatically. So I can make `git` notify some other component of each change. Now I need something to listen to these notifications and then execute the update-steps automatically.
@ -41,7 +41,7 @@ My first intention was to build that component myself. I know all the commands t
* Push the docker image to my private docker repository. Another authentication required * Push the docker image to my private docker repository. Another authentication required
* On the host exchange the currently running docker container with the newly built one * On the host exchange the currently running docker container with the newly built one
Around that time of planning I decided this isn't the way to go. At my job I rely heavily on [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) which conveniently covers all these tasks and requirements. But for my private free-time-projects I imposed the restriction on myself to run everything on my own server(s). But until now I only looked at the furthest cases on the automation spectrum: doing everything myself and have everything done by Microsoft in the cloud. I decided that the truth probably lies somewhere inbetween (as so often in life). I then looked at self-hosted CI/CD ("Continous Integration"/"Continous Delivery") systems. Around that time of planning I decided this isn't the way to go. At my job I rely heavily on [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) which conveniently covers all these tasks and requirements. But for my private free-time-projects I imposed the restriction on myself to run everything on my own server(s). But until now I only looked at the furthest cases on the automation spectrum: doing everything myself and have everything done by Microsoft in the cloud. I decided that the truth probably lies somewhere inbetween (as it does so often in life). I then looked at self-hosted CI/CD ("Continous Integration"/"Continous Delivery") systems.
[Drone](https://drone.io) caught my eye, as it has full docker support, is open source and can have multiple, distributed workers. Perfect, I finally get to use the "sandbox" VPS I rent which just accumulates virtual dust. After reading the documentation, the setup was fairly easy. [Drone](https://drone.io) caught my eye, as it has full docker support, is open source and can have multiple, distributed workers. Perfect, I finally get to use the "sandbox" VPS I rent which just accumulates virtual dust. After reading the documentation, the setup was fairly easy.
@ -74,7 +74,7 @@ steps:
commands: commands:
- npm install - npm install
- npx gatsby build - npx gatsby build
- name: docker - name: docker
image: plugins/docker image: plugins/docker
settings: settings:
@ -91,7 +91,7 @@ For now I only require two steps:
1. install node.js dependencies and build the gatsby project 1. install node.js dependencies and build the gatsby project
2. build the new docker image and push it to the registry 2. build the new docker image and push it to the registry
An additional benefit of the drone build is this beatiful badge, every project has nowadays, conveniently prepared as markdown: An additional benefit of the drone build is this beautiful badge, every project has nowadays, conveniently prepared as markdown:
[![Build Status](https://drone.while-false.de/api/badges/stephan/blog/status.svg)](https://drone.while-false.de/stephan/blog) [![Build Status](https://drone.while-false.de/api/badges/stephan/blog/status.svg)](https://drone.while-false.de/stephan/blog)

View File

@ -0,0 +1,86 @@
---
title: Comments
date: "2020-09-05T17:56:22.339Z"
description: I would love to hear from you!
---
## Where do comments live?
I built this blog with [`GatsbyJS`](https://www.gatsbyjs.org/), which is optimized for static content. It works great for everything that exists when I build the code and create another docker image. But now I want to add comments from my readers. Which of course aren't static and don't exist on compile-time. So it seems GatsbyJS would not be the right tool for the job.
Luckily, GatsbyJS is expandable. There even is a [guide on how to integrate comments](https://www.gatsbyjs.com/docs/adding-comments/) in a blog on the gatsby website itself. But it assumes you just go and use some external service like disqus (although it does mention several alternatives). I didn't want to outsource that, the whole point of this blog is to run things myself and learn from that.
What I found was [Commento](https://commento.io/). It focusses around privacy and can be [self-hosted](https://docs.commento.io/installation/self-hosting/) instead of using an external service. It even has docker support. I quickly set up my very own instance on my server:
```bash
docker run -d --name blog-commento \
-e "COMMENTO_ORIGIN=https://comments.while-false.de" \
-e "COMMENTO_POSTGRES=postgres://commentoDbUser:SuperSecretPassword@db_postgres/commento?sslmode=disable" \
registry.gitlab.com/commento/commento
```
*(In reality, I use a few more parameters and steps required for my specific hosting setup, which I will explain in a future post)*
So my blog itself can still be static and throught the magic of gatsby's automatic optimization blazing fast, while the dynamic comments are handled by an external server, which I can fully control as I host it myself.
## Include the comments in the blog
Next, I followed the [documentation for commento](https://docs.commento.io/installation/self-hosting/register-your-website/) and registered the blog on my commento instance. Easy.
Now to the tricky part. The static Gatsby.js website must embed the dynamic comments from the commento server. Again, I am lucky and found that [someone already did exactly that](https://itnext.io/adding-commento-to-react-apps-like-gatsby-871824fb57ae). With some small tweaks, this is what I use:
```js
import React, { useEffect } from 'react';
/**
* Helper to add scripts to the page.
* @param {string} src The source path for the script to insert.
* @param {string} id The unique identifier for the script element to insert.
* @param {HTMLElement} parentElement The DOM element to insert the script into.
*/
const insertScript = (src, id, parentElement) => {
const script = window.document.createElement('script');
script.async = true;
script.src = src;
script.id = id;
parentElement.appendChild(script);
return script;
};
/**
* Helper to remove scripts from the page.
* @param {string} id The unique identifier for the script element to remove.
* @param {HTMLElement} parentElement The DOM element to remove the script from
*/
const removeScript = (id, parentElement) => {
const script = window.document.getElementById(id);
if (script) {
parentElement.removeChild(script);
}
};
const Commento = ({ id }) => {
useEffect(() => {
// If there's no window there's nothing to do
if (!window) {
return;
}
const { document } = window;
// In case the #commento container exists, the commento script can be added
if (document.getElementById('commento')) {
insertScript('https://comments.while-false.de/js/commento.js', 'commento-script', document.body);
}
// Cleanup; remove the script from the page
return () => removeScript('commento-script', document.body);
}, [id]);
return <div id="commento" />;
};
export default Commento;
```
This component itself is evaluated at runtime (it uses `useEffect`, which gatsby understands as non-static). It dynamically loads the scripts required by commento. The commento component is then included in my default blog-post template component by adding the line `<Commento id={this.props.slug} />`. The slug is the part of the URL after the hostname, i.e. `004-comments/` for this page. Thus, commento differentiates which comment belongs to which page.
Now, users can register and directly comment or comment anonymously with a required moderator review (which is me). Also, markdown, up- and downvoting, sorting, sticky, replies and moderation tools are included. Give it a try, I'd love to hear from you!
In the next few days I will tinker around with commento's settings for moderation notification emails, custom styling and comment analytics (number of views and number of comments).

View File

@ -1,6 +1,6 @@
// custom typefaces // custom typefaces
import "typeface-montserrat" import 'typeface-montserrat';
import "typeface-merriweather" import 'typeface-merriweather';
import "./src/styles/global.css"; import './src/styles/global.css';
import "prismjs/themes/prism-solarizedlight.css"; import 'prismjs/themes/prism-solarizedlight.css';

View File

@ -1,76 +1,129 @@
module.exports = { module.exports = {
siteMetadata: { siteMetadata: {
title: `While False Blog`, title: 'While False Blog',
author: `Stephan Dörfler`, author: 'Stephan Dörfler',
description: `Self-built developer blog based on gatsby.`, description: 'Self-built developer blog based on gatsby.',
siteUrl: `https://blog.while-false.de`, siteUrl: 'https://blog.while-false.de',
type: `website`, type: 'website',
}, },
plugins: [ plugins: [
{ {
resolve: `gatsby-source-filesystem`, resolve: 'gatsby-source-filesystem',
options: { options: {
path: `${__dirname}/content/blog`, path: `${__dirname}/content/blog`,
name: `blog`, name: 'blog',
}, },
}, },
{ {
resolve: `gatsby-source-filesystem`, resolve: 'gatsby-source-filesystem',
options: { options: {
path: `${__dirname}/content/assets`, path: `${__dirname}/content/assets`,
name: `assets`, name: 'assets',
}, },
}, },
{ {
resolve: `gatsby-transformer-remark`, resolve: 'gatsby-transformer-remark',
options: { options: {
plugins: [ plugins: [
{ {
resolve: `gatsby-remark-images`, resolve: 'gatsby-remark-images',
options: { options: {
maxWidth: 590, maxWidth: 590,
}, },
}, },
{ {
resolve: `gatsby-remark-responsive-iframe`, resolve: 'gatsby-remark-responsive-iframe',
options: { options: {
wrapperStyle: `margin-bottom: 1.0725rem`, wrapperStyle: 'margin-bottom: 1.0725rem',
}, },
}, },
`gatsby-remark-prismjs`, 'gatsby-remark-prismjs',
`gatsby-remark-copy-linked-files`, 'gatsby-remark-copy-linked-files',
`gatsby-remark-smartypants`, 'gatsby-remark-smartypants',
], ],
}, },
}, },
`gatsby-transformer-sharp`, 'gatsby-transformer-sharp',
`gatsby-plugin-sharp`, 'gatsby-plugin-sharp',
{ {
resolve: `gatsby-plugin-google-analytics`, resolve: 'gatsby-plugin-matomo',
options: { options: {
//trackingId: `ADD YOUR TRACKING ID HERE`, siteId: '1',
matomoUrl: 'https://matomo.while-false.de',
siteUrl: 'https://blog.while-false.de',
matomoPhpScript: 'matomo.php',
matomoJsScript: 'matomo.js',
}, },
}, },
`gatsby-plugin-feed`,
{ {
resolve: `gatsby-plugin-manifest`, resolve: 'gatsby-plugin-feed',
options: { options: {
name: `While False Blog`, query: `
short_name: `while-false`, {
start_url: `/`, site {
background_color: `#f9ebe0`, siteMetadata {
theme_color: `#3b7080`, title
display: `minimal-ui`, description
icon: `content/assets/gatsby-icon.png`, siteUrl
site_url: siteUrl
}
}
}
`,
feeds: [
{
serialize: ({ query: { site, allMarkdownRemark } }) => allMarkdownRemark.edges.map((edge) => ({
...edge.node.frontmatter,
description: edge.node.excerpt,
date: edge.node.frontmatter.date,
url: site.siteMetadata.siteUrl + edge.node.fields.slug,
guid: site.siteMetadata.siteUrl + edge.node.fields.slug,
custom_elements: [{ 'content:encoded': edge.node.html }],
})),
query: `
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] },
) {
edges {
node {
excerpt
html
fields { slug }
frontmatter {
title
date
}
}
}
}
}
`,
output: '/rss.xml',
title: 'while-false blog RSS Feed',
},
],
}, },
}, },
`gatsby-plugin-offline`,
`gatsby-plugin-react-helmet`,
{ {
resolve: `gatsby-plugin-typography`, resolve: 'gatsby-plugin-manifest',
options: { options: {
pathToConfigModule: `src/utils/typography`, name: 'While False Blog',
short_name: 'while-false',
start_url: '/',
background_color: '#f9ebe0',
theme_color: '#3b7080',
display: 'minimal-ui',
icon: 'content/assets/logo.png',
},
},
'gatsby-plugin-offline',
'gatsby-plugin-react-helmet',
{
resolve: 'gatsby-plugin-typography',
options: {
pathToConfigModule: 'src/utils/typography',
}, },
}, },
], ],
} };

View File

@ -1,10 +1,10 @@
const path = require(`path`) const path = require('path');
const { createFilePath } = require(`gatsby-source-filesystem`) const { createFilePath } = require('gatsby-source-filesystem');
exports.createPages = async ({ graphql, actions }) => { exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions const { createPage } = actions;
const blogPost = path.resolve(`./src/templates/blog-post.js`) const blogPost = path.resolve('./src/templates/blog-post.js');
const result = await graphql( const result = await graphql(
` `
{ {
@ -25,18 +25,18 @@ exports.createPages = async ({ graphql, actions }) => {
} }
} }
` `
) );
if (result.errors) { if (result.errors) {
throw result.errors throw result.errors;
} }
// Create blog posts pages. // Create blog posts pages.
const posts = result.data.allMarkdownRemark.edges const posts = result.data.allMarkdownRemark.edges;
posts.forEach((post, index) => { posts.forEach((post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node const previous = index === posts.length - 1 ? null : posts[index + 1].node;
const next = index === 0 ? null : posts[index - 1].node const next = index === 0 ? null : posts[index - 1].node;
createPage({ createPage({
path: post.node.fields.slug, path: post.node.fields.slug,
@ -46,19 +46,18 @@ exports.createPages = async ({ graphql, actions }) => {
previous, previous,
next, next,
}, },
}) });
}) });
} };
exports.onCreateNode = ({ node, actions, getNode }) => { exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions const { createNodeField } = actions;
if (node.internal.type === 'MarkdownRemark') {
if (node.internal.type === `MarkdownRemark`) { const value = createFilePath({ node, getNode });
const value = createFilePath({ node, getNode })
createNodeField({ createNodeField({
name: `slug`, name: 'slug',
node, node,
value, value,
}) });
} }
} };

18040
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,39 +2,47 @@
"name": "while-false-blog", "name": "while-false-blog",
"private": true, "private": true,
"description": "A simple blog powered by Gatsby and Markdown", "description": "A simple blog powered by Gatsby and Markdown",
"version": "0.1.0", "version": "1.0.0",
"author": "Stephan Dörfler <st.doerfler@outlook.com>", "author": "Stephan Dörfler <stephan@while-false.de>",
"dependencies": { "dependencies": {
"gatsby": "^2.18.8", "gatsby": "^2.23.3",
"gatsby-image": "^2.2.34", "gatsby-cli": "^2.12.45",
"gatsby-plugin-feed": "^2.3.23", "gatsby-image": "^2.4.6",
"gatsby-plugin-google-analytics": "^2.1.29", "gatsby-plugin-feed": "^2.5.4",
"gatsby-plugin-manifest": "^2.2.31", "gatsby-plugin-manifest": "^2.4.10",
"gatsby-plugin-offline": "^3.0.27", "gatsby-plugin-matomo": "^0.8.3",
"gatsby-plugin-react-helmet": "^3.1.16", "gatsby-plugin-offline": "^3.2.8",
"gatsby-plugin-sharp": "^2.3.5", "gatsby-plugin-react-helmet": "^3.3.3",
"gatsby-plugin-typography": "^2.3.18", "gatsby-plugin-sharp": "^2.6.10",
"gatsby-remark-copy-linked-files": "^2.1.31", "gatsby-plugin-typography": "^2.5.3",
"gatsby-remark-images": "^3.1.35", "gatsby-remark-copy-linked-files": "^2.3.4",
"gatsby-remark-prismjs": "^3.3.25", "gatsby-remark-images": "^3.3.9",
"gatsby-remark-responsive-iframe": "^2.2.28", "gatsby-remark-prismjs": "^3.5.3",
"gatsby-remark-smartypants": "^2.1.17", "gatsby-remark-responsive-iframe": "^2.4.4",
"gatsby-source-filesystem": "^2.1.40", "gatsby-remark-smartypants": "^2.3.3",
"gatsby-transformer-remark": "^2.6.39", "gatsby-source-filesystem": "^2.3.10",
"gatsby-transformer-sharp": "^2.3.7", "gatsby-transformer-remark": "^2.8.14",
"prismjs": "^1.17.1", "gatsby-transformer-sharp": "^2.5.4",
"react": "^16.12.0", "global": "^4.4.0",
"react-dom": "^16.12.0", "prismjs": "^1.20.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-helmet": "^5.2.1", "react-helmet": "^5.2.1",
"react-typography": "^0.16.19", "react-typography": "^0.16.19",
"typeface-merriweather": "0.0.72", "typeface-merriweather": "^0.0.72",
"typeface-montserrat": "0.0.75", "typeface-montserrat": "^0.0.75",
"typography": "^0.16.19", "typography": "^0.16.19",
"typography-theme-moraga": "^0.16.19", "typography-theme-moraga": "^0.16.19",
"typography-theme-wordpress-2016": "^0.16.19" "typography-theme-wordpress-2016": "^0.16.19"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^1.19.1" "eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.20.0",
"eslint-plugin-react-hooks": "^2.5.1",
"prettier": "^2.0.5"
}, },
"keywords": [ "keywords": [
"gatsby" "gatsby"

View File

@ -5,11 +5,11 @@
* See: https://www.gatsbyjs.org/docs/use-static-query/ * See: https://www.gatsbyjs.org/docs/use-static-query/
*/ */
import React from "react" import React from 'react';
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from 'gatsby';
import Image from "gatsby-image" import Image from 'gatsby-image';
import { rhythm } from "../utils/typography" import { rhythm } from '../utils/typography';
const Bio = () => { const Bio = () => {
const data = useStaticQuery(graphql` const data = useStaticQuery(graphql`
@ -27,13 +27,13 @@ const Bio = () => {
} }
} }
} }
`) `);
const { author } = data.site.siteMetadata const { author } = data.site.siteMetadata;
return ( return (
<div <div
style={{ style={{
display: `flex`, display: 'flex',
marginBottom: rhythm(2.5), marginBottom: rhythm(2.5),
}} }}
> >
@ -44,18 +44,22 @@ const Bio = () => {
marginRight: rhythm(1 / 2), marginRight: rhythm(1 / 2),
marginBottom: 0, marginBottom: 0,
minWidth: 50, minWidth: 50,
borderRadius: `100%`, borderRadius: '100%',
}} }}
imgStyle={{ imgStyle={{
borderRadius: `50%`, borderRadius: '50%',
}} }}
/> />
<p> <p>
Written by <strong>{author}</strong> who lives and works in Germany trying to build useful things. Written by
{` `} {' '}
<strong>{author}</strong>
{' '}
who lives and works in Germany trying to build useful things.
{' '}
</p> </p>
</div> </div>
) );
} };
export default Bio export default Bio;

View File

@ -0,0 +1,48 @@
import React, { useEffect } from 'react';
/**
* Helper to add scripts to the page.
* @param {string} src The source path for the script to insert.
* @param {string} id The unique identifier for the script element to insert.
* @param {HTMLElement} parentElement The DOM element to insert the script into.
*/
const insertScript = (src, id, parentElement) => {
const script = window.document.createElement('script');
script.async = true;
script.src = src;
script.id = id;
parentElement.appendChild(script);
return script;
};
/**
* Helper to remove scripts from the page.
* @param {string} id The unique identifier for the script element to remove.
* @param {HTMLElement} parentElement The DOM element to remove the script from
*/
const removeScript = (id, parentElement) => {
const script = window.document.getElementById(id);
if (script) {
parentElement.removeChild(script);
}
};
const Commento = ({ id }) => {
useEffect(() => {
// If there's no window there's nothing to do for us
if (!window) {
return;
}
const { document } = window;
// In case our #commento container exists we can add our commento script
if (document.getElementById('commento')) {
insertScript('https://comments.while-false.de/js/commento.js', 'commento-script', document.body);
}
// Cleanup; remove the script from the page
return () => removeScript('commento-script', document.body);
}, [id]);
return <div id="commento" />;
};
export default Commento;

View File

@ -1,13 +1,13 @@
import React from "react" import React from 'react';
import { Link } from "gatsby" import { Link } from 'gatsby';
import { rhythm, scale } from "../utils/typography" import { rhythm, scale } from '../utils/typography';
class Layout extends React.Component { class Layout extends React.Component {
render() { render() {
const { location, title, children } = this.props const { location, title, children } = this.props;
const rootPath = `${__PATH_PREFIX__}/` const rootPath = `${__PATH_PREFIX__}/`;
let header let header;
if (location.pathname === rootPath) { if (location.pathname === rootPath) {
header = ( header = (
@ -20,56 +20,59 @@ class Layout extends React.Component {
> >
<Link <Link
style={{ style={{
boxShadow: `none`, boxShadow: 'none',
textDecoration: `none`, textDecoration: 'none',
color: `inherit`, color: 'inherit',
}} }}
to={`/`} to="/"
> >
{title} {title}
</Link> </Link>
</h1> </h1>
) );
} else { } else {
header = ( header = (
<h3 <h3
style={{ style={{
fontFamily: `Montserrat, sans-serif`, fontFamily: 'Montserrat, sans-serif',
marginTop: 0, marginTop: 0,
}} }}
> >
<Link <Link
style={{ style={{
boxShadow: `none`, boxShadow: 'none',
textDecoration: `none`, textDecoration: 'none',
color: `inherit`, color: 'inherit',
}} }}
to={`/`} to="/"
> >
{title} {title}
</Link> </Link>
</h3> </h3>
) );
} }
return ( return (
<div <div
style={{ style={{
marginLeft: `auto`, marginLeft: 'auto',
marginRight: `auto`, marginRight: 'auto',
maxWidth: rhythm(24), maxWidth: rhythm(24),
padding: `${rhythm(1.5)} ${rhythm(3 / 4)}` padding: `${rhythm(1.5)} ${rhythm(3 / 4)}`,
}} }}
> >
<header>{header}</header> <header>{header}</header>
<main>{children}</main> <main>{children}</main>
<footer> <footer>
© {new Date().getFullYear()}, Built with ©
{` `} {' '}
{new Date().getFullYear()}
, Built with
{' '}
<a href="https://www.gatsbyjs.org">Gatsby</a> <a href="https://www.gatsbyjs.org">Gatsby</a>
</footer> </footer>
</div> </div>
) );
} }
} }
export default Layout export default Layout;

View File

@ -5,12 +5,14 @@
* See: https://www.gatsbyjs.org/docs/use-static-query/ * See: https://www.gatsbyjs.org/docs/use-static-query/
*/ */
import React from "react" import React from 'react';
import PropTypes from "prop-types" import PropTypes from 'prop-types';
import Helmet from "react-helmet" import Helmet from 'react-helmet';
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from 'gatsby';
function SEO({ description, lang, meta, title }) { function SEO({
description, lang, meta, title,
}) {
const { site } = useStaticQuery( const { site } = useStaticQuery(
graphql` graphql`
query { query {
@ -23,11 +25,11 @@ function SEO({ description, lang, meta, title }) {
} }
} }
} }
` `,
) );
const metaDescription = description || site.siteMetadata.description const metaDescription = description || site.siteMetadata.description;
const metaType = `website` || site.siteMetadata.type const metaType = 'website' || site.siteMetadata.type;
return ( return (
<Helmet <Helmet
@ -38,39 +40,39 @@ function SEO({ description, lang, meta, title }) {
titleTemplate={`%s | ${site.siteMetadata.title}`} titleTemplate={`%s | ${site.siteMetadata.title}`}
meta={[ meta={[
{ {
name: `description`, name: 'description',
content: metaDescription, content: metaDescription,
}, },
{ {
property: `og:title`, property: 'og:title',
content: title, content: title,
}, },
{ {
property: `og:description`, property: 'og:description',
content: metaDescription, content: metaDescription,
}, },
{ {
property: `og:type`, property: 'og:type',
content: metaType, content: metaType,
}, },
].concat(meta)} ].concat(meta)}
> >
<link href="https://fonts.googleapis.com/css?family=Oxygen+Mono&display=swap" rel="stylesheet"></link> <link href="https://fonts.googleapis.com/css?family=Oxygen+Mono&display=swap" rel="stylesheet" />
</Helmet> </Helmet>
) );
} }
SEO.defaultProps = { SEO.defaultProps = {
lang: `en`, lang: 'en',
meta: [], meta: [],
description: `A selfmade developer blog.`, description: 'A selfmade developer blog.',
} };
SEO.propTypes = { SEO.propTypes = {
description: PropTypes.string, description: PropTypes.string,
lang: PropTypes.string, lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object), meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
} };
export default SEO export default SEO;

View File

@ -1,13 +1,13 @@
import React from "react" import React from 'react';
import { graphql } from "gatsby" import { graphql } from 'gatsby';
import Layout from "../components/layout" import Layout from '../components/layout';
import SEO from "../components/seo" import SEO from '../components/seo';
class NotFoundPage extends React.Component { class NotFoundPage extends React.Component {
render() { render() {
const { data } = this.props const { data } = this.props;
const siteTitle = data.site.siteMetadata.title const siteTitle = data.site.siteMetadata.title;
return ( return (
<Layout location={this.props.location} title={siteTitle}> <Layout location={this.props.location} title={siteTitle}>
@ -15,11 +15,11 @@ class NotFoundPage extends React.Component {
<h1>Not Found</h1> <h1>Not Found</h1>
<p>You just hit a route that doesn&#39;t exist... the sadness.</p> <p>You just hit a route that doesn&#39;t exist... the sadness.</p>
</Layout> </Layout>
) );
} }
} }
export default NotFoundPage export default NotFoundPage;
export const pageQuery = graphql` export const pageQuery = graphql`
query { query {
@ -29,4 +29,4 @@ export const pageQuery = graphql`
} }
} }
} }
` `;

View File

@ -1,23 +1,23 @@
import React from "react" import React from 'react';
import { Link, graphql } from "gatsby" import { Link, graphql } from 'gatsby';
import Bio from "../components/bio" import Bio from '../components/bio';
import Layout from "../components/layout" import Layout from '../components/layout';
import SEO from "../components/seo" import SEO from '../components/seo';
import { rhythm } from "../utils/typography" import { rhythm } from '../utils/typography';
class BlogIndex extends React.Component { class BlogIndex extends React.Component {
render() { render() {
const { data } = this.props const { data } = this.props;
const siteTitle = data.site.siteMetadata.title const siteTitle = data.site.siteMetadata.title;
const posts = data.allMarkdownRemark.edges const posts = data.allMarkdownRemark.edges;
return ( return (
<Layout location={this.props.location} title={siteTitle}> <Layout location={this.props.location} title={siteTitle}>
<SEO title="All posts" /> <SEO title="All posts" />
<Bio /> <Bio />
{posts.map(({ node }) => { {posts.map(({ node }) => {
const title = node.frontmatter.title || node.fields.slug const title = node.frontmatter.title || node.fields.slug;
return ( return (
<article key={node.fields.slug}> <article key={node.fields.slug}>
<header> <header>
@ -26,7 +26,7 @@ class BlogIndex extends React.Component {
marginBottom: rhythm(1 / 4), marginBottom: rhythm(1 / 4),
}} }}
> >
<Link style={{ boxShadow: `none` }} to={node.fields.slug}> <Link style={{ boxShadow: 'none' }} to={node.fields.slug}>
{title} {title}
</Link> </Link>
</h3> </h3>
@ -40,29 +40,14 @@ class BlogIndex extends React.Component {
/> />
</section> </section>
</article> </article>
) );
})} })}
</Layout> </Layout>
) );
} }
} }
export default BlogIndex export default BlogIndex;
/** Matomo tracking */
var _paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//matomo.while-false.de/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
/** End of Matomo tracking */
export const pageQuery = graphql` export const pageQuery = graphql`
query { query {
@ -87,4 +72,4 @@ export const pageQuery = graphql`
} }
} }
} }
` `;

View File

@ -1,3 +1,7 @@
body { body {
background-color: #e3dcc2; background-color: #e3dcc2;
} }
#commento-textarea-root {
background-color: #00000010;
}

View File

@ -1,23 +1,24 @@
import React from "react" import React from 'react';
import { Link, graphql } from "gatsby" import { Link, graphql } from 'gatsby';
import Bio from "../components/bio" import Bio from '../components/bio';
import Layout from "../components/layout" import Layout from '../components/layout';
import SEO from "../components/seo" import SEO from '../components/seo';
import { rhythm, scale } from "../utils/typography" import { rhythm, scale } from '../utils/typography';
import Commento from '../components/commento';
class BlogPostTemplate extends React.Component { class BlogPostTemplate extends React.Component {
render() { render() {
const post = this.props.data.markdownRemark const post = this.props.data.markdownRemark;
const siteTitle = this.props.data.site.siteMetadata.title const siteTitle = this.props.data.site.siteMetadata.title;
const { previous, next } = this.props.pageContext const { previous, next } = this.props.pageContext;
return ( return (
<Layout location={this.props.location} title={siteTitle}> <Layout location={this.props.location} title={siteTitle}>
<SEO <SEO
title={post.frontmatter.title} title={post.frontmatter.title}
description={post.frontmatter.description || post.excerpt} description={post.frontmatter.description || post.excerpt}
type='article' type="article"
/> />
<article> <article>
<header> <header>
@ -32,7 +33,7 @@ class BlogPostTemplate extends React.Component {
<p <p
style={{ style={{
...scale(-1 / 5), ...scale(-1 / 5),
display: `block`, display: 'block',
marginBottom: rhythm(1), marginBottom: rhythm(1),
}} }}
> >
@ -47,41 +48,49 @@ class BlogPostTemplate extends React.Component {
/> />
<footer> <footer>
<Bio /> <Bio />
<div>
<h2>Comments</h2>
<Commento id={this.props.slug} />
</div>
</footer> </footer>
</article> </article>
<nav> <nav>
<ul <ul
style={{ style={{
display: `flex`, display: 'flex',
flexWrap: `wrap`, flexWrap: 'wrap',
justifyContent: `space-between`, justifyContent: 'space-between',
listStyle: `none`, listStyle: 'none',
padding: 0, padding: 0,
}} }}
> >
<li> <li>
{previous && ( {previous && (
<Link to={previous.fields.slug} rel="prev"> <Link to={previous.fields.slug} rel="prev">
{previous.frontmatter.title}
{' '}
{previous.frontmatter.title}
</Link> </Link>
)} )}
</li> </li>
<li> <li>
{next && ( {next && (
<Link to={next.fields.slug} rel="next"> <Link to={next.fields.slug} rel="next">
{next.frontmatter.title} {next.frontmatter.title}
{' '}
</Link> </Link>
)} )}
</li> </li>
</ul> </ul>
</nav> </nav>
</Layout> </Layout>
) );
} }
} }
export default BlogPostTemplate export default BlogPostTemplate;
export const pageQuery = graphql` export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) { query BlogPostBySlug($slug: String!) {
@ -101,4 +110,4 @@ export const pageQuery = graphql`
} }
} }
} }
` `;

View File

@ -1,23 +1,21 @@
import Typography from "typography" import Typography from 'typography';
import Moraga from "typography-theme-moraga" import Moraga from 'typography-theme-moraga';
Moraga.overrideThemeStyles = () => { Moraga.overrideThemeStyles = () => ({
return { 'a.gatsby-resp-image-link': {
"a.gatsby-resp-image-link": { boxShadow: 'none',
boxShadow: `none`, },
}, });
}
}
Moraga.headerFontFamily = ['Oxygen Mono']; Moraga.headerFontFamily = ['Oxygen Mono'];
const typography = new Typography(Moraga); const typography = new Typography(Moraga);
// Hot reload typography in development. // Hot reload typography in development.
if (process.env.NODE_ENV !== `production`) { if (process.env.NODE_ENV !== 'production') {
typography.injectStyles() typography.injectStyles();
} }
export default typography export default typography;
export const rhythm = typography.rhythm export const { rhythm } = typography;
export const scale = typography.scale export const { scale } = typography;

16169
yarn.lock Normal file

File diff suppressed because it is too large Load Diff