Compare commits
No commits in common. "master" and "1.3" have entirely different histories.
|
|
@ -1,3 +0,0 @@
|
|||
.cache/
|
||||
node_modules/
|
||||
public/
|
||||
14
.drone.yml
14
.drone.yml
|
|
@ -3,15 +3,19 @@ type: docker
|
|||
name: Blog build and release
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node:alpine
|
||||
commands:
|
||||
- npm install
|
||||
- npx gatsby build
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username: stephan
|
||||
password: eRFJ1R7UNo5zv1FFvLzv
|
||||
repo: registry.while-false.de/blog
|
||||
registry: registry.while-false.de
|
||||
tags:
|
||||
- 'latest'
|
||||
- '1.4.0'
|
||||
- '1.3.0'
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
11
Dockerfile
11
Dockerfile
|
|
@ -1,10 +1 @@
|
|||
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
|
||||
FROM gatsbyjs/gatsby:onbuild
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
|
|
@ -8,11 +8,11 @@ description: Initial commit - Introduction
|
|||
|
||||
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 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.
|
||||
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.
|
||||
|
||||
# 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 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.
|
||||
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.
|
||||
|
||||
# Contents
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
* 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 to 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 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.
|
||||
* 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,18 +19,16 @@ Some time ago I listened to [an episode of the podcast .Net rocks](https://dotne
|
|||
# How this is built
|
||||
|
||||
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).
|
||||
|
||||
# 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:
|
||||
```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!
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -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
|
||||
* 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 it does 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 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.
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ steps:
|
|||
commands:
|
||||
- npm install
|
||||
- npx gatsby build
|
||||
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
|
|
@ -91,7 +91,7 @@ For now I only require two steps:
|
|||
1. install node.js dependencies and build the gatsby project
|
||||
2. build the new docker image and push it to the registry
|
||||
|
||||
An additional benefit of the drone build is this beautiful badge, every project has nowadays, conveniently prepared as markdown:
|
||||
An additional benefit of the drone build is this beatiful badge, every project has nowadays, conveniently prepared as markdown:
|
||||
|
||||
[](https://drone.while-false.de/stephan/blog)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
---
|
||||
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).
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// custom typefaces
|
||||
import 'typeface-montserrat';
|
||||
import 'typeface-merriweather';
|
||||
import "typeface-montserrat"
|
||||
import "typeface-merriweather"
|
||||
|
||||
import './src/styles/global.css';
|
||||
import 'prismjs/themes/prism-solarizedlight.css';
|
||||
import "./src/styles/global.css";
|
||||
import "prismjs/themes/prism-solarizedlight.css";
|
||||
|
|
|
|||
121
gatsby-config.js
121
gatsby-config.js
|
|
@ -1,129 +1,76 @@
|
|||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: 'While False Blog',
|
||||
author: 'Stephan Dörfler',
|
||||
description: 'Self-built developer blog based on gatsby.',
|
||||
siteUrl: 'https://blog.while-false.de',
|
||||
type: 'website',
|
||||
title: `While False Blog`,
|
||||
author: `Stephan Dörfler`,
|
||||
description: `Self-built developer blog based on gatsby.`,
|
||||
siteUrl: `https://blog.while-false.de`,
|
||||
type: `website`,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
path: `${__dirname}/content/blog`,
|
||||
name: 'blog',
|
||||
name: `blog`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
path: `${__dirname}/content/assets`,
|
||||
name: 'assets',
|
||||
name: `assets`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-transformer-remark',
|
||||
resolve: `gatsby-transformer-remark`,
|
||||
options: {
|
||||
plugins: [
|
||||
{
|
||||
resolve: 'gatsby-remark-images',
|
||||
resolve: `gatsby-remark-images`,
|
||||
options: {
|
||||
maxWidth: 590,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-remark-responsive-iframe',
|
||||
resolve: `gatsby-remark-responsive-iframe`,
|
||||
options: {
|
||||
wrapperStyle: 'margin-bottom: 1.0725rem',
|
||||
wrapperStyle: `margin-bottom: 1.0725rem`,
|
||||
},
|
||||
},
|
||||
'gatsby-remark-prismjs',
|
||||
'gatsby-remark-copy-linked-files',
|
||||
'gatsby-remark-smartypants',
|
||||
`gatsby-remark-prismjs`,
|
||||
`gatsby-remark-copy-linked-files`,
|
||||
`gatsby-remark-smartypants`,
|
||||
],
|
||||
},
|
||||
},
|
||||
'gatsby-transformer-sharp',
|
||||
'gatsby-plugin-sharp',
|
||||
`gatsby-transformer-sharp`,
|
||||
`gatsby-plugin-sharp`,
|
||||
{
|
||||
resolve: 'gatsby-plugin-matomo',
|
||||
resolve: `gatsby-plugin-google-analytics`,
|
||||
options: {
|
||||
siteId: '1',
|
||||
matomoUrl: 'https://matomo.while-false.de',
|
||||
siteUrl: 'https://blog.while-false.de',
|
||||
matomoPhpScript: 'matomo.php',
|
||||
matomoJsScript: 'matomo.js',
|
||||
//trackingId: `ADD YOUR TRACKING ID HERE`,
|
||||
},
|
||||
},
|
||||
`gatsby-plugin-feed`,
|
||||
{
|
||||
resolve: 'gatsby-plugin-feed',
|
||||
resolve: `gatsby-plugin-manifest`,
|
||||
options: {
|
||||
query: `
|
||||
{
|
||||
site {
|
||||
siteMetadata {
|
||||
title
|
||||
description
|
||||
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',
|
||||
},
|
||||
],
|
||||
name: `While False Blog`,
|
||||
short_name: `while-false`,
|
||||
start_url: `/`,
|
||||
background_color: `#f9ebe0`,
|
||||
theme_color: `#3b7080`,
|
||||
display: `minimal-ui`,
|
||||
icon: `content/assets/gatsby-icon.png`,
|
||||
},
|
||||
},
|
||||
`gatsby-plugin-offline`,
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: 'gatsby-plugin-manifest',
|
||||
resolve: `gatsby-plugin-typography`,
|
||||
options: {
|
||||
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',
|
||||
pathToConfigModule: `src/utils/typography`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const path = require('path');
|
||||
const { createFilePath } = require('gatsby-source-filesystem');
|
||||
const path = require(`path`)
|
||||
const { createFilePath } = require(`gatsby-source-filesystem`)
|
||||
|
||||
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(
|
||||
`
|
||||
{
|
||||
|
|
@ -25,18 +25,18 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
)
|
||||
|
||||
if (result.errors) {
|
||||
throw result.errors;
|
||||
throw result.errors
|
||||
}
|
||||
|
||||
// Create blog posts pages.
|
||||
const posts = result.data.allMarkdownRemark.edges;
|
||||
const posts = result.data.allMarkdownRemark.edges
|
||||
|
||||
posts.forEach((post, index) => {
|
||||
const previous = index === posts.length - 1 ? null : posts[index + 1].node;
|
||||
const next = index === 0 ? 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
|
||||
|
||||
createPage({
|
||||
path: post.node.fields.slug,
|
||||
|
|
@ -46,18 +46,19 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
previous,
|
||||
next,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.onCreateNode = ({ node, actions, getNode }) => {
|
||||
const { createNodeField } = actions;
|
||||
if (node.internal.type === 'MarkdownRemark') {
|
||||
const value = createFilePath({ node, getNode });
|
||||
const { createNodeField } = actions
|
||||
|
||||
if (node.internal.type === `MarkdownRemark`) {
|
||||
const value = createFilePath({ node, getNode })
|
||||
createNodeField({
|
||||
name: 'slug',
|
||||
name: `slug`,
|
||||
node,
|
||||
value,
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
58
package.json
58
package.json
|
|
@ -2,47 +2,39 @@
|
|||
"name": "while-false-blog",
|
||||
"private": true,
|
||||
"description": "A simple blog powered by Gatsby and Markdown",
|
||||
"version": "1.0.0",
|
||||
"author": "Stephan Dörfler <stephan@while-false.de>",
|
||||
"version": "0.1.0",
|
||||
"author": "Stephan Dörfler <st.doerfler@outlook.com>",
|
||||
"dependencies": {
|
||||
"gatsby": "^2.23.3",
|
||||
"gatsby-cli": "^2.12.45",
|
||||
"gatsby-image": "^2.4.6",
|
||||
"gatsby-plugin-feed": "^2.5.4",
|
||||
"gatsby-plugin-manifest": "^2.4.10",
|
||||
"gatsby-plugin-matomo": "^0.8.3",
|
||||
"gatsby-plugin-offline": "^3.2.8",
|
||||
"gatsby-plugin-react-helmet": "^3.3.3",
|
||||
"gatsby-plugin-sharp": "^2.6.10",
|
||||
"gatsby-plugin-typography": "^2.5.3",
|
||||
"gatsby-remark-copy-linked-files": "^2.3.4",
|
||||
"gatsby-remark-images": "^3.3.9",
|
||||
"gatsby-remark-prismjs": "^3.5.3",
|
||||
"gatsby-remark-responsive-iframe": "^2.4.4",
|
||||
"gatsby-remark-smartypants": "^2.3.3",
|
||||
"gatsby-source-filesystem": "^2.3.10",
|
||||
"gatsby-transformer-remark": "^2.8.14",
|
||||
"gatsby-transformer-sharp": "^2.5.4",
|
||||
"global": "^4.4.0",
|
||||
"prismjs": "^1.20.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"gatsby": "^2.18.8",
|
||||
"gatsby-image": "^2.2.34",
|
||||
"gatsby-plugin-feed": "^2.3.23",
|
||||
"gatsby-plugin-google-analytics": "^2.1.29",
|
||||
"gatsby-plugin-manifest": "^2.2.31",
|
||||
"gatsby-plugin-offline": "^3.0.27",
|
||||
"gatsby-plugin-react-helmet": "^3.1.16",
|
||||
"gatsby-plugin-sharp": "^2.3.5",
|
||||
"gatsby-plugin-typography": "^2.3.18",
|
||||
"gatsby-remark-copy-linked-files": "^2.1.31",
|
||||
"gatsby-remark-images": "^3.1.35",
|
||||
"gatsby-remark-prismjs": "^3.3.25",
|
||||
"gatsby-remark-responsive-iframe": "^2.2.28",
|
||||
"gatsby-remark-smartypants": "^2.1.17",
|
||||
"gatsby-source-filesystem": "^2.1.40",
|
||||
"gatsby-transformer-remark": "^2.6.39",
|
||||
"gatsby-transformer-sharp": "^2.3.7",
|
||||
"prismjs": "^1.17.1",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-typography": "^0.16.19",
|
||||
"typeface-merriweather": "^0.0.72",
|
||||
"typeface-montserrat": "^0.0.75",
|
||||
"typeface-merriweather": "0.0.72",
|
||||
"typeface-montserrat": "0.0.75",
|
||||
"typography": "^0.16.19",
|
||||
"typography-theme-moraga": "^0.16.19",
|
||||
"typography-theme-wordpress-2016": "^0.16.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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"
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
* See: https://www.gatsbyjs.org/docs/use-static-query/
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
import Image from 'gatsby-image';
|
||||
import React from "react"
|
||||
import { useStaticQuery, graphql } from "gatsby"
|
||||
import Image from "gatsby-image"
|
||||
|
||||
import { rhythm } from '../utils/typography';
|
||||
import { rhythm } from "../utils/typography"
|
||||
|
||||
const Bio = () => {
|
||||
const data = useStaticQuery(graphql`
|
||||
|
|
@ -27,13 +27,13 @@ const Bio = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`)
|
||||
|
||||
const { author } = data.site.siteMetadata;
|
||||
const { author } = data.site.siteMetadata
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
display: `flex`,
|
||||
marginBottom: rhythm(2.5),
|
||||
}}
|
||||
>
|
||||
|
|
@ -44,22 +44,18 @@ const Bio = () => {
|
|||
marginRight: rhythm(1 / 2),
|
||||
marginBottom: 0,
|
||||
minWidth: 50,
|
||||
borderRadius: '100%',
|
||||
borderRadius: `100%`,
|
||||
}}
|
||||
imgStyle={{
|
||||
borderRadius: '50%',
|
||||
borderRadius: `50%`,
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Bio;
|
||||
export default Bio
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'gatsby';
|
||||
import React from "react"
|
||||
import { Link } from "gatsby"
|
||||
|
||||
import { rhythm, scale } from '../utils/typography';
|
||||
import { rhythm, scale } from "../utils/typography"
|
||||
|
||||
class Layout extends React.Component {
|
||||
render() {
|
||||
const { location, title, children } = this.props;
|
||||
const rootPath = `${__PATH_PREFIX__}/`;
|
||||
let header;
|
||||
const { location, title, children } = this.props
|
||||
const rootPath = `${__PATH_PREFIX__}/`
|
||||
let header
|
||||
|
||||
if (location.pathname === rootPath) {
|
||||
header = (
|
||||
|
|
@ -20,59 +20,56 @@ class Layout extends React.Component {
|
|||
>
|
||||
<Link
|
||||
style={{
|
||||
boxShadow: 'none',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
boxShadow: `none`,
|
||||
textDecoration: `none`,
|
||||
color: `inherit`,
|
||||
}}
|
||||
to="/"
|
||||
to={`/`}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</h1>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
header = (
|
||||
<h3
|
||||
style={{
|
||||
fontFamily: 'Montserrat, sans-serif',
|
||||
fontFamily: `Montserrat, sans-serif`,
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
style={{
|
||||
boxShadow: 'none',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
boxShadow: `none`,
|
||||
textDecoration: `none`,
|
||||
color: `inherit`,
|
||||
}}
|
||||
to="/"
|
||||
to={`/`}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
);
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginLeft: `auto`,
|
||||
marginRight: `auto`,
|
||||
maxWidth: rhythm(24),
|
||||
padding: `${rhythm(1.5)} ${rhythm(3 / 4)}`,
|
||||
padding: `${rhythm(1.5)} ${rhythm(3 / 4)}`
|
||||
}}
|
||||
>
|
||||
<header>{header}</header>
|
||||
<main>{children}</main>
|
||||
<footer>
|
||||
©
|
||||
{' '}
|
||||
{new Date().getFullYear()}
|
||||
, Built with
|
||||
{' '}
|
||||
© {new Date().getFullYear()}, Built with
|
||||
{` `}
|
||||
<a href="https://www.gatsbyjs.org">Gatsby</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
export default Layout
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@
|
|||
* See: https://www.gatsbyjs.org/docs/use-static-query/
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Helmet from "react-helmet"
|
||||
import { useStaticQuery, graphql } from "gatsby"
|
||||
|
||||
function SEO({
|
||||
description, lang, meta, title,
|
||||
}) {
|
||||
function SEO({ description, lang, meta, title }) {
|
||||
const { site } = useStaticQuery(
|
||||
graphql`
|
||||
query {
|
||||
|
|
@ -25,11 +23,11 @@ function SEO({
|
|||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
);
|
||||
`
|
||||
)
|
||||
|
||||
const metaDescription = description || site.siteMetadata.description;
|
||||
const metaType = 'website' || site.siteMetadata.type;
|
||||
const metaDescription = description || site.siteMetadata.description
|
||||
const metaType = `website` || site.siteMetadata.type
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
|
|
@ -40,39 +38,39 @@ function SEO({
|
|||
titleTemplate={`%s | ${site.siteMetadata.title}`}
|
||||
meta={[
|
||||
{
|
||||
name: 'description',
|
||||
name: `description`,
|
||||
content: metaDescription,
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
property: `og:title`,
|
||||
content: title,
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
property: `og:description`,
|
||||
content: metaDescription,
|
||||
},
|
||||
{
|
||||
property: 'og:type',
|
||||
property: `og:type`,
|
||||
content: metaType,
|
||||
},
|
||||
].concat(meta)}
|
||||
>
|
||||
<link href="https://fonts.googleapis.com/css?family=Oxygen+Mono&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Oxygen+Mono&display=swap" rel="stylesheet"></link>
|
||||
</Helmet>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
SEO.defaultProps = {
|
||||
lang: 'en',
|
||||
lang: `en`,
|
||||
meta: [],
|
||||
description: 'A selfmade developer blog.',
|
||||
};
|
||||
description: `A selfmade developer blog.`,
|
||||
}
|
||||
|
||||
SEO.propTypes = {
|
||||
description: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
meta: PropTypes.arrayOf(PropTypes.object),
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
export default SEO;
|
||||
export default SEO
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import { graphql } from 'gatsby';
|
||||
import React from "react"
|
||||
import { graphql } from "gatsby"
|
||||
|
||||
import Layout from '../components/layout';
|
||||
import SEO from '../components/seo';
|
||||
import Layout from "../components/layout"
|
||||
import SEO from "../components/seo"
|
||||
|
||||
class NotFoundPage extends React.Component {
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
const siteTitle = data.site.siteMetadata.title;
|
||||
const { data } = this.props
|
||||
const siteTitle = data.site.siteMetadata.title
|
||||
|
||||
return (
|
||||
<Layout location={this.props.location} title={siteTitle}>
|
||||
|
|
@ -15,11 +15,11 @@ class NotFoundPage extends React.Component {
|
|||
<h1>Not Found</h1>
|
||||
<p>You just hit a route that doesn't exist... the sadness.</p>
|
||||
</Layout>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
||||
export default NotFoundPage
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query {
|
||||
|
|
@ -29,4 +29,4 @@ export const pageQuery = graphql`
|
|||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import React from 'react';
|
||||
import { Link, graphql } from 'gatsby';
|
||||
import React from "react"
|
||||
import { Link, graphql } from "gatsby"
|
||||
|
||||
import Bio from '../components/bio';
|
||||
import Layout from '../components/layout';
|
||||
import SEO from '../components/seo';
|
||||
import { rhythm } from '../utils/typography';
|
||||
import Bio from "../components/bio"
|
||||
import Layout from "../components/layout"
|
||||
import SEO from "../components/seo"
|
||||
import { rhythm } from "../utils/typography"
|
||||
|
||||
class BlogIndex extends React.Component {
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
const siteTitle = data.site.siteMetadata.title;
|
||||
const posts = data.allMarkdownRemark.edges;
|
||||
const { data } = this.props
|
||||
const siteTitle = data.site.siteMetadata.title
|
||||
const posts = data.allMarkdownRemark.edges
|
||||
|
||||
return (
|
||||
<Layout location={this.props.location} title={siteTitle}>
|
||||
<SEO title="All posts" />
|
||||
<Bio />
|
||||
{posts.map(({ node }) => {
|
||||
const title = node.frontmatter.title || node.fields.slug;
|
||||
const title = node.frontmatter.title || node.fields.slug
|
||||
return (
|
||||
<article key={node.fields.slug}>
|
||||
<header>
|
||||
|
|
@ -26,7 +26,7 @@ class BlogIndex extends React.Component {
|
|||
marginBottom: rhythm(1 / 4),
|
||||
}}
|
||||
>
|
||||
<Link style={{ boxShadow: 'none' }} to={node.fields.slug}>
|
||||
<Link style={{ boxShadow: `none` }} to={node.fields.slug}>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
|
|
@ -40,14 +40,29 @@ class BlogIndex extends React.Component {
|
|||
/>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</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`
|
||||
query {
|
||||
|
|
@ -72,4 +87,4 @@ export const pageQuery = graphql`
|
|||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
body {
|
||||
background-color: #e3dcc2;
|
||||
}
|
||||
|
||||
#commento-textarea-root {
|
||||
background-color: #00000010;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,23 @@
|
|||
import React from 'react';
|
||||
import { Link, graphql } from 'gatsby';
|
||||
import React from "react"
|
||||
import { Link, graphql } from "gatsby"
|
||||
|
||||
import Bio from '../components/bio';
|
||||
import Layout from '../components/layout';
|
||||
import SEO from '../components/seo';
|
||||
import { rhythm, scale } from '../utils/typography';
|
||||
import Commento from '../components/commento';
|
||||
import Bio from "../components/bio"
|
||||
import Layout from "../components/layout"
|
||||
import SEO from "../components/seo"
|
||||
import { rhythm, scale } from "../utils/typography"
|
||||
|
||||
class BlogPostTemplate extends React.Component {
|
||||
render() {
|
||||
const post = this.props.data.markdownRemark;
|
||||
const siteTitle = this.props.data.site.siteMetadata.title;
|
||||
const { previous, next } = this.props.pageContext;
|
||||
const post = this.props.data.markdownRemark
|
||||
const siteTitle = this.props.data.site.siteMetadata.title
|
||||
const { previous, next } = this.props.pageContext
|
||||
|
||||
return (
|
||||
<Layout location={this.props.location} title={siteTitle}>
|
||||
<SEO
|
||||
title={post.frontmatter.title}
|
||||
description={post.frontmatter.description || post.excerpt}
|
||||
type="article"
|
||||
type='article'
|
||||
/>
|
||||
<article>
|
||||
<header>
|
||||
|
|
@ -33,7 +32,7 @@ class BlogPostTemplate extends React.Component {
|
|||
<p
|
||||
style={{
|
||||
...scale(-1 / 5),
|
||||
display: 'block',
|
||||
display: `block`,
|
||||
marginBottom: rhythm(1),
|
||||
}}
|
||||
>
|
||||
|
|
@ -48,49 +47,41 @@ class BlogPostTemplate extends React.Component {
|
|||
/>
|
||||
<footer>
|
||||
<Bio />
|
||||
<div>
|
||||
<h2>Comments</h2>
|
||||
<Commento id={this.props.slug} />
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<nav>
|
||||
<ul
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
listStyle: 'none',
|
||||
display: `flex`,
|
||||
flexWrap: `wrap`,
|
||||
justifyContent: `space-between`,
|
||||
listStyle: `none`,
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<li>
|
||||
{previous && (
|
||||
<Link to={previous.fields.slug} rel="prev">
|
||||
←
|
||||
{' '}
|
||||
{previous.frontmatter.title}
|
||||
← {previous.frontmatter.title}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{next && (
|
||||
<Link to={next.fields.slug} rel="next">
|
||||
{next.frontmatter.title}
|
||||
{' '}
|
||||
→
|
||||
{next.frontmatter.title} →
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</Layout>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default BlogPostTemplate;
|
||||
export default BlogPostTemplate
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query BlogPostBySlug($slug: String!) {
|
||||
|
|
@ -110,4 +101,4 @@ export const pageQuery = graphql`
|
|||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
import Typography from 'typography';
|
||||
import Moraga from 'typography-theme-moraga';
|
||||
import Typography from "typography"
|
||||
import Moraga from "typography-theme-moraga"
|
||||
|
||||
Moraga.overrideThemeStyles = () => ({
|
||||
'a.gatsby-resp-image-link': {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
});
|
||||
Moraga.overrideThemeStyles = () => {
|
||||
return {
|
||||
"a.gatsby-resp-image-link": {
|
||||
boxShadow: `none`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Moraga.headerFontFamily = ['Oxygen Mono'];
|
||||
|
||||
const typography = new Typography(Moraga);
|
||||
|
||||
// Hot reload typography in development.
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
typography.injectStyles();
|
||||
if (process.env.NODE_ENV !== `production`) {
|
||||
typography.injectStyles()
|
||||
}
|
||||
|
||||
export default typography;
|
||||
export const { rhythm } = typography;
|
||||
export const { scale } = typography;
|
||||
export default typography
|
||||
export const rhythm = typography.rhythm
|
||||
export const scale = typography.scale
|
||||
|
|
|
|||
Loading…
Reference in New Issue