1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-22 01:03:37 +01:00

remove react-helmet

This commit is contained in:
Matthias Kretschmann 2022-11-19 15:09:13 +00:00
parent a0934b1165
commit cf083d3288
Signed by: m
GPG Key ID: 606EEEF3C479A91F
29 changed files with 432 additions and 591 deletions

View File

@ -0,0 +1,27 @@
const matchMediaMock = Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest
.fn()
.mockImplementationOnce((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
.mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
}))
})
export default matchMediaMock

View File

@ -1,4 +1,5 @@
import '@testing-library/jest-dom/extend-expect'
import './__mocks__/matchMedia'
import * as Gatsby from 'gatsby'
const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery')

View File

@ -26,38 +26,29 @@ A continuously updated list of devices, tools, and services I use to get digital
My office is where my MacBook is, all these devices go wherever I travel to:
- **[MacBook Pro (16-inch, 2019)](https://www.apple.com/macbook-pro-16/)**
_Space Gray, 2.6GHz 6-Core Intel Core i7, 16 GB RAM, 512GB SSD, AMD Radeon Pro 5300M 4GB, US International keyboard_
My main workhorse powering my daily work & play. Everything is pretty much amazing and it handles everything I throw at it. Love the screen, keyboard, and speakers. Still miss the SD card slot which Apple deemed not Pro enough.
_Space Gray, 2.6GHz 6-Core Intel Core i7, 16 GB RAM, 512GB SSD, AMD Radeon Pro 5300M 4GB, US International keyboard_
- **[iPhone 11 Pro](https://www.apple.com/iphone-11-pro/)**
_Space Gray, 256GB_
My main camera and used for everything when I'm not on my Mac. I try to keep it only for personal communication and reading. I'm never quite sure what to do with all its unbelievable power, but the screen is the best display I ever looked at. Because of that, replaces a lot the iPad for reading.
_Space Gray, 256GB_
- **[AirPods Pro](https://www.apple.com/airpods-pro/)**
Rocked the original AirPods since their introduction, one of the best products Apple has ever made. By now using the Pro variant pretty much all the time when talking into my devices and participating in online meetings, or listening to music on the go.
- **[AirPods Pro](https://www.apple.com/airpods-pro/)**
- **[iPad Pro (12.9-inch) 2021](https://www.apple.com/ipad-pro/)**
_Space Gray, 512GB, WiFi + Cellular_
My 4th iPad after the original iPad. Since that time used it as a paper document replacement for reading and research and it became my favorite device to browse and edit photos on. Until I can run a local web development environment on iPadOS it is rarely useful for me to get coding work done but still intrigued by the possibilities and cleanliness of iPadOS compared to macOS.
_Space Gray, 512GB, WiFi + Cellular_
- **[Logitech MX Master 3 for Mac](https://www.logitech.com/en-us/products/mice/mx-master-3-mac-wireless-mouse.910-005693.html)**
_Black & Space Gray_
Still a fan of the simplicity and gestures of the Magic Mouse 2, but it's just no fit for working precisely, or for gaming. Logitech's MX Master 3 provides both, and has great macOS support, unlike the people over at Razer.
_Black & Space Gray_
- **[Satechi Slim X1 Bluetooth Backlit Keyboard](https://satechi.net/products/slim-x1-bluetooth-backlit-keyboard)**
_US, Space Gray_
Great compact keyboard with black keys which have a great key travel. Packed with other welcome features over Apple's keyboards like backlit keys and easy switching between 3 paired devices.
_US, Space Gray_
- **[Apple Watch Series 5](https://www.apple.com/apple-watch-series-5/)**
_40mm Space Gray Aluminum Case, Black Solo Loop_
Mainly used for its health & fitness features so I can go on a run without my iPhone, and track the shit out of me. I like to keep it unobtrusive, all notifications are turned off, except for calendar event & Activity.app notifications.
_40mm Space Gray Aluminum Case, Black Solo Loop_
- **[Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)**
_4GB RAM_
The little Linux and local server playground performing some serious tasks for all devices within any network I arrive at: [Pi-Hole](https://pi-hole.net) keeps all browsing adventures clean, and every network device's DNS traffic is routed through the Raspberry Pi. [Plex](https://www.plex.tv) is delivering a video library, capable of playing 4K content. It runs a [Tor](https://www.torproject.org) non-exit relay. Has a 4 TB hard drive attached for [storage for all network devices](/raspberry-pi-file-and-screen-sharing-macos-ios).
_4GB RAM_
- **[Kindle Voyage](https://www.amazon.com/Amazon-Kindle-Voyage-6-Inch-4GB-eReader/dp/B00IOY8XWQ)**
The best experience for long time reading. Almost all my non-technical books are Kindle books.
- **[Kindle Paperwhite 11th Gen](https://www.amazon.com/dp/B08KTZ8249)**
## Software
@ -77,7 +68,7 @@ I live with **automatic dark mode** where all my devices and apps have a light t
- **[iCloud Drive, 2 TB](https://www.icloud.com)**
I have used Dropbox Pro for many years but it became too clunky and Apple's version turned into what I wanted Dropbox to be. Most of my non-code related files live there and are happily synced.
- **[Tresorit Premium, 500 GB](https://tresorit.com)**
- **[Tresorit Premium, 1 TB](https://tresorit.com)**
Holds all the personal and sensitive documents. Works like Dropbox or iCloud Drive but with end-to-end encryption with my own private keys, and some nicely paranoid sharing features.
- **[Scanner Pro](https://readdle.com/scannerpro)**
@ -224,7 +215,7 @@ I live with **automatic dark mode** where all my devices and apps have a light t
- **[GnuPG](https://gnupg.org)**
Use it since I use email but its clunky and rarely anyone uses it. Interacting with it only in Terminal.app for decrypting and encrypting, and use it to sign Git commits. Yes, I'm aware of [GPG Suite](https://gpgtools.org) but the instability it introduces into the whole operating system is not worth the usage to me.
- **[Messages](https://support.apple.com/explore/messages) & [Telegram](https://telegram.org)**
- **[Messages](https://support.apple.com/explore/messages) & [WhatsApp](https://whatsapp.com)**
The only messengers I use every day for personal stuff, mostly on my iPhone.
- **[Signal](https://www.signal.org)**
@ -288,16 +279,13 @@ Except within Notes.app, everything I write is composed as [GitHub Flavored Mark
- **[iCloud Backup](https://support.apple.com/en-us/HT203977)**
All mobile devices simply use this to create their backups.
- **[Rclone](https://rclone.org)**
For moving large cloud data around within and between online storage services. Usually run from my remote web server.
## Self Hosted
- I host my **[blog](https://kremalicious.com)** (which also includes my photo stream) and **[portfolio](https://matthiaskretschmann.com)** on **[AWS S3](https://aws.amazon.com/s3/)**, with **[Cloudflare](https://www.cloudflare.com)** in front of it.
- I run my own **web and development server**, a droplet on **[DigitalOcean](https://m.do.co/c/9882a054acf6)**, running **[Nginx](https://nginx.org)**.
- I run my own **analytics server** with **[Matomo](https://matomo.org)**.
- I run my own **analytics server** with **[Umami](https://umami.is)**.
- I run my own **Git repository hosting** with **[Gitea](https://gitea.com)** for private projects, and for automatically mirroring every GitHub repository I touch into it. A VPS running within **[Amazon Lightsail](https://aws.amazon.com/lightsail/)**.

View File

@ -85,10 +85,7 @@ const config: GatsbyConfig = {
parentSelector: { 'body.dark': 'Nord' }
},
injectStyles: false,
extensions: [
'nord-visual-studio-code',
`${__dirname}/vendor/polar-0.0.6.vsix`
],
extensions: ['nord-visual-studio-code', 'polar'],
languageAliases: {}
}
}
@ -217,7 +214,6 @@ const config: GatsbyConfig = {
excludes: ['/archive', '/archive/**/*', '/thanks', '/tags']
}
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-catch-links',
'gatsby-redirect-from',
'gatsby-plugin-meta-redirect',

130
package-lock.json generated
View File

@ -27,7 +27,6 @@
"gatsby-plugin-manifest": "^5.0.0",
"gatsby-plugin-meta-redirect": "^1.1.1",
"gatsby-plugin-offline": "^6.0.0",
"gatsby-plugin-react-helmet": "^6.0.0",
"gatsby-plugin-sharp": "^5.0.0",
"gatsby-plugin-sitemap": "^6.0.0",
"gatsby-plugin-svgr": "^3.0.0-beta.0",
@ -48,7 +47,6 @@
"react": "^18.2.0",
"react-clipboard.js": "^2.0.16",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-transition-group": "^4.4.5",
"rehype-react": "^7.1.1",
"remark-parse": "^10.0.1",
@ -68,11 +66,9 @@
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8",
"@types/react-helmet": "^6.1.5",
"@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-preset-gatsby": "^3.0.0",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
@ -89,6 +85,7 @@
"node-iptc": "^1.0.5",
"npm-run-all": "^4.1.5",
"ora": "^6.1.2",
"polar": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"stylelint": "^14.14.1",
@ -9721,15 +9718,6 @@
"@types/react": "*"
}
},
"node_modules/@types/react-helmet": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz",
"integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -11047,18 +11035,6 @@
"@xtuc/long": "4.2.2"
}
},
"node_modules/@welldone-software/why-did-you-render": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz",
"integrity": "sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA==",
"dev": true,
"dependencies": {
"lodash": "^4"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18"
}
},
"node_modules/@wry/context": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz",
@ -18629,21 +18605,6 @@
"node": ">=10.0.0"
}
},
"node_modules/gatsby-plugin-react-helmet": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-6.0.0.tgz",
"integrity": "sha512-uK+KManFE06oeWrZcxpmrpDT0dfDLMPtTxBiU1YPeQeP31IMSsR1vMDTYnQcB+8y8Jw7xwM59id+mwmAYz1fZA==",
"dependencies": {
"@babel/runtime": "^7.15.4"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"gatsby": "^5.0.0-next",
"react-helmet": "^5.1.3 || ^6.0.0"
}
},
"node_modules/gatsby-plugin-sharp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-5.0.0.tgz",
@ -30359,6 +30320,15 @@
"node": ">=10.13.0"
}
},
"node_modules/polar": {
"version": "0.1.1",
"resolved": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"integrity": "sha512-2QrsAD1Xzewu+T4BJxOB1kIrtRibEdC+/xboP81ueu03/YHYGwdlGXz/YYvqQ09uLqHEPD0BgguWoq/YpCk6Xg==",
"dev": true,
"engines": {
"vscode": "^1.30.0"
}
},
"node_modules/posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -31686,25 +31656,6 @@
"react": "^18.2.0"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"node_modules/react-helmet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.7.2",
"react-fast-compare": "^3.1.1",
"react-side-effect": "^2.1.0"
},
"peerDependencies": {
"react": ">=16.3.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -31822,14 +31773,6 @@
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-side-effect": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
"peerDependencies": {
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@ -45057,15 +45000,6 @@
"@types/react": "*"
}
},
"@types/react-helmet": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz",
"integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -46106,15 +46040,6 @@
"@xtuc/long": "4.2.2"
}
},
"@welldone-software/why-did-you-render": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz",
"integrity": "sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA==",
"dev": true,
"requires": {
"lodash": "^4"
}
},
"@wry/context": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz",
@ -52439,14 +52364,6 @@
}
}
},
"gatsby-plugin-react-helmet": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-6.0.0.tgz",
"integrity": "sha512-uK+KManFE06oeWrZcxpmrpDT0dfDLMPtTxBiU1YPeQeP31IMSsR1vMDTYnQcB+8y8Jw7xwM59id+mwmAYz1fZA==",
"requires": {
"@babel/runtime": "^7.15.4"
}
},
"gatsby-plugin-sharp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-5.0.0.tgz",
@ -60605,6 +60522,11 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"polar": {
"version": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"integrity": "sha512-2QrsAD1Xzewu+T4BJxOB1kIrtRibEdC+/xboP81ueu03/YHYGwdlGXz/YYvqQ09uLqHEPD0BgguWoq/YpCk6Xg==",
"dev": true
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -61521,22 +61443,6 @@
"scheduler": "^0.23.0"
}
},
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-helmet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
"requires": {
"object-assign": "^4.1.1",
"prop-types": "^15.7.2",
"react-fast-compare": "^3.1.1",
"react-side-effect": "^2.1.0"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -61613,12 +61519,6 @@
"react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
}
},
"react-side-effect": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
"requires": {}
},
"react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",

View File

@ -45,7 +45,6 @@
"gatsby-plugin-manifest": "^5.0.0",
"gatsby-plugin-meta-redirect": "^1.1.1",
"gatsby-plugin-offline": "^6.0.0",
"gatsby-plugin-react-helmet": "^6.0.0",
"gatsby-plugin-sharp": "^5.0.0",
"gatsby-plugin-sitemap": "^6.0.0",
"gatsby-plugin-svgr": "^3.0.0-beta.0",
@ -66,7 +65,6 @@
"react": "^18.2.0",
"react-clipboard.js": "^2.0.16",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-transition-group": "^4.4.5",
"rehype-react": "^7.1.1",
"remark-parse": "^10.0.1",
@ -86,11 +84,9 @@
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8",
"@types/react-helmet": "^6.1.5",
"@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-preset-gatsby": "^3.0.0",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
@ -107,6 +103,7 @@
"node-iptc": "^1.0.5",
"npm-run-all": "^4.1.5",
"ora": "^6.1.2",
"polar": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"stylelint": "^14.14.1",

View File

@ -4,12 +4,6 @@ import Header from './organisms/Header'
import Footer from './organisms/Footer'
import * as styles from './Layout.module.css'
// if (process.env.NODE_ENV !== 'production') {
// // eslint-disable-next-line
// const whyDidYouRender = require('@welldone-software/why-did-you-render/dist/no-classes-transpile/umd/whyDidYouRender.min.js')
// whyDidYouRender(React)
// }
export default function Layout({ children }: { children: any }): ReactElement {
return (
<>

View File

@ -19,8 +19,7 @@ export default function ExifMap({
}: {
gps: { latitude: number; longitude: number }
}): ReactElement {
const { value } = useDarkMode()
const isDarkMode = value
const { isDarkMode } = useDarkMode()
const [zoom, setZoom] = useState(12)
const zoomIn = () => {

View File

@ -0,0 +1,62 @@
import { ImageDataLike } from 'gatsby-plugin-image'
import React, { ReactElement } from 'react'
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
type SchemaOrgProps = {
post?: {
title: string
url: string
image: ImageDataLike
description: string
datePublished: string
dateModified: string
}
}
export default function SchemaOrg({ post }: SchemaOrgProps): ReactElement {
const { siteTitle, siteUrl, author } = useSiteMetadata()
const schemaOrgJsonLd: any = [
{
'@context': 'http://schema.org',
'@type': 'Blog',
url: siteUrl,
name: siteTitle
}
]
if (post) {
schemaOrgJsonLd.push({
'@context': 'http://schema.org',
'@type': 'BlogPosting',
author: {
'@type': 'Person',
name: author.name
},
publisher: {
'@type': 'Organization',
name: author.name
},
url: post.url,
name: post.title,
headline: post.title,
image: {
'@type': 'ImageObject',
url: post.image
},
description: post.description,
datePublished: post.datePublished,
dateModified: post.dateModified || post.datePublished,
mainEntityOfPage: {
'@type': 'Blog',
'@id': siteUrl
}
})
}
return (
<script type="application/ld+json">
{JSON.stringify(schemaOrgJsonLd)}
</script>
)
}

View File

@ -0,0 +1,72 @@
import React, { ReactElement } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { getSrc, ImageDataLike } from 'gatsby-plugin-image'
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
import useDarkMode from '../../../hooks/useDarkMode'
const query = graphql`
query Logo {
logo: allFile(filter: { name: { eq: "apple-touch-icon" } }) {
edges {
node {
relativePath
}
}
}
}
`
export type HeadMetaProps = {
title?: string
description?: string
image?: ImageDataLike
slug: string
children?: ReactElement
}
export default function HeadMeta(props: HeadMetaProps): ReactElement {
const data = useStaticQuery<Queries.LogoQuery>(query)
const logo = data.logo.edges[0].node.relativePath
const { siteTitle, siteUrl, siteDescription, author } = useSiteMetadata()
const { themeColor } = useDarkMode()
const title = props.title
? `${props.title} ¦ ${siteTitle}`
: `${siteTitle} ¦ ${siteDescription}`
const description = props.description
? props.description.slice(0, 160)
: siteDescription
const url = props.slug ? `${siteUrl}${props.slug}` : siteUrl
const image = props.image
? `${siteUrl}${getSrc(props.image)}`
: `${siteUrl}${logo}`
return (
<>
<link rel="canonical" href={url} />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content={author.twitter} />
<meta name="theme-color" content={themeColor} />
<meta name="msapplication-TileColor" content={themeColor} />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<link
rel="alternate"
title="JSON Feed"
type="application/json"
href={`${siteUrl}/feed.json`}
/>
{props.children}
</>
)
}

View File

@ -1,75 +0,0 @@
import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
import schemaOrg from './schemaOrg'
function feedLinks(siteUrl: string) {
return (
<link
rel="alternate"
title="JSON Feed"
type="application/json"
href={`${siteUrl}/feed.json`}
/>
)
}
export default function MetaTags({
description,
image,
url,
postSEO,
title,
datePublished,
dateModified
}: {
description: string
image: string
url: string
postSEO: boolean
title: string
datePublished: string
dateModified: string
}): ReactElement {
const { siteTitle, siteDescription, siteUrl, author } = useSiteMetadata()
return (
<Helmet
defaultTitle={`${siteTitle} ¦ ${siteDescription}`}
titleTemplate={`%s ¦ ${siteTitle}`}
>
<html lang="en" />
<meta name="description" content={description} />
<meta name="image" content={image} />
<link rel="canonical" href={url} />
{schemaOrg(
siteUrl,
title,
postSEO,
url,
image,
description,
author.name,
datePublished,
dateModified
)}
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta property="og:site_name" content={siteTitle}></meta>
{postSEO && <meta property="og:type" content="article" />}
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:creator"
content={author.twitter ? author.twitter : ''}
/>
{feedLinks(siteUrl)}
</Helmet>
)
}

View File

@ -1,75 +0,0 @@
import React, { ReactElement } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { getSrc } from 'gatsby-plugin-image'
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
import MetaTags from './MetaTags'
const query = graphql`
query Logo {
logo: allFile(filter: { name: { eq: "apple-touch-icon" } }) {
edges {
node {
relativePath
}
}
}
}
`
export interface SeoPost {
frontmatter: {
title: string
description?: string
image?: any
updated?: string
}
fields?: {
date: string
}
excerpt?: string
}
export default function SEO({
post,
slug
}: {
post?: SeoPost
slug?: string
}): ReactElement {
const data = useStaticQuery<Queries.LogoQuery>(query)
const logo = data.logo.edges[0].node.relativePath
const { siteTitle, siteUrl, siteDescription } = useSiteMetadata()
let title: string
let description: string
let image: string
let postURL: string
if (post) {
const postMeta = post.frontmatter
title = `${postMeta.title} ¦ ${siteTitle}`
description = postMeta.description ? postMeta.description : post.excerpt
image = postMeta.image ? getSrc(postMeta.image) : `/${logo}`
postURL = `${siteUrl}${slug}`
} else {
title = `${siteTitle} ¦ ${siteDescription}`
description = siteDescription
image = `/${logo}`
}
image = `${siteUrl}${image}`
const blogURL = siteUrl
const url = post ? postURL : blogURL
return (
<MetaTags
description={description}
image={image}
url={url || ''}
postSEO={Boolean(post)}
title={title}
datePublished={post?.fields && post.fields.date}
dateModified={post?.frontmatter.updated}
/>
)
}

View File

@ -1,57 +0,0 @@
import React, { ReactElement } from 'react'
export default function schemaOrg(
blogURL: string,
title: string,
postSEO: boolean,
postURL: string,
image: string,
description: string,
author: string,
datePublished: string,
dateModified: string
): ReactElement {
const schemaOrgJSONLD: any = [
{
'@context': 'http://schema.org',
'@type': 'Blog',
url: blogURL,
name: title
}
]
if (postSEO) {
schemaOrgJSONLD.push({
'@context': 'http://schema.org',
'@type': 'BlogPosting',
author: {
'@type': 'Person',
name: author
},
publisher: {
'@type': 'Organization',
name: author
},
url: postURL,
name: title,
headline: title,
image: {
'@type': 'ImageObject',
url: image
},
description,
datePublished,
dateModified: dateModified || datePublished,
mainEntityOfPage: {
'@type': 'Blog',
'@id': blogURL
}
})
}
return (
<script type="application/ld+json">
{JSON.stringify(schemaOrgJSONLD)}
</script>
)
}

View File

@ -1,26 +1,17 @@
import { Script } from 'gatsby'
import React from 'react'
import { Helmet } from 'react-helmet'
const getTypekitScript = () => (
<script>
{`
(function(d) {
var config = {
kitId: '${process.env.GATSBY_TYPEKIT_ID}',
scriptTimeout: 3000,
async: true
},
h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
})(document);
`}
</script>
)
const script = `
(function(d) {
var config = {
kitId: '${process.env.GATSBY_TYPEKIT_ID}',
scriptTimeout: 3000,
async: true
},
h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
})(document);
`
export default function Typekit(): JSX.Element {
return (
<Helmet>
<link rel="preconnect" href="https://use.typekit.net" />
{getTypekitScript()}
</Helmet>
)
return <Script id="typekit" dangerouslySetInnerHTML={{ __html: script }} />
}

View File

@ -1,5 +1,4 @@
import React, { ReactElement, useState } from 'react'
import { Helmet } from 'react-helmet'
import React, { ReactElement, useEffect, useState } from 'react'
import { Link } from 'gatsby'
import Hamburger from '../atoms/Hamburger'
import * as styles from './Menu.module.css'
@ -13,6 +12,14 @@ export default function Menu(): ReactElement {
setMenuOpen(!menuOpen)
}
useEffect(() => {
if (menuOpen) {
document.body.classList.add('has-menu-open')
} else {
document.body.classList.remove('has-menu-open')
}
}, [menuOpen])
const MenuItems = menu.map((item) => (
<li key={item.title}>
<Link onClick={toggleMenu} to={item.link}>
@ -23,9 +30,6 @@ export default function Menu(): ReactElement {
return (
<>
<Helmet>
<html className={menuOpen ? 'has-menu-open' : undefined} lang="en" />
</Helmet>
<Hamburger onClick={toggleMenu} />
<nav className={styles.menu}>
<ul>{MenuItems}</ul>

View File

@ -1,5 +1,4 @@
import React, { useState, useEffect, ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { CSSTransition } from 'react-transition-group'
import SearchInput from './SearchInput'
import SearchButton from './SearchButton'
@ -26,6 +25,14 @@ export default function Search(): ReactElement {
)
}, [query])
useEffect(() => {
if (searchOpen) {
document.body.classList.add('hasSearchOpen')
} else {
document.body.classList.remove('hasSearchOpen')
}
}, [searchOpen])
function toggleSearch(): void {
setSearchOpen(!searchOpen)
}
@ -36,10 +43,6 @@ export default function Search(): ReactElement {
{searchOpen && (
<>
<Helmet>
<html className="hasSearchOpen" lang="en" />
</Helmet>
<CSSTransition
appear={searchOpen}
in={searchOpen}

View File

@ -14,9 +14,7 @@ describe('ThemeSwitch', () => {
const toggle = container.querySelector('input')
const label = container.querySelector('label')
expect(toggle.checked).toBeFalsy()
fireEvent.click(label)
fireEvent.change(toggle, { target: { checked: true } })
expect(toggle.checked).toBeTruthy()
})
})

View File

@ -1,71 +1,33 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import React, { ReactElement } from 'react'
import * as styles from './ThemeSwitch.module.css'
import Icon from '../atoms/Icon'
import useDarkMode from '../../hooks/useDarkMode'
const ThemeToggleInput = ({
isDark,
toggleDark
}: {
isDark: boolean
toggleDark: () => void
}) => (
<input
onChange={() => toggleDark()}
type="checkbox"
name="toggle"
value="toggle"
aria-describedby="toggle"
checked={isDark}
/>
)
const HeadMarkup = ({
bodyClass,
themeColor
}: {
bodyClass: string
themeColor: string
}) => (
<Helmet>
<body className={bodyClass} />
<meta name="theme-color" content={themeColor} />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
</Helmet>
)
export default function ThemeSwitch(): ReactElement {
const { value, toggle } = useDarkMode()
const [themeColor, setThemeColor] = useState<string>()
const [bodyClass, setBodyClass] = useState<string>()
useEffect(() => {
setBodyClass(value ? 'dark' : null)
setThemeColor(value ? '#1d2224' : '#e7eef4')
}, [value])
const { isDarkMode, setIsDarkMode } = useDarkMode()
return (
<>
<HeadMarkup themeColor={themeColor} bodyClass={bodyClass} />
<aside className={styles.themeSwitch} title="Toggle Dark Mode">
<label
htmlFor="toggle"
className={styles.checkbox}
onClick={toggle}
onKeyPress={toggle}
role="presentation"
>
<span className={styles.label}>Toggle Dark Mode</span>
<ThemeToggleInput isDark={value} toggleDark={toggle} />
<div aria-live="assertive">
{value ? <Icon name="Sun" /> : <Icon name="Moon" />}
</div>
</label>
</aside>
</>
<aside className={styles.themeSwitch} title="Toggle Dark Mode">
<label
htmlFor="toggle"
className={styles.checkbox}
onClick={() => setIsDarkMode(!isDarkMode)}
onKeyPress={() => setIsDarkMode(!isDarkMode)}
role="presentation"
>
<span className={styles.label}>Toggle Dark Mode</span>
<input
onChange={() => setIsDarkMode(!isDarkMode)}
type="checkbox"
name="toggle"
value="toggle"
aria-describedby="toggle"
checked={isDarkMode}
/>
<div aria-live="assertive">
{isDarkMode ? <Icon name="Sun" /> : <Icon name="Moon" />}
</div>
</label>
</aside>
)
}

View File

@ -5,6 +5,23 @@ import Pagination from '../molecules/Pagination'
import PostTeaser from '../molecules/PostTeaser'
import Page from './Page'
import * as styles from './Archive.module.css'
import HeadMeta, { HeadMetaProps } from '../atoms/HeadMeta'
function getMetadata(pageContext: PageContext) {
const { tag, currentPageNumber, numPages } = pageContext
const title = tag ? `#${tag}` : 'Archive'
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
const meta: Partial<HeadMetaProps> = {
title: `${title} ${paginationTitle}`,
description: 'All the articles & links.'
}
return meta
}
export default function Archive({
data,
@ -14,38 +31,25 @@ export default function Archive({
pageContext: PageContext
}): ReactElement {
const edges = data.allMarkdownRemark.edges
const { tag, currentPageNumber, numPages } = pageContext
const meta = getMetadata(pageContext)
const PostsList = edges.map(({ node }) => (
<PostTeaser key={node.id} post={node} />
))
const title = tag ? `#${tag}` : 'Archive'
const paginationTitle =
numPages > 1 && currentPageNumber > 1
? `Page ${currentPageNumber} / ${numPages}`
: ''
const page = {
frontmatter: {
title: `${title} ${paginationTitle}`,
description: 'All the articles & links.'
}
}
return (
<Page
title={page.frontmatter.title}
post={page}
pathname={pageContext.slug}
>
<Page title={meta.title}>
<div className={styles.posts}>{PostsList}</div>
{numPages > 1 && <Pagination pageContext={pageContext} />}
{pageContext.numPages > 1 && <Pagination pageContext={pageContext} />}
</Page>
)
}
export function Head({ pageContext }: { pageContext: PageContext }) {
const meta = getMetadata(pageContext)
return <HeadMeta {...meta} slug={pageContext.slug} />
}
export const archiveQuery = graphql`
query ArchiveTemplate($tag: String, $skip: Int, $limit: Int) {
allMarkdownRemark(

View File

@ -1,26 +1,17 @@
import React, { ReactElement, ReactNode } from 'react'
import { Helmet } from 'react-helmet'
import SEO, { SeoPost } from '../atoms/SEO'
import * as styles from './Page.module.css'
export default function Page({
title,
section,
children,
pathname,
post
children
}: {
title: string
children: ReactNode
pathname: string
section?: string
post?: SeoPost
}): ReactElement {
return (
<>
<Helmet title={title} />
<SEO slug={pathname} post={post} />
<h1 className={styles.pagetitle}>{title}</h1>
{section ? <section className={section}>{children}</section> : children}
</>

View File

@ -5,6 +5,7 @@ import { Image } from '../atoms/Image'
import Pagination from '../molecules/Pagination'
import Page from './Page'
import * as styles from './Photos.module.css'
import HeadMeta, { HeadMetaProps } from '../atoms/HeadMeta'
export const PhotoThumb = ({
photo
@ -37,37 +38,45 @@ function getMetadata(currentPageNumber: number, numPages: number) {
? `Page ${currentPageNumber} / ${numPages}`
: ''
return {
frontmatter: {
title: `Photos ${paginationTitle}`,
description:
'Personal photos of designer & developer Matthias Kretschmann.'
}
const meta: Partial<HeadMetaProps> = {
title: `Photos ${paginationTitle}`,
description: 'Personal photos of designer & developer Matthias Kretschmann.'
}
return meta
}
export default function Photos(props: PhotosPageProps): ReactElement {
const photos = props.data.allMarkdownRemark.edges
const { currentPageNumber, numPages } = props.pageContext
const page = getMetadata(currentPageNumber, numPages)
export default function Photos({
data,
pageContext
}: PhotosPageProps): ReactElement {
const photos = data.allMarkdownRemark.edges
const { currentPageNumber, numPages } = pageContext
const meta = getMetadata(currentPageNumber, numPages)
return (
<Page
title={page.frontmatter.title}
post={page}
pathname={props.location.pathname}
>
<Page title={meta.title}>
<section className={styles.photos}>
{photos.map(({ node }) => (
<PhotoThumb key={node.id} photo={node} />
))}
</section>
{numPages > 1 && <Pagination pageContext={props.pageContext} />}
{numPages > 1 && <Pagination pageContext={pageContext} />}
</Page>
)
}
export function Head({
pageContext
}: {
pageContext: PhotosPageProps['pageContext']
}) {
const { currentPageNumber, numPages } = pageContext
const meta = getMetadata(currentPageNumber, numPages)
return <HeadMeta {...meta} slug={pageContext.slug} />
}
export const photosQuery = graphql`
query PhotosTemplate($skip: Int, $limit: Int) {
allMarkdownRemark(

View File

@ -1,8 +1,6 @@
import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { graphql } from 'gatsby'
import Exif from '../../atoms/Exif'
import SEO from '../../atoms/SEO'
import RelatedPosts from '../../molecules/RelatedPosts'
import PostTitle from './Title'
import PostLead from './Lead'
@ -13,6 +11,10 @@ import PostMeta from './Meta'
import PrevNext from './PrevNext'
import * as styles from './index.module.css'
import { Image } from '../../atoms/Image'
import HeadMeta from '../../atoms/HeadMeta'
import { PageContext } from '../../../@types/Post'
import SchemaOrg from '../../atoms/HeadMeta/schemaOrg'
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
export default function Post({
data,
@ -25,17 +27,11 @@ export default function Post({
}
}): ReactElement {
const { post } = data
const { title, image, linkurl, style, tags, updated } = post.frontmatter
const { title, image, linkurl, tags, updated } = post.frontmatter
const { slug, githubLink, date, type } = post.fields
return (
<>
<Helmet title={title}>
{style && <link rel="stylesheet" href={style.publicURL} />}
</Helmet>
<SEO slug={slug} post={post} />
<article className={styles.hentry}>
<header>
<PostTitle
@ -77,6 +73,43 @@ export default function Post({
)
}
export function Head({
pageContext,
data
}: {
pageContext: PageContext
data: Queries.BlogPostBySlugQuery
}): ReactElement {
const { siteUrl } = useSiteMetadata()
const { excerpt, rawMarkdownBody } = data.post
const { title, image, style, updated } = data.post.frontmatter
const { date } = data.post.fields
const description = excerpt || rawMarkdownBody
return (
<HeadMeta
title={title}
description={description}
slug={pageContext.slug}
image={image}
>
<>
<SchemaOrg
post={{
title,
description,
image,
url: `${siteUrl}${pageContext.slug}`,
datePublished: date,
dateModified: updated
}}
/>
{style && <link rel="stylesheet" href={style.publicURL} />}
</>
</HeadMeta>
)
}
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
post: markdownRemark(fields: { slug: { eq: $slug } }) {

View File

@ -125,15 +125,6 @@ h6 {
transition: color 0.2s ease-out;
}
.wf-loading h1,
.wf-loading h2,
.wf-loading h3,
.wf-loading h4,
.wf-loading h5,
.wf-loading h6 {
font-weight: 600;
}
h1 .anchor.before,
h2 .anchor.before,
h3 .anchor.before,
@ -349,9 +340,6 @@ td {
}
#___gatsby {
/* // display: flex;
// min-height: 100vh;
// flex-direction: column; */
position: relative;
}

View File

@ -2,7 +2,13 @@
// adapted from
// https://github.com/daveschumaker/react-dark-mode-hook/blob/master/useDarkMode.js
//
import { useState, useEffect, useCallback } from 'react'
import {
useState,
useEffect,
useCallback,
Dispatch,
SetStateAction
} from 'react'
const isClient = typeof window === 'object'
@ -24,25 +30,41 @@ function getDarkMode() {
}
}
export default function useDarkMode(): {
value: boolean
toggle?: () => void
} {
const [darkMode, setDarkMode] = useState(getDarkMode)
export type UseDarkMode = {
isDarkMode: boolean
themeColor: string
setIsDarkMode: Dispatch<SetStateAction<boolean>>
}
function toggleDarkMode() {
setDarkMode(!darkMode)
}
export default function useDarkMode(): UseDarkMode {
const [isDarkMode, setIsDarkMode] = useState<boolean>(getDarkMode())
const [themeColor, setThemeColor] = useState<string>()
const changeTheme = useCallback(() => {
if (isDarkMode) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
setThemeColor(isDarkMode === true ? '#1d2224' : '#e7eef4')
}, [isDarkMode])
//
// Init
//
useEffect(() => {
changeTheme()
}, [changeTheme])
//
// Handle system theme change events
//
const handleChange = useCallback(() => {
setDarkMode(getDarkMode())
setIsDarkMode(getDarkMode())
}, [])
useEffect(() => {
if (!isClient || process.env.NODE_ENV === 'test') return
if (!isClient) return
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
@ -58,5 +80,5 @@ export default function useDarkMode(): {
.removeEventListener('change', handleChange)
}, [handleChange])
return { value: darkMode, toggle: toggleDarkMode }
return { isDarkMode, setIsDarkMode, themeColor }
}

View File

@ -2,28 +2,27 @@ import React, { ReactElement } from 'react'
import { Link, PageProps } from 'gatsby'
import Page from '../components/templates/Page'
import * as styles from './404.module.css'
import { SeoPost } from '../components/atoms/SEO'
import HeadMeta, { HeadMetaProps } from '../components/atoms/HeadMeta'
const page: SeoPost = {
frontmatter: {
title: '404 - Not Found'
}
const meta: Partial<HeadMetaProps> = {
title: `I'm sorry Dave`,
description: `I'm afraid I can't do that`
}
const NotFound = (props: PageProps): ReactElement => (
<Page
title={page.frontmatter?.title}
post={page}
pathname={props.location.pathname}
>
const NotFound = (): ReactElement => (
<Page title={meta.title}>
<div className={styles.hal9000} />
<div className={styles.wrapper}>
<h1 className={styles.title}>{"I'm sorry Dave"}</h1>{' '}
<p className={styles.text}>{"I'm afraid I can't do that"}</p>
<h1 className={styles.title}>{meta.title}</h1>{' '}
<p className={styles.text}>{meta.description}</p>
<Link to={'/'}>Back to homepage</Link>
</div>
</Page>
)
export default NotFound
export function Head(props: PageProps) {
return <HeadMeta {...meta} slug={props.location.pathname} />
}

View File

@ -1,6 +1,6 @@
import { graphql, PageProps } from 'gatsby'
import React, { ReactElement } from 'react'
import SEO from '../components/atoms/SEO'
import HeadMeta from '../components/atoms/HeadMeta'
import PostTeaser from '../components/molecules/PostTeaser'
import { PhotoThumb } from '../components/templates/Photos'
import PostMore from '../components/templates/Post/More'
@ -11,7 +11,6 @@ export default function Home(
): ReactElement {
return (
<>
<SEO />
<section className={styles.section}>
<div className={styles.articles}>
{props.data.latestArticles.edges.slice(0, 2).map(({ node }) => (
@ -40,6 +39,10 @@ export default function Home(
)
}
export function Head() {
return <HeadMeta slug="/" />
}
export const homeQuery = graphql`
query HomePage {
latestArticles: allMarkdownRemark(

View File

@ -3,20 +3,15 @@ import { graphql, PageProps } from 'gatsby'
import Page from '../components/templates/Page'
import Tag from '../components/atoms/Tag'
import * as styles from './tags.module.css'
import { SeoPost } from '../components/atoms/SEO'
import HeadMeta, { HeadMetaProps } from '../components/atoms/HeadMeta'
const page: SeoPost = {
frontmatter: {
title: 'Tags',
description: 'All the tags being used.'
}
const meta: Partial<HeadMetaProps> = {
title: 'Tags',
description: 'All the tags being used.'
}
const TagsPage = ({
location,
data
}: PageProps<Queries.TagsPageQuery>): ReactElement => (
<Page title={page.frontmatter.title} post={page} pathname={location.pathname}>
const TagsPage = ({ data }: PageProps<Queries.TagsPageQuery>): ReactElement => (
<Page title={meta.title}>
<ul className={styles.tags}>
{Array.from(data.allMarkdownRemark.group)
.sort((a, b) => b.totalCount - a.totalCount)
@ -36,6 +31,10 @@ const TagsPage = ({
export default TagsPage
export function Head(props: PageProps) {
return <HeadMeta {...meta} slug={props.location.pathname} />
}
export const tagsPageQuery = graphql`
query TagsPage {
allMarkdownRemark {

View File

@ -1,5 +1,4 @@
import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { useSiteMetadata } from '../hooks/use-site-metadata'
import Icon from '../components/atoms/Icon'
import * as styles from './thanks.module.css'
@ -8,6 +7,12 @@ import Copy from '../components/atoms/Copy'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiConfig } from 'wagmi'
import { chains, theme, wagmiClient } from '../helpers/rainbowkit'
import Meta, { HeadMetaProps } from '../components/atoms/HeadMeta'
import { HeadProps } from 'gatsby'
const meta: Partial<HeadMetaProps> = {
title: `Say Thanks`
}
function Coin({ address, title }: { address: string; title: string }) {
return (
@ -37,34 +42,35 @@ export default function Thanks(): ReactElement {
)
return (
<>
<Helmet>
<title>Say thanks</title>
<meta name="robots" content="noindex,nofollow" />
</Helmet>
<article className={styles.thanks}>
<BackButton />
<header>
<h1 className={styles.title}>{meta.title}</h1>
</header>
<article className={styles.thanks}>
<BackButton />
<header>
<h1 className={styles.title}>Say Thanks</h1>
</header>
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains} theme={theme}>
<Web3Donation address={author.ether} />
</RainbowKitProvider>
</WagmiConfig>
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains} theme={theme}>
<Web3Donation address={author.ether} />
</RainbowKitProvider>
</WagmiConfig>
<div className={styles.coins}>
<h3 className={styles.subTitle}>
Send Bitcoin or ERC-20 tokens from any wallet.
</h3>
<div className={styles.coins}>
<h3 className={styles.subTitle}>
Send Bitcoin or ERC-20 tokens from any wallet.
</h3>
{coins.map(([key, value]) => (
<Coin key={key} title={key} address={value} />
))}
</div>
</article>
</>
{coins.map(([key, value]) => (
<Coin key={key} title={key} address={value} />
))}
</div>
</article>
)
}
export function Head(props: HeadProps) {
return (
<Meta {...meta} slug={props.location.pathname}>
<meta name="robots" content="noindex,nofollow" />
</Meta>
)
}

Binary file not shown.