1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-11-22 01:46:51 +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 '@testing-library/jest-dom/extend-expect'
import './__mocks__/matchMedia'
import * as Gatsby from 'gatsby' import * as Gatsby from 'gatsby'
const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery') 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: 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/)** - **[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_ _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.
- **[iPhone 11 Pro](https://www.apple.com/iphone-11-pro/)** - **[iPhone 11 Pro](https://www.apple.com/iphone-11-pro/)**
_Space Gray, 256GB_ _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.
- **[AirPods Pro](https://www.apple.com/airpods-pro/)** - **[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.
- **[iPad Pro (12.9-inch) 2021](https://www.apple.com/ipad-pro/)** - **[iPad Pro (12.9-inch) 2021](https://www.apple.com/ipad-pro/)**
_Space Gray, 512GB, WiFi + Cellular_ _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.
- **[Logitech MX Master 3 for Mac](https://www.logitech.com/en-us/products/mice/mx-master-3-mac-wireless-mouse.910-005693.html)** - **[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_ _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.
- **[Satechi Slim X1 Bluetooth Backlit Keyboard](https://satechi.net/products/slim-x1-bluetooth-backlit-keyboard)** - **[Satechi Slim X1 Bluetooth Backlit Keyboard](https://satechi.net/products/slim-x1-bluetooth-backlit-keyboard)**
_US, Space Gray_ _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.
- **[Apple Watch Series 5](https://www.apple.com/apple-watch-series-5/)** - **[Apple Watch Series 5](https://www.apple.com/apple-watch-series-5/)**
_40mm Space Gray Aluminum Case, Black Solo Loop_ _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.
- **[Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)** - **[Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)**
_4GB RAM_ _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).
- **[Kindle Voyage](https://www.amazon.com/Amazon-Kindle-Voyage-6-Inch-4GB-eReader/dp/B00IOY8XWQ)** - **[Kindle Paperwhite 11th Gen](https://www.amazon.com/dp/B08KTZ8249)**
The best experience for long time reading. Almost all my non-technical books are Kindle books.
## Software ## 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)** - **[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. 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. 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)** - **[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)** - **[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. 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. The only messengers I use every day for personal stuff, mostly on my iPhone.
- **[Signal](https://www.signal.org)** - **[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)** - **[iCloud Backup](https://support.apple.com/en-us/HT203977)**
All mobile devices simply use this to create their backups. 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 ## 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 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 **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/)**. - 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' } parentSelector: { 'body.dark': 'Nord' }
}, },
injectStyles: false, injectStyles: false,
extensions: [ extensions: ['nord-visual-studio-code', 'polar'],
'nord-visual-studio-code',
`${__dirname}/vendor/polar-0.0.6.vsix`
],
languageAliases: {} languageAliases: {}
} }
} }
@ -217,7 +214,6 @@ const config: GatsbyConfig = {
excludes: ['/archive', '/archive/**/*', '/thanks', '/tags'] excludes: ['/archive', '/archive/**/*', '/thanks', '/tags']
} }
}, },
'gatsby-plugin-react-helmet',
'gatsby-plugin-catch-links', 'gatsby-plugin-catch-links',
'gatsby-redirect-from', 'gatsby-redirect-from',
'gatsby-plugin-meta-redirect', 'gatsby-plugin-meta-redirect',

130
package-lock.json generated
View File

@ -27,7 +27,6 @@
"gatsby-plugin-manifest": "^5.0.0", "gatsby-plugin-manifest": "^5.0.0",
"gatsby-plugin-meta-redirect": "^1.1.1", "gatsby-plugin-meta-redirect": "^1.1.1",
"gatsby-plugin-offline": "^6.0.0", "gatsby-plugin-offline": "^6.0.0",
"gatsby-plugin-react-helmet": "^6.0.0",
"gatsby-plugin-sharp": "^5.0.0", "gatsby-plugin-sharp": "^5.0.0",
"gatsby-plugin-sitemap": "^6.0.0", "gatsby-plugin-sitemap": "^6.0.0",
"gatsby-plugin-svgr": "^3.0.0-beta.0", "gatsby-plugin-svgr": "^3.0.0-beta.0",
@ -48,7 +47,6 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"rehype-react": "^7.1.1", "rehype-react": "^7.1.1",
"remark-parse": "^10.0.1", "remark-parse": "^10.0.1",
@ -68,11 +66,9 @@
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8", "@types/react-dom": "^18.0.8",
"@types/react-helmet": "^6.1.5",
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1", "@typescript-eslint/parser": "^5.42.1",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-preset-gatsby": "^3.0.0", "babel-preset-gatsby": "^3.0.0",
"eslint": "^8.27.0", "eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
@ -89,6 +85,7 @@
"node-iptc": "^1.0.5", "node-iptc": "^1.0.5",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"ora": "^6.1.2", "ora": "^6.1.2",
"polar": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"postcss": "^8.4.19", "postcss": "^8.4.19",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"stylelint": "^14.14.1", "stylelint": "^14.14.1",
@ -9721,15 +9718,6 @@
"@types/react": "*" "@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": { "node_modules/@types/react-transition-group": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", "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" "@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": { "node_modules/@wry/context": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz", "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz",
@ -18629,21 +18605,6 @@
"node": ">=10.0.0" "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": { "node_modules/gatsby-plugin-sharp": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-5.0.0.tgz", "resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-5.0.0.tgz",
@ -30359,6 +30320,15 @@
"node": ">=10.13.0" "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": { "node_modules/posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -31686,25 +31656,6 @@
"react": "^18.2.0" "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": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "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" "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": { "node_modules/react-style-singleton": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@ -45057,15 +45000,6 @@
"@types/react": "*" "@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": { "@types/react-transition-group": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", "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" "@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": { "@wry/context": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz", "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": { "gatsby-plugin-sharp": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-5.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" "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": { "posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -61521,22 +61443,6 @@
"scheduler": "^0.23.0" "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": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "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-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": { "react-style-singleton": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", "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-manifest": "^5.0.0",
"gatsby-plugin-meta-redirect": "^1.1.1", "gatsby-plugin-meta-redirect": "^1.1.1",
"gatsby-plugin-offline": "^6.0.0", "gatsby-plugin-offline": "^6.0.0",
"gatsby-plugin-react-helmet": "^6.0.0",
"gatsby-plugin-sharp": "^5.0.0", "gatsby-plugin-sharp": "^5.0.0",
"gatsby-plugin-sitemap": "^6.0.0", "gatsby-plugin-sitemap": "^6.0.0",
"gatsby-plugin-svgr": "^3.0.0-beta.0", "gatsby-plugin-svgr": "^3.0.0-beta.0",
@ -66,7 +65,6 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"rehype-react": "^7.1.1", "rehype-react": "^7.1.1",
"remark-parse": "^10.0.1", "remark-parse": "^10.0.1",
@ -86,11 +84,9 @@
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8", "@types/react-dom": "^18.0.8",
"@types/react-helmet": "^6.1.5",
"@types/react-transition-group": "^4.4.5", "@types/react-transition-group": "^4.4.5",
"@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1", "@typescript-eslint/parser": "^5.42.1",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-preset-gatsby": "^3.0.0", "babel-preset-gatsby": "^3.0.0",
"eslint": "^8.27.0", "eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
@ -107,6 +103,7 @@
"node-iptc": "^1.0.5", "node-iptc": "^1.0.5",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"ora": "^6.1.2", "ora": "^6.1.2",
"polar": "https://gitpkg.now.sh/mtyn/polar/vscode?master",
"postcss": "^8.4.19", "postcss": "^8.4.19",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"stylelint": "^14.14.1", "stylelint": "^14.14.1",

View File

@ -4,12 +4,6 @@ import Header from './organisms/Header'
import Footer from './organisms/Footer' import Footer from './organisms/Footer'
import * as styles from './Layout.module.css' 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 { export default function Layout({ children }: { children: any }): ReactElement {
return ( return (
<> <>

View File

@ -19,8 +19,7 @@ export default function ExifMap({
}: { }: {
gps: { latitude: number; longitude: number } gps: { latitude: number; longitude: number }
}): ReactElement { }): ReactElement {
const { value } = useDarkMode() const { isDarkMode } = useDarkMode()
const isDarkMode = value
const [zoom, setZoom] = useState(12) const [zoom, setZoom] = useState(12)
const zoomIn = () => { 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 React from 'react'
import { Helmet } from 'react-helmet'
const getTypekitScript = () => ( const script = `
<script> (function(d) {
{` var config = {
(function(d) { kitId: '${process.env.GATSBY_TYPEKIT_ID}',
var config = { scriptTimeout: 3000,
kitId: '${process.env.GATSBY_TYPEKIT_ID}', async: true
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);
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>
)
export default function Typekit(): JSX.Element { export default function Typekit(): JSX.Element {
return ( return <Script id="typekit" dangerouslySetInnerHTML={{ __html: script }} />
<Helmet>
<link rel="preconnect" href="https://use.typekit.net" />
{getTypekitScript()}
</Helmet>
)
} }

View File

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

View File

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

View File

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

View File

@ -1,71 +1,33 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import * as styles from './ThemeSwitch.module.css' import * as styles from './ThemeSwitch.module.css'
import Icon from '../atoms/Icon' import Icon from '../atoms/Icon'
import useDarkMode from '../../hooks/useDarkMode' 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 { export default function ThemeSwitch(): ReactElement {
const { value, toggle } = useDarkMode() const { isDarkMode, setIsDarkMode } = useDarkMode()
const [themeColor, setThemeColor] = useState<string>()
const [bodyClass, setBodyClass] = useState<string>()
useEffect(() => {
setBodyClass(value ? 'dark' : null)
setThemeColor(value ? '#1d2224' : '#e7eef4')
}, [value])
return ( return (
<> <aside className={styles.themeSwitch} title="Toggle Dark Mode">
<HeadMarkup themeColor={themeColor} bodyClass={bodyClass} /> <label
<aside className={styles.themeSwitch} title="Toggle Dark Mode"> htmlFor="toggle"
<label className={styles.checkbox}
htmlFor="toggle" onClick={() => setIsDarkMode(!isDarkMode)}
className={styles.checkbox} onKeyPress={() => setIsDarkMode(!isDarkMode)}
onClick={toggle} role="presentation"
onKeyPress={toggle} >
role="presentation" <span className={styles.label}>Toggle Dark Mode</span>
> <input
<span className={styles.label}>Toggle Dark Mode</span> onChange={() => setIsDarkMode(!isDarkMode)}
<ThemeToggleInput isDark={value} toggleDark={toggle} /> type="checkbox"
<div aria-live="assertive"> name="toggle"
{value ? <Icon name="Sun" /> : <Icon name="Moon" />} value="toggle"
</div> aria-describedby="toggle"
</label> checked={isDarkMode}
</aside> />
</> <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 PostTeaser from '../molecules/PostTeaser'
import Page from './Page' import Page from './Page'
import * as styles from './Archive.module.css' 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({ export default function Archive({
data, data,
@ -14,38 +31,25 @@ export default function Archive({
pageContext: PageContext pageContext: PageContext
}): ReactElement { }): ReactElement {
const edges = data.allMarkdownRemark.edges const edges = data.allMarkdownRemark.edges
const { tag, currentPageNumber, numPages } = pageContext const meta = getMetadata(pageContext)
const PostsList = edges.map(({ node }) => ( const PostsList = edges.map(({ node }) => (
<PostTeaser key={node.id} post={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 ( return (
<Page <Page title={meta.title}>
title={page.frontmatter.title}
post={page}
pathname={pageContext.slug}
>
<div className={styles.posts}>{PostsList}</div> <div className={styles.posts}>{PostsList}</div>
{numPages > 1 && <Pagination pageContext={pageContext} />} {pageContext.numPages > 1 && <Pagination pageContext={pageContext} />}
</Page> </Page>
) )
} }
export function Head({ pageContext }: { pageContext: PageContext }) {
const meta = getMetadata(pageContext)
return <HeadMeta {...meta} slug={pageContext.slug} />
}
export const archiveQuery = graphql` export const archiveQuery = graphql`
query ArchiveTemplate($tag: String, $skip: Int, $limit: Int) { query ArchiveTemplate($tag: String, $skip: Int, $limit: Int) {
allMarkdownRemark( allMarkdownRemark(

View File

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

View File

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

View File

@ -1,8 +1,6 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { graphql } from 'gatsby' import { graphql } from 'gatsby'
import Exif from '../../atoms/Exif' import Exif from '../../atoms/Exif'
import SEO from '../../atoms/SEO'
import RelatedPosts from '../../molecules/RelatedPosts' import RelatedPosts from '../../molecules/RelatedPosts'
import PostTitle from './Title' import PostTitle from './Title'
import PostLead from './Lead' import PostLead from './Lead'
@ -13,6 +11,10 @@ import PostMeta from './Meta'
import PrevNext from './PrevNext' import PrevNext from './PrevNext'
import * as styles from './index.module.css' import * as styles from './index.module.css'
import { Image } from '../../atoms/Image' 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({ export default function Post({
data, data,
@ -25,17 +27,11 @@ export default function Post({
} }
}): ReactElement { }): ReactElement {
const { post } = data 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 const { slug, githubLink, date, type } = post.fields
return ( return (
<> <>
<Helmet title={title}>
{style && <link rel="stylesheet" href={style.publicURL} />}
</Helmet>
<SEO slug={slug} post={post} />
<article className={styles.hentry}> <article className={styles.hentry}>
<header> <header>
<PostTitle <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` export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) { query BlogPostBySlug($slug: String!) {
post: markdownRemark(fields: { slug: { eq: $slug } }) { post: markdownRemark(fields: { slug: { eq: $slug } }) {

View File

@ -125,15 +125,6 @@ h6 {
transition: color 0.2s ease-out; 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, h1 .anchor.before,
h2 .anchor.before, h2 .anchor.before,
h3 .anchor.before, h3 .anchor.before,
@ -349,9 +340,6 @@ td {
} }
#___gatsby { #___gatsby {
/* // display: flex;
// min-height: 100vh;
// flex-direction: column; */
position: relative; position: relative;
} }

View File

@ -2,7 +2,13 @@
// adapted from // adapted from
// https://github.com/daveschumaker/react-dark-mode-hook/blob/master/useDarkMode.js // 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' const isClient = typeof window === 'object'
@ -24,25 +30,41 @@ function getDarkMode() {
} }
} }
export default function useDarkMode(): { export type UseDarkMode = {
value: boolean isDarkMode: boolean
toggle?: () => void themeColor: string
} { setIsDarkMode: Dispatch<SetStateAction<boolean>>
const [darkMode, setDarkMode] = useState(getDarkMode) }
function toggleDarkMode() { export default function useDarkMode(): UseDarkMode {
setDarkMode(!darkMode) 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 // Handle system theme change events
// //
const handleChange = useCallback(() => { const handleChange = useCallback(() => {
setDarkMode(getDarkMode()) setIsDarkMode(getDarkMode())
}, []) }, [])
useEffect(() => { useEffect(() => {
if (!isClient || process.env.NODE_ENV === 'test') return if (!isClient) return
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)') const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
@ -58,5 +80,5 @@ export default function useDarkMode(): {
.removeEventListener('change', handleChange) .removeEventListener('change', handleChange)
}, [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 { Link, PageProps } from 'gatsby'
import Page from '../components/templates/Page' import Page from '../components/templates/Page'
import * as styles from './404.module.css' import * as styles from './404.module.css'
import { SeoPost } from '../components/atoms/SEO' import HeadMeta, { HeadMetaProps } from '../components/atoms/HeadMeta'
const page: SeoPost = { const meta: Partial<HeadMetaProps> = {
frontmatter: { title: `I'm sorry Dave`,
title: '404 - Not Found' description: `I'm afraid I can't do that`
}
} }
const NotFound = (props: PageProps): ReactElement => ( const NotFound = (): ReactElement => (
<Page <Page title={meta.title}>
title={page.frontmatter?.title}
post={page}
pathname={props.location.pathname}
>
<div className={styles.hal9000} /> <div className={styles.hal9000} />
<div className={styles.wrapper}> <div className={styles.wrapper}>
<h1 className={styles.title}>{"I'm sorry Dave"}</h1>{' '} <h1 className={styles.title}>{meta.title}</h1>{' '}
<p className={styles.text}>{"I'm afraid I can't do that"}</p> <p className={styles.text}>{meta.description}</p>
<Link to={'/'}>Back to homepage</Link> <Link to={'/'}>Back to homepage</Link>
</div> </div>
</Page> </Page>
) )
export default NotFound 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 { graphql, PageProps } from 'gatsby'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import SEO from '../components/atoms/SEO' import HeadMeta from '../components/atoms/HeadMeta'
import PostTeaser from '../components/molecules/PostTeaser' import PostTeaser from '../components/molecules/PostTeaser'
import { PhotoThumb } from '../components/templates/Photos' import { PhotoThumb } from '../components/templates/Photos'
import PostMore from '../components/templates/Post/More' import PostMore from '../components/templates/Post/More'
@ -11,7 +11,6 @@ export default function Home(
): ReactElement { ): ReactElement {
return ( return (
<> <>
<SEO />
<section className={styles.section}> <section className={styles.section}>
<div className={styles.articles}> <div className={styles.articles}>
{props.data.latestArticles.edges.slice(0, 2).map(({ node }) => ( {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` export const homeQuery = graphql`
query HomePage { query HomePage {
latestArticles: allMarkdownRemark( latestArticles: allMarkdownRemark(

View File

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

View File

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

Binary file not shown.