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:
parent
a0934b1165
commit
cf083d3288
27
.jest/__mocks__/matchMedia.ts
Normal file
27
.jest/__mocks__/matchMedia.ts
Normal 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
|
@ -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')
|
||||||
|
@ -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/)**.
|
||||||
|
|
||||||
|
@ -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
130
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
@ -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 = () => {
|
||||||
|
62
src/components/atoms/HeadMeta/SchemaOrg.tsx
Normal file
62
src/components/atoms/HeadMeta/SchemaOrg.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
72
src/components/atoms/HeadMeta/index.tsx
Normal file
72
src/components/atoms/HeadMeta/index.tsx
Normal 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}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
|
@ -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()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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}
|
||||||
</>
|
</>
|
||||||
|
@ -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(
|
||||||
|
@ -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 } }) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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} />
|
||||||
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
BIN
vendor/polar-0.0.6.vsix
vendored
BIN
vendor/polar-0.0.6.vsix
vendored
Binary file not shown.
Loading…
Reference in New Issue
Block a user