mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +01:00
parent
8d0a900d98
commit
0aaf874538
58
.github/workflows/ci.yml
vendored
58
.github/workflows/ci.yml
vendored
@ -10,41 +10,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
|
|
||||||
- name: Cache node_modules
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: ${{ runner.os }}-node-
|
|
||||||
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm test
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: coverage
|
|
||||||
path: coverage/
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [test]
|
|
||||||
if: ${{ success() && github.actor != 'dependabot[bot]' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: coverage
|
|
||||||
- uses: paambaati/codeclimate-action@v2.7.5
|
|
||||||
env:
|
|
||||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
|
||||||
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -76,9 +41,15 @@ jobs:
|
|||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
env:
|
env:
|
||||||
GATSBY_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GATSBY_TYPEKIT_ID: ${{ secrets.GATSBY_TYPEKIT_ID }}
|
GATSBY_TYPEKIT_ID: ${{ secrets.GATSBY_TYPEKIT_ID }}
|
||||||
GATSBY_MAPBOX_ACCESS_TOKEN: ${{ secrets.GATSBY_MAPBOX_ACCESS_TOKEN }}
|
GATSBY_MAPBOX_ACCESS_TOKEN: ${{ secrets.GATSBY_MAPBOX_ACCESS_TOKEN }}
|
||||||
|
- run: npm test
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage
|
||||||
|
path: coverage/
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v1
|
- uses: actions/upload-artifact@v1
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
@ -86,8 +57,21 @@ jobs:
|
|||||||
name: public
|
name: public
|
||||||
path: public
|
path: public
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test]
|
||||||
|
if: ${{ success() && github.actor != 'dependabot[bot]' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage
|
||||||
|
- uses: paambaati/codeclimate-action@v2.7.5
|
||||||
|
env:
|
||||||
|
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
needs: build
|
needs: test
|
||||||
if: success() && github.ref == 'refs/heads/main'
|
if: success() && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ coverage
|
|||||||
.env
|
.env
|
||||||
.env.development
|
.env.development
|
||||||
.nova
|
.nova
|
||||||
|
src/@types/Gatsby.d.ts
|
@ -202,12 +202,6 @@ npm test
|
|||||||
|
|
||||||
All test files live beside the respective component. Testing setup, fixtures, and mocks can be found in `./jest.config.js` and `./jest` folder.
|
All test files live beside the respective component. Testing setup, fixtures, and mocks can be found in `./jest.config.js` and `./jest` folder.
|
||||||
|
|
||||||
For local development, run the test watcher:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test:watch
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎈 Add a new post
|
### 🎈 Add a new post
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
siteTitle: 'kremalicious',
|
siteTitle: 'kremalicious',
|
||||||
siteTitleShort: 'krlc',
|
siteTitleShort: 'krlc',
|
||||||
siteDescription: 'Blog of designer & developer Matthias Kretschmann',
|
siteDescription: 'Blog of designer & developer Matthias Kretschmann',
|
@ -1,15 +0,0 @@
|
|||||||
import './src/global/global.css'
|
|
||||||
import './src/global/imports.css'
|
|
||||||
|
|
||||||
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
|
||||||
export const wrapPageElement = wrapPageElementWithLayout
|
|
||||||
|
|
||||||
// Display a message when a service worker updates
|
|
||||||
// https://www.gatsbyjs.org/docs/add-offline-support-with-a-service-worker/#displaying-a-message-when-a-service-worker-updates
|
|
||||||
export const onServiceWorkerUpdateReady = () => {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.id = 'toast'
|
|
||||||
div.classList.add('alert', 'alert-info')
|
|
||||||
div.innerHTML = `<button onClick="window.location.reload()">This application has been updated. <span>Click to Reload</span>.</button>`
|
|
||||||
document.body.append(div)
|
|
||||||
}
|
|
18
gatsby-browser.tsx
Normal file
18
gatsby-browser.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GatsbyBrowser } from 'gatsby'
|
||||||
|
import './src/global/global.css'
|
||||||
|
import './src/global/imports.css'
|
||||||
|
|
||||||
|
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
||||||
|
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] =
|
||||||
|
wrapPageElementWithLayout
|
||||||
|
|
||||||
|
// Display a message when a service worker updates
|
||||||
|
// https://www.gatsbyjs.org/docs/add-offline-support-with-a-service-worker/#displaying-a-message-when-a-service-worker-updates
|
||||||
|
export const onServiceWorkerUpdateReady: GatsbyBrowser['onServiceWorkerUpdateReady'] =
|
||||||
|
() => {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.id = 'toast'
|
||||||
|
div.classList.add('alert', 'alert-info')
|
||||||
|
div.innerHTML = `<button onClick="window.location.reload()">This application has been updated. <span>Click to Reload</span>.</button>`
|
||||||
|
document.body.append(div)
|
||||||
|
}
|
@ -1,23 +1,30 @@
|
|||||||
require('dotenv').config()
|
import type { GatsbyConfig } from 'gatsby'
|
||||||
|
import * as dotenv from 'dotenv'
|
||||||
|
|
||||||
if (!process.env.GATSBY_GITHUB_TOKEN) {
|
dotenv.config()
|
||||||
|
|
||||||
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.warn(`
|
console.warn(`
|
||||||
|
|
||||||
⚠️ A GitHub token as GATSBY_GITHUB_TOKEN is required to build some parts of the blog.
|
⚠️ A GitHub token as GITHUB_TOKEN is required to build some parts of the blog.
|
||||||
⚠️ Check the README https://github.com/kremalicious/blog#-development.
|
⚠️ Check the README https://github.com/kremalicious/blog#-development.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const siteConfig = require('./config')
|
import siteConfig from './config'
|
||||||
const sources = require('./gatsby/sources')
|
import sources from './gatsby/sources'
|
||||||
const { feedContent } = require('./gatsby/feeds')
|
import { feedContent } from './gatsby/feeds'
|
||||||
|
|
||||||
// required for gatsby-plugin-meta-redirect
|
// required for gatsby-plugin-meta-redirect
|
||||||
require('regenerator-runtime/runtime')
|
import 'regenerator-runtime/runtime'
|
||||||
|
|
||||||
module.exports = {
|
const config: GatsbyConfig = {
|
||||||
|
graphqlTypegen: {
|
||||||
|
typesOutputPath: './src/@types/Gatsby.d.ts',
|
||||||
|
generateOnBuild: true
|
||||||
|
},
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
...siteConfig
|
...siteConfig
|
||||||
},
|
},
|
||||||
@ -169,44 +176,44 @@ module.exports = {
|
|||||||
`,
|
`,
|
||||||
feeds: [
|
feeds: [
|
||||||
{
|
{
|
||||||
serialize: ({ query: { allMarkdownRemark } }) => {
|
serialize: ({ query }: { query: Queries.AllContentFeedQuery }) => {
|
||||||
return allMarkdownRemark.edges.map((edge) => {
|
return query.allMarkdownRemark.edges.map((edge) => {
|
||||||
return Object.assign({}, edge.node.frontmatter, {
|
return Object.assign({}, edge.node.frontmatter, {
|
||||||
title: edge.node.frontmatter.title,
|
title: edge.node.frontmatter?.title,
|
||||||
date: edge.node.fields.date,
|
date: edge.node.fields?.date,
|
||||||
description: edge.node.excerpt,
|
description: edge.node.excerpt,
|
||||||
url: siteConfig.siteUrl + edge.node.fields.slug,
|
url: siteConfig.siteUrl + edge.node.fields?.slug,
|
||||||
categories: edge.node.frontmatter.tags,
|
categories: edge.node.frontmatter?.tags,
|
||||||
author: siteConfig.author.name,
|
author: siteConfig.author.name,
|
||||||
guid: siteConfig.siteUrl + edge.node.fields.slug,
|
guid: siteConfig.siteUrl + edge.node.fields?.slug,
|
||||||
custom_elements: [{ 'content:encoded': feedContent(edge) }]
|
custom_elements: [{ 'content:encoded': feedContent(edge) }]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
query: `{
|
query: `{
|
||||||
allMarkdownRemark(sort: {fields: {date: DESC}}, limit: 40) {
|
allMarkdownRemark(sort: {fields: {date: DESC}}, limit: 40) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
html
|
html
|
||||||
fields {
|
fields {
|
||||||
slug
|
slug
|
||||||
date
|
date
|
||||||
}
|
}
|
||||||
excerpt
|
excerpt
|
||||||
frontmatter {
|
frontmatter {
|
||||||
title
|
title
|
||||||
image {
|
image {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
resize(width: 940, quality: 85) {
|
resize(width: 940, quality: 85) {
|
||||||
src
|
src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}`,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
output: '/feed.xml',
|
output: '/feed.xml',
|
||||||
title: siteConfig.siteTitle
|
title: siteConfig.siteTitle
|
||||||
}
|
}
|
||||||
@ -226,3 +233,5 @@ module.exports = {
|
|||||||
'gatsby-plugin-offline'
|
'gatsby-plugin-offline'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default config
|
155
gatsby-node.js
155
gatsby-node.js
@ -1,155 +0,0 @@
|
|||||||
const { createMarkdownFields } = require('./gatsby/createMarkdownFields')
|
|
||||||
const { createExif } = require('./gatsby/createExif')
|
|
||||||
const {
|
|
||||||
generatePostPages,
|
|
||||||
generateTagPages,
|
|
||||||
generateRedirectPages,
|
|
||||||
generateArchivePages,
|
|
||||||
generatePhotosPages
|
|
||||||
} = require('./gatsby/createPages')
|
|
||||||
const { generateJsonFeed } = require('./gatsby/feeds')
|
|
||||||
|
|
||||||
exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
|
|
||||||
// Markdown files
|
|
||||||
if (node.internal.type === 'MarkdownRemark') {
|
|
||||||
createMarkdownFields(node, actions, getNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image files
|
|
||||||
if (node.internal.mediaType === 'image/jpeg') {
|
|
||||||
createExif(node, actions, createNodeId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createPages = async ({ graphql, actions, reporter }) => {
|
|
||||||
const { createPage, createRedirect } = actions
|
|
||||||
|
|
||||||
const result = await graphql(`
|
|
||||||
{
|
|
||||||
all: allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
|
||||||
edges {
|
|
||||||
next {
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node {
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previous {
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
photos: allMarkdownRemark(filter: { fields: { type: { eq: "photo" } } }) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
archive: allMarkdownRemark(
|
|
||||||
filter: { fields: { type: { nin: "photo" } } }
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tags: allMarkdownRemark {
|
|
||||||
group(field: { frontmatter: { tags: SELECT } }) {
|
|
||||||
tag: fieldValue
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
if (result.errors) {
|
|
||||||
reporter.panicOnBuild(`Error while running GraphQL query.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const all = result.data.all.edges
|
|
||||||
const photosLength = result.data.photos.edges.length
|
|
||||||
const archiveLength = result.data.archive.edges.length
|
|
||||||
const tags = result.data.tags.group
|
|
||||||
|
|
||||||
// Generate post pages
|
|
||||||
generatePostPages(createPage, all)
|
|
||||||
|
|
||||||
// Generate photos archive pages
|
|
||||||
generatePhotosPages(createPage, photosLength)
|
|
||||||
|
|
||||||
// Generate tag pages
|
|
||||||
generateTagPages(createPage, tags)
|
|
||||||
|
|
||||||
// Generate archive pages
|
|
||||||
generateArchivePages(createPage, archiveLength)
|
|
||||||
|
|
||||||
// Create manual redirects
|
|
||||||
generateRedirectPages(createRedirect)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.onPostBuild = async ({ graphql }) => {
|
|
||||||
// JSON Feed query
|
|
||||||
const result = await graphql(`
|
|
||||||
{
|
|
||||||
allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
html
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
date
|
|
||||||
}
|
|
||||||
excerpt
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
tags
|
|
||||||
updated
|
|
||||||
image {
|
|
||||||
childImageSharp {
|
|
||||||
resize(width: 940, quality: 85) {
|
|
||||||
src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
if (result.errors) throw result.errors
|
|
||||||
|
|
||||||
// Generate json feed
|
|
||||||
await generateJsonFeed(result.data.allMarkdownRemark.edges)
|
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.onCreateWebpackConfig = ({ actions }) => {
|
|
||||||
actions.setWebpackConfig({
|
|
||||||
resolve: {
|
|
||||||
fallback: {
|
|
||||||
util: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
171
gatsby-node.ts
Normal file
171
gatsby-node.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { createMarkdownFields } from './gatsby/createMarkdownFields'
|
||||||
|
import { createExif } from './gatsby/createExif'
|
||||||
|
import {
|
||||||
|
generatePostPages,
|
||||||
|
generateTagPages,
|
||||||
|
generateRedirectPages,
|
||||||
|
generateArchivePages,
|
||||||
|
generatePhotosPages
|
||||||
|
} from './gatsby/createPages'
|
||||||
|
import { generateJsonFeed } from './gatsby/feeds'
|
||||||
|
import type { GatsbyNode } from 'gatsby'
|
||||||
|
|
||||||
|
export const onCreateNode: GatsbyNode['onCreateNode'] = ({
|
||||||
|
node,
|
||||||
|
actions,
|
||||||
|
getNode,
|
||||||
|
createNodeId
|
||||||
|
}) => {
|
||||||
|
// Markdown files
|
||||||
|
if (node.internal.type === 'MarkdownRemark') {
|
||||||
|
createMarkdownFields(node, actions, getNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image files
|
||||||
|
if (node.internal.mediaType === 'image/jpeg') {
|
||||||
|
createExif(node, actions, createNodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPages: GatsbyNode['createPages'] = async ({
|
||||||
|
graphql,
|
||||||
|
actions,
|
||||||
|
reporter
|
||||||
|
}) => {
|
||||||
|
const { createPage, createRedirect } = actions
|
||||||
|
|
||||||
|
const result: { data?: Queries.AllContentQuery; errors?: any } =
|
||||||
|
await graphql(`
|
||||||
|
query AllContent {
|
||||||
|
all: allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
||||||
|
edges {
|
||||||
|
next {
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
frontmatter {
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous {
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
photos: allMarkdownRemark(
|
||||||
|
filter: { fields: { type: { eq: "photo" } } }
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
archive: allMarkdownRemark(
|
||||||
|
filter: { fields: { type: { nin: "photo" } } }
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags: allMarkdownRemark {
|
||||||
|
group(field: { frontmatter: { tags: SELECT } }) {
|
||||||
|
tag: fieldValue
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
if (result.errors) {
|
||||||
|
reporter.panicOnBuild(`Error while running GraphQL query.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const all = result?.data?.all.edges
|
||||||
|
const photosLength = result?.data?.photos.edges.length
|
||||||
|
const archiveLength = result?.data?.archive.edges.length
|
||||||
|
const tags = result?.data?.tags.group
|
||||||
|
|
||||||
|
// Generate post pages
|
||||||
|
generatePostPages(createPage, all)
|
||||||
|
|
||||||
|
// Generate photos archive pages
|
||||||
|
generatePhotosPages(createPage, photosLength)
|
||||||
|
|
||||||
|
// Generate tag pages
|
||||||
|
generateTagPages(createPage, tags)
|
||||||
|
|
||||||
|
// Generate archive pages
|
||||||
|
generateArchivePages(createPage, archiveLength)
|
||||||
|
|
||||||
|
// Create manual redirects
|
||||||
|
generateRedirectPages(createRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onPostBuild: GatsbyNode['onPostBuild'] = async ({ graphql }) => {
|
||||||
|
// JSON Feed query
|
||||||
|
const result: { data?: Queries.AllContentFeedQuery; errors?: any } =
|
||||||
|
await graphql(`
|
||||||
|
query AllContentFeed {
|
||||||
|
allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
html
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
date
|
||||||
|
}
|
||||||
|
excerpt
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
tags
|
||||||
|
updated
|
||||||
|
image {
|
||||||
|
childImageSharp {
|
||||||
|
resize(width: 940, quality: 85) {
|
||||||
|
src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
if (result.errors) throw result.errors
|
||||||
|
|
||||||
|
// Generate json feed
|
||||||
|
await generateJsonFeed(result?.data?.allMarkdownRemark.edges)
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onCreateWebpackConfig: GatsbyNode['onCreateWebpackConfig'] = ({
|
||||||
|
actions
|
||||||
|
}) => {
|
||||||
|
actions.setWebpackConfig({
|
||||||
|
resolve: {
|
||||||
|
fallback: {
|
||||||
|
util: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
|
||||||
export const wrapPageElement = wrapPageElementWithLayout
|
|
5
gatsby-ssr.tsx
Normal file
5
gatsby-ssr.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { GatsbySSR } from 'gatsby'
|
||||||
|
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
||||||
|
|
||||||
|
export const wrapPageElement: GatsbySSR['wrapPageElement'] =
|
||||||
|
wrapPageElementWithLayout
|
@ -1,43 +1,50 @@
|
|||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const util = require('util')
|
import util from 'util'
|
||||||
const fastExif = require('fast-exif')
|
import fastExif from 'fast-exif'
|
||||||
const Fraction = require('fraction.js')
|
import Fraction from 'fraction.js'
|
||||||
const getCoordinates = require('dms2dec')
|
import getCoordinates from 'dms2dec'
|
||||||
const iptc = require('node-iptc')
|
import iptc from 'node-iptc'
|
||||||
|
import type { Actions, NodePluginArgs, Node } from 'gatsby'
|
||||||
|
|
||||||
const readFile = util.promisify(fs.readFile)
|
const readFile = util.promisify(fs.readFile)
|
||||||
|
|
||||||
exports.createExif = async (node, actions, createNodeId) => {
|
export const createExif = async (
|
||||||
|
node: Node,
|
||||||
|
actions: Actions,
|
||||||
|
createNodeId: NodePluginArgs['createNodeId']
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
// exif
|
// exif
|
||||||
const exifData = await fastExif.read(node.absolutePath, true)
|
const exifData = await fastExif.read(node.absolutePath, true)
|
||||||
if (!exifData) return
|
if (!exifData) return
|
||||||
|
|
||||||
// iptc
|
// iptc
|
||||||
const file = await readFile(node.absolutePath)
|
const file = await readFile(node.absolutePath as string)
|
||||||
const iptcData = iptc(file)
|
const iptcData = iptc(file)
|
||||||
|
|
||||||
createNodes(exifData, iptcData, node, actions, createNodeId)
|
createNodes(exifData, iptcData, node, actions, createNodeId)
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(`${node.name}: ${error.message}`)
|
console.error(`${node.name}: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNodes(exifData, iptcData, node, actions, createNodeId) {
|
function createNodes(
|
||||||
|
exifData: Queries.ImageExif,
|
||||||
|
iptcData: any,
|
||||||
|
node: Node,
|
||||||
|
actions: Actions,
|
||||||
|
createNodeId: NodePluginArgs['createNodeId']
|
||||||
|
) {
|
||||||
const { createNodeField, createNode, createParentChildLink } = actions
|
const { createNodeField, createNode, createParentChildLink } = actions
|
||||||
const exifDataFormatted = formatExif(exifData)
|
const exifDataFormatted = formatExif(exifData)
|
||||||
|
|
||||||
const exif = {
|
const exif = {
|
||||||
...exifData,
|
...exifData,
|
||||||
iptc: {
|
iptc: { ...iptcData },
|
||||||
...iptcData
|
formatted: { ...exifDataFormatted }
|
||||||
},
|
|
||||||
formatted: {
|
|
||||||
...exifDataFormatted
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const exifNode = {
|
const exifNode: any = {
|
||||||
id: createNodeId(`${node.id} >> ImageExif`),
|
id: createNodeId(`${node.id} >> ImageExif`),
|
||||||
children: [],
|
children: [],
|
||||||
...exif,
|
...exif,
|
||||||
@ -49,22 +56,15 @@ function createNodes(exifData, iptcData, node, actions, createNodeId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add exif fields to existing type file
|
// add exif fields to existing type file
|
||||||
createNodeField({
|
createNodeField({ node, name: 'exif', value: exif })
|
||||||
node,
|
|
||||||
name: 'exif',
|
|
||||||
value: exif
|
|
||||||
})
|
|
||||||
|
|
||||||
// create new nodes from all exif data
|
// create new nodes from all exif data
|
||||||
// allowing to be queried with imageExif & AllImageExif
|
// allowing to be queried with imageExif & AllImageExif
|
||||||
createNode(exifNode)
|
createNode(exifNode)
|
||||||
createParentChildLink({
|
createParentChildLink({ parent: node, child: exifNode })
|
||||||
parent: node,
|
|
||||||
child: exifNode
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatExif(exifData) {
|
function formatExif(exifData: Queries.ImageExif) {
|
||||||
if (!exifData.exif) return
|
if (!exifData.exif) return
|
||||||
|
|
||||||
const { Model } = exifData.image
|
const { Model } = exifData.image
|
||||||
@ -107,8 +107,11 @@ function formatExif(exifData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatGps(gpsData) {
|
function formatGps(gpsData: Queries.ImageExif['gps']): {
|
||||||
if (!gpsData) return
|
latitude: string
|
||||||
|
longitude: string
|
||||||
|
} {
|
||||||
|
if (!gpsData) return { latitude: '', longitude: '' }
|
||||||
|
|
||||||
const { GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude } = gpsData
|
const { GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude } = gpsData
|
||||||
|
|
||||||
@ -125,7 +128,7 @@ function formatGps(gpsData) {
|
|||||||
return { latitude, longitude }
|
return { latitude, longitude }
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatExposure(exposureMode) {
|
function formatExposure(exposureMode: Queries.ImageExifExif['ExposureMode']) {
|
||||||
if (exposureMode === null || exposureMode === undefined) return
|
if (exposureMode === null || exposureMode === undefined) return
|
||||||
|
|
||||||
const exposureShortened = parseFloat(exposureMode.toFixed(2))
|
const exposureShortened = parseFloat(exposureMode.toFixed(2))
|
@ -1,62 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const { createFilePath } = require('gatsby-source-filesystem')
|
|
||||||
const { repoContentPath } = require('../config')
|
|
||||||
|
|
||||||
// Create slug, date & github file link for posts from file path values
|
|
||||||
exports.createMarkdownFields = (node, actions, getNode) => {
|
|
||||||
const { createNodeField } = actions
|
|
||||||
const fileNode = getNode(node.parent)
|
|
||||||
const parsedFilePath = path.parse(fileNode.relativePath)
|
|
||||||
const slugOriginal = createFilePath({ node, getNode })
|
|
||||||
|
|
||||||
createSlug(node, createNodeField, parsedFilePath)
|
|
||||||
createDate(node, createNodeField, slugOriginal)
|
|
||||||
|
|
||||||
// github file link
|
|
||||||
const type = fileNode.sourceInstanceName
|
|
||||||
const file = fileNode.relativePath
|
|
||||||
const githubLink = `${repoContentPath}/${type}/${file}`
|
|
||||||
|
|
||||||
createNodeField({
|
|
||||||
node,
|
|
||||||
name: 'githubLink',
|
|
||||||
value: githubLink
|
|
||||||
})
|
|
||||||
|
|
||||||
createNodeField({
|
|
||||||
node,
|
|
||||||
name: 'type',
|
|
||||||
value: type.replace('s', '')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSlug(node, createNodeField, parsedFilePath) {
|
|
||||||
let slug
|
|
||||||
|
|
||||||
if (parsedFilePath.name === 'index') {
|
|
||||||
slug = `/${parsedFilePath.dir.substring(11)}` // remove date from file dir
|
|
||||||
} else {
|
|
||||||
slug = `/${parsedFilePath.name.substring(11)}` // remove date from file path
|
|
||||||
}
|
|
||||||
|
|
||||||
createNodeField({
|
|
||||||
node,
|
|
||||||
name: 'slug',
|
|
||||||
value: slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDate(node, createNodeField, slugOriginal) {
|
|
||||||
// grab date from file path
|
|
||||||
let date = new Date(slugOriginal.substring(1, 11)).toISOString() // grab date from file path
|
|
||||||
|
|
||||||
if (node.frontmatter.date) {
|
|
||||||
date = new Date(node.frontmatter.date).toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
createNodeField({
|
|
||||||
node,
|
|
||||||
name: 'date',
|
|
||||||
value: date
|
|
||||||
})
|
|
||||||
}
|
|
72
gatsby/createMarkdownFields.ts
Normal file
72
gatsby/createMarkdownFields.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { parse } from 'path'
|
||||||
|
import { createFilePath } from 'gatsby-source-filesystem'
|
||||||
|
import config from '../config'
|
||||||
|
import { Actions, Node, NodePluginArgs } from 'gatsby'
|
||||||
|
|
||||||
|
// Create slug, date & github file link for posts from file path values
|
||||||
|
export function createMarkdownFields(
|
||||||
|
node: Node,
|
||||||
|
actions: Actions,
|
||||||
|
getNode: NodePluginArgs['getNode']
|
||||||
|
) {
|
||||||
|
const { createNodeField } = actions
|
||||||
|
const fileNode = getNode(node.parent as string)
|
||||||
|
const parsedFilePath = parse(fileNode?.relativePath as string)
|
||||||
|
const slugOriginal = createFilePath({ node, getNode })
|
||||||
|
|
||||||
|
createSlug(node, createNodeField, parsedFilePath)
|
||||||
|
createDate(node, createNodeField, slugOriginal)
|
||||||
|
|
||||||
|
// github file link
|
||||||
|
const type = fileNode?.sourceInstanceName as string
|
||||||
|
const file = fileNode?.relativePath as string
|
||||||
|
const githubLink = `${config.repoContentPath}/${type}/${file}`
|
||||||
|
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: 'githubLink',
|
||||||
|
value: githubLink
|
||||||
|
})
|
||||||
|
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: 'type',
|
||||||
|
value: type?.replace('s', '')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSlug(
|
||||||
|
node: Node,
|
||||||
|
createNodeField: Actions['createNodeField'],
|
||||||
|
parsedFilePath: { name: string; dir: string }
|
||||||
|
) {
|
||||||
|
let slug
|
||||||
|
|
||||||
|
if (parsedFilePath.name === 'index') {
|
||||||
|
slug = `/${parsedFilePath.dir.substring(11)}` // remove date from file dir
|
||||||
|
} else {
|
||||||
|
slug = `/${parsedFilePath.name.substring(11)}` // remove date from file path
|
||||||
|
}
|
||||||
|
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: 'slug',
|
||||||
|
value: slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDate(
|
||||||
|
node: Node,
|
||||||
|
createNodeField: Actions['createNodeField'],
|
||||||
|
slugOriginal: string
|
||||||
|
) {
|
||||||
|
// grab date from file path
|
||||||
|
let date = new Date(slugOriginal.substring(1, 11)).toISOString() // grab date from file path
|
||||||
|
|
||||||
|
// allow date overwrite in frontmatter
|
||||||
|
if ((node.frontmatter as any).date) {
|
||||||
|
date = new Date((node.frontmatter as any).date).toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
createNodeField({ node, name: 'date', value: date })
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
const path = require('path')
|
import path from 'path'
|
||||||
const { itemsPerPage } = require('../config')
|
import config from '../config'
|
||||||
|
import { Actions } from 'gatsby'
|
||||||
|
|
||||||
const postTemplate = path.resolve('src/components/templates/Post/index.tsx')
|
const postTemplate = path.resolve('src/components/templates/Post/index.tsx')
|
||||||
const archiveTemplate = path.resolve('src/components/templates/Archive.tsx')
|
const archiveTemplate = path.resolve('src/components/templates/Archive.tsx')
|
||||||
@ -11,7 +12,7 @@ const redirects = [
|
|||||||
{ f: '/goodies/', t: '/archive/goodies/' }
|
{ f: '/goodies/', t: '/archive/goodies/' }
|
||||||
]
|
]
|
||||||
|
|
||||||
function getPaginationData(i, numPages, slug) {
|
function getPaginationData(i: number, numPages: number, slug: string) {
|
||||||
const currentPage = i + 1
|
const currentPage = i + 1
|
||||||
const prevPageNumber = currentPage <= 1 ? null : currentPage - 1
|
const prevPageNumber = currentPage <= 1 ? null : currentPage - 1
|
||||||
const nextPageNumber = currentPage + 1 > numPages ? null : currentPage + 1
|
const nextPageNumber = currentPage + 1 > numPages ? null : currentPage + 1
|
||||||
@ -26,29 +27,38 @@ function getPaginationData(i, numPages, slug) {
|
|||||||
return { prevPagePath, nextPagePath, path }
|
return { prevPagePath, nextPagePath, path }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generatePostPages = (createPage, posts) => {
|
export const generatePostPages = (
|
||||||
|
createPage: Actions['createPage'],
|
||||||
|
posts: Queries.AllContentQuery['all']['edges'] | undefined
|
||||||
|
) => {
|
||||||
// Create Post pages
|
// Create Post pages
|
||||||
posts.forEach((post) => {
|
posts?.forEach((post) => {
|
||||||
createPage({
|
createPage({
|
||||||
path: `${post.node.fields.slug}`,
|
path: `${post.node.fields?.slug}`,
|
||||||
component: postTemplate,
|
component: postTemplate,
|
||||||
context: {
|
context: {
|
||||||
slug: post.node.fields.slug,
|
slug: post.node.fields?.slug,
|
||||||
prev: post.previous && {
|
prev: post.previous && {
|
||||||
title: post.previous.frontmatter.title,
|
title: post.previous.frontmatter?.title,
|
||||||
slug: post.previous.fields.slug
|
slug: post.previous.fields?.slug
|
||||||
},
|
},
|
||||||
next: post.next && {
|
next: post.next && {
|
||||||
title: post.next.frontmatter.title,
|
title: post.next.frontmatter?.title,
|
||||||
slug: post.next.fields.slug
|
slug: post.next.fields?.slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateIndexPages(createPage, length, slug, template, tag) {
|
function generateIndexPages(
|
||||||
const numPages = Math.ceil(length / itemsPerPage)
|
createPage: Actions['createPage'],
|
||||||
|
length: number,
|
||||||
|
slug: string,
|
||||||
|
template: string,
|
||||||
|
tag?: string
|
||||||
|
) {
|
||||||
|
const numPages = Math.ceil(length / config.itemsPerPage)
|
||||||
|
|
||||||
Array.from({ length: numPages }).forEach((_, i) => {
|
Array.from({ length: numPages }).forEach((_, i) => {
|
||||||
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
const { prevPagePath, nextPagePath, path } = getPaginationData(
|
||||||
@ -62,8 +72,8 @@ function generateIndexPages(createPage, length, slug, template, tag) {
|
|||||||
component: template,
|
component: template,
|
||||||
context: {
|
context: {
|
||||||
slug,
|
slug,
|
||||||
limit: itemsPerPage,
|
limit: config.itemsPerPage,
|
||||||
skip: i * itemsPerPage,
|
skip: i * config.itemsPerPage,
|
||||||
numPages: numPages,
|
numPages: numPages,
|
||||||
currentPageNumber: i + 1,
|
currentPageNumber: i + 1,
|
||||||
prevPagePath,
|
prevPagePath,
|
||||||
@ -75,29 +85,44 @@ function generateIndexPages(createPage, length, slug, template, tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create paginated archive pages
|
// Create paginated archive pages
|
||||||
exports.generateArchivePages = (createPage, archiveLength) => {
|
export const generateArchivePages = (
|
||||||
|
createPage: Actions['createPage'],
|
||||||
|
archiveLength: number | undefined
|
||||||
|
) => {
|
||||||
|
if (!archiveLength) return
|
||||||
generateIndexPages(createPage, archiveLength, `/archive/`, archiveTemplate)
|
generateIndexPages(createPage, archiveLength, `/archive/`, archiveTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create paginated photos pages
|
// Create paginated photos pages
|
||||||
exports.generatePhotosPages = (createPage, photosLength) => {
|
export const generatePhotosPages = (
|
||||||
|
createPage: Actions['createPage'],
|
||||||
|
photosLength: number | undefined
|
||||||
|
) => {
|
||||||
|
if (!photosLength) return
|
||||||
generateIndexPages(createPage, photosLength, `/photos/`, photosTemplate)
|
generateIndexPages(createPage, photosLength, `/photos/`, photosTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create paginated tag pages
|
// Create paginated tag pages
|
||||||
exports.generateTagPages = (createPage, tags) => {
|
export const generateTagPages = (
|
||||||
|
createPage: Actions['createPage'],
|
||||||
|
tags: Queries.AllContentQuery['tags']['group'] | undefined
|
||||||
|
) => {
|
||||||
|
if (!tags) return
|
||||||
|
|
||||||
tags.forEach(({ tag, totalCount }) => {
|
tags.forEach(({ tag, totalCount }) => {
|
||||||
generateIndexPages(
|
generateIndexPages(
|
||||||
createPage,
|
createPage,
|
||||||
totalCount,
|
totalCount,
|
||||||
`/archive/${tag}/`,
|
`/archive/${tag}/`,
|
||||||
archiveTemplate,
|
archiveTemplate,
|
||||||
tag
|
tag || ''
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateRedirectPages = (createRedirect) => {
|
export const generateRedirectPages = (
|
||||||
|
createRedirect: Actions['createRedirect']
|
||||||
|
) => {
|
||||||
redirects.forEach(({ f, t }) => {
|
redirects.forEach(({ f, t }) => {
|
||||||
createRedirect({
|
createRedirect({
|
||||||
fromPath: f,
|
fromPath: f,
|
@ -1,67 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const util = require('util')
|
|
||||||
const path = require('path')
|
|
||||||
const { siteUrl, siteTitle, siteDescription, author } = require('../config')
|
|
||||||
const writeFile = util.promisify(fs.writeFile)
|
|
||||||
|
|
||||||
const feedContent = (edge) => {
|
|
||||||
const { image } = edge.node.frontmatter
|
|
||||||
const { html } = edge.node
|
|
||||||
const footer =
|
|
||||||
'<hr />This post was published on <a href="https://kremalicious.com">kremalicious.com</a>'
|
|
||||||
|
|
||||||
return image
|
|
||||||
? `<img src="${image.childImageSharp.resize.src}" /><br />${html}${footer}`
|
|
||||||
: `${html}${footer}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function jsonItems(posts) {
|
|
||||||
return await posts.map((edge) => {
|
|
||||||
const { frontmatter, fields, excerpt } = edge.node
|
|
||||||
const { slug, date } = fields
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: path.join(siteUrl, slug),
|
|
||||||
url: path.join(siteUrl, slug),
|
|
||||||
title: frontmatter.title,
|
|
||||||
summary: excerpt,
|
|
||||||
date_published: new Date(date).toISOString(),
|
|
||||||
date_modified: frontmatter.updated
|
|
||||||
? new Date(frontmatter.updated).toISOString()
|
|
||||||
: new Date(date).toISOString(),
|
|
||||||
tags: [frontmatter.tags],
|
|
||||||
content_html: feedContent(edge)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const createJsonFeed = async (posts) => ({
|
|
||||||
version: 'https://jsonfeed.org/version/1',
|
|
||||||
title: siteTitle,
|
|
||||||
description: siteDescription,
|
|
||||||
home_page_url: siteUrl,
|
|
||||||
feed_url: path.join(siteUrl, 'feed.json'),
|
|
||||||
user_comment:
|
|
||||||
'This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — https://kremalicious.com/feed.json — and add it your reader.',
|
|
||||||
favicon: path.join(siteUrl, 'favicon.ico'),
|
|
||||||
icon: path.join(siteUrl, 'apple-touch-icon.png'),
|
|
||||||
author: {
|
|
||||||
name: author.name,
|
|
||||||
url: author.uri
|
|
||||||
},
|
|
||||||
items: await jsonItems(posts)
|
|
||||||
})
|
|
||||||
|
|
||||||
const generateJsonFeed = async (posts) => {
|
|
||||||
await writeFile(
|
|
||||||
path.join('./public', 'feed.json'),
|
|
||||||
JSON.stringify(await createJsonFeed(posts)),
|
|
||||||
'utf8'
|
|
||||||
).catch((err) => {
|
|
||||||
throw Error('\nFailed to write JSON Feed file: ', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('\nsuccess Generating JSON feed')
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { generateJsonFeed, feedContent }
|
|
77
gatsby/feeds.ts
Normal file
77
gatsby/feeds.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import util from 'util'
|
||||||
|
import path from 'path'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
const writeFile = util.promisify(fs.writeFile)
|
||||||
|
|
||||||
|
const feedContent = (
|
||||||
|
edge: Queries.AllContentFeedQuery['allMarkdownRemark']['edges'][0]
|
||||||
|
) => {
|
||||||
|
const { html, frontmatter } = edge.node
|
||||||
|
const footer =
|
||||||
|
'<hr />This post was published on <a href="https://kremalicious.com">kremalicious.com</a>'
|
||||||
|
|
||||||
|
return frontmatter?.image
|
||||||
|
? `<img src="${frontmatter?.image?.childImageSharp?.resize?.src}" /><br />${html}${footer}`
|
||||||
|
: `${html}${footer}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function jsonItems(
|
||||||
|
posts: Queries.AllContentFeedQuery['allMarkdownRemark']['edges']
|
||||||
|
) {
|
||||||
|
return posts.map((edge) => {
|
||||||
|
const { frontmatter, fields, excerpt } = edge.node
|
||||||
|
if (!fields?.slug || !fields?.date) return
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: path.join(config.siteUrl, fields.slug),
|
||||||
|
url: path.join(config.siteUrl, fields.slug),
|
||||||
|
title: frontmatter?.title,
|
||||||
|
summary: excerpt,
|
||||||
|
date_published: new Date(fields.date).toISOString(),
|
||||||
|
date_modified: frontmatter?.updated
|
||||||
|
? new Date(frontmatter?.updated).toISOString()
|
||||||
|
: new Date(fields.date).toISOString(),
|
||||||
|
tags: [frontmatter?.tags],
|
||||||
|
content_html: feedContent(edge)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createJsonFeed = async (
|
||||||
|
posts: Queries.AllContentFeedQuery['allMarkdownRemark']['edges']
|
||||||
|
) => ({
|
||||||
|
version: 'https://jsonfeed.org/version/1',
|
||||||
|
title: config.siteTitle,
|
||||||
|
description: config.siteDescription,
|
||||||
|
home_page_url: config.siteUrl,
|
||||||
|
feed_url: path.join(config.siteUrl, 'feed.json'),
|
||||||
|
user_comment:
|
||||||
|
'This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — https://kremalicious.com/feed.json — and add it your reader.',
|
||||||
|
favicon: path.join(config.siteUrl, 'favicon.ico'),
|
||||||
|
icon: path.join(config.siteUrl, 'apple-touch-icon.png'),
|
||||||
|
author: {
|
||||||
|
name: config.author.name,
|
||||||
|
url: config.author.uri
|
||||||
|
},
|
||||||
|
items: await jsonItems(posts)
|
||||||
|
})
|
||||||
|
|
||||||
|
const generateJsonFeed = async (
|
||||||
|
posts: Queries.AllContentFeedQuery['allMarkdownRemark']['edges'] | undefined
|
||||||
|
) => {
|
||||||
|
if (!posts) return
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
path.join('./public', 'feed.json'),
|
||||||
|
JSON.stringify(await createJsonFeed(posts)),
|
||||||
|
'utf8'
|
||||||
|
).catch((err) => {
|
||||||
|
throw Error('\nFailed to write JSON Feed file: ', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\nsuccess Generating JSON feed')
|
||||||
|
}
|
||||||
|
|
||||||
|
export { generateJsonFeed, feedContent }
|
@ -1,6 +1,6 @@
|
|||||||
const path = require('path')
|
import path from 'path'
|
||||||
|
|
||||||
module.exports = [
|
export default [
|
||||||
{
|
{
|
||||||
resolve: 'gatsby-source-filesystem',
|
resolve: 'gatsby-source-filesystem',
|
||||||
options: {
|
options: {
|
||||||
@ -43,7 +43,7 @@ module.exports = [
|
|||||||
fieldName: 'github',
|
fieldName: 'github',
|
||||||
url: 'https://api.github.com/graphql',
|
url: 'https://api.github.com/graphql',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `bearer ${process.env.GATSBY_GITHUB_TOKEN}`
|
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
|
||||||
}
|
}
|
||||||
// Additional options to pass to node-fetch
|
// Additional options to pass to node-fetch
|
||||||
// fetchOptions: {},
|
// fetchOptions: {},
|
2395
package-lock.json
generated
2395
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -5,19 +5,18 @@
|
|||||||
"description": "Blog of Designer & Developer Matthias Kretschmann",
|
"description": "Blog of Designer & Developer Matthias Kretschmann",
|
||||||
"homepage": "https://kremalicious.com",
|
"homepage": "https://kremalicious.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "gatsby develop --host 0.0.0.0",
|
"start": "gatsby develop --host 0.0.0.0",
|
||||||
"build": "gatsby build",
|
"build": "gatsby build",
|
||||||
"ssr": "npm run build && serve -s public/",
|
"ssr": "npm run build && serve -s public/",
|
||||||
"test": "npm run lint && jest -c .jest/jest.config.js --coverage --silent",
|
"test": "npm run lint && npm run type-check && npm run jest",
|
||||||
"test:watch": "npm run lint && jest -c .jest/jest.config.js --coverage --watch",
|
"jest": "jest -c .jest/jest.config.js --coverage --silent",
|
||||||
"lint": "run-p --continue-on-error lint:js lint:css lint:md",
|
"lint": "run-p --continue-on-error lint:js lint:css lint:md",
|
||||||
"lint:js": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.tsx .",
|
"lint:js": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.tsx .",
|
||||||
"lint:css": "stylelint 'src/**/*.css'",
|
"lint:css": "stylelint 'src/**/*.css'",
|
||||||
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git,coverage}/**/*'",
|
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git,coverage}/**/*'",
|
||||||
"format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx,md,json,css}'",
|
"format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx,md,json,css}'",
|
||||||
"tsc": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"deploy:s3": "./scripts/deploy-s3.sh",
|
"deploy:s3": "./scripts/deploy-s3.sh",
|
||||||
"new": "ts-node scripts/new.ts"
|
"new": "ts-node scripts/new.ts"
|
||||||
},
|
},
|
||||||
@ -29,16 +28,16 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kremalicious/react-feather": "^2.1.0",
|
"@kremalicious/react-feather": "^2.1.0",
|
||||||
"@rainbow-me/rainbowkit": "^0.7.0",
|
"@rainbow-me/rainbowkit": "^0.7.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^1.1.3",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"dms2dec": "^1.1.0",
|
"dms2dec": "^1.1.0",
|
||||||
"ethers": "^5.7.1",
|
"ethers": "^5.7.2",
|
||||||
"fast-exif": "^1.0.1",
|
"fast-exif": "^1.0.1",
|
||||||
"feather-icons": "^4.29.0",
|
"feather-icons": "^4.29.0",
|
||||||
"fraction.js": "^4.2.0",
|
"fraction.js": "^4.2.0",
|
||||||
"gatsby": "^5.0.0",
|
"gatsby": "^5.0.1",
|
||||||
"gatsby-plugin-catch-links": "^5.0.0",
|
"gatsby-plugin-catch-links": "^5.0.0",
|
||||||
"gatsby-plugin-feed": "^5.0.0",
|
"gatsby-plugin-feed": "^5.0.0",
|
||||||
"gatsby-plugin-image": "^3.0.0",
|
"gatsby-plugin-image": "^3.0.0",
|
||||||
@ -63,7 +62,7 @@
|
|||||||
"gatsby-transformer-remark": "^6.0.0",
|
"gatsby-transformer-remark": "^6.0.0",
|
||||||
"gatsby-transformer-sharp": "^5.0.0",
|
"gatsby-transformer-sharp": "^5.0.0",
|
||||||
"nord-visual-studio-code": "github:arcticicestudio/nord-visual-studio-code",
|
"nord-visual-studio-code": "github:arcticicestudio/nord-visual-studio-code",
|
||||||
"pigeon-maps": "^0.21.0",
|
"pigeon-maps": "^0.21.3",
|
||||||
"pigeon-marker": "^0.3.4",
|
"pigeon-marker": "^0.3.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-clipboard.js": "^2.0.16",
|
"react-clipboard.js": "^2.0.16",
|
||||||
@ -79,11 +78,11 @@
|
|||||||
"wagmi": "^0.6.6"
|
"wagmi": "^0.6.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@svgr/webpack": "^6.3.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/jest": "^29.0.3",
|
"@types/jest": "^29.2.2",
|
||||||
"@types/lunr": "^2.3.4",
|
"@types/lunr": "^2.3.4",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
"@types/react": "^18.0.25",
|
"@types/react": "^18.0.25",
|
||||||
@ -103,17 +102,17 @@
|
|||||||
"eslint-plugin-testing-library": "^5.9.1",
|
"eslint-plugin-testing-library": "^5.9.1",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.3.0",
|
"jest": "^29.3.1",
|
||||||
"jest-environment-jsdom": "^29.3.0",
|
"jest-environment-jsdom": "^29.3.1",
|
||||||
"markdownlint-cli": "^0.32.2",
|
"markdownlint-cli": "^0.32.2",
|
||||||
"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",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.19",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"stylelint": "^14.14.1",
|
"stylelint": "^14.14.1",
|
||||||
"stylelint-config-css-modules": "^4.1.0",
|
"stylelint-config-css-modules": "^4.1.0",
|
||||||
"stylelint-config-prettier": "^9.0.3",
|
"stylelint-config-prettier": "^9.0.4",
|
||||||
"stylelint-config-standard": "^29.0.0",
|
"stylelint-config-standard": "^29.0.0",
|
||||||
"stylelint-prettier": "^2.0.0",
|
"stylelint-prettier": "^2.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
25
src/@types/GitHub.d.ts
vendored
25
src/@types/GitHub.d.ts
vendored
@ -1,25 +0,0 @@
|
|||||||
export interface GitHubRepo {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
owner: {
|
|
||||||
login: string
|
|
||||||
}
|
|
||||||
object: {
|
|
||||||
id: string
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GitHub {
|
|
||||||
github: {
|
|
||||||
viewer: {
|
|
||||||
repositories: {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: GitHubRepo
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
31
src/@types/Image.d.ts
vendored
31
src/@types/Image.d.ts
vendored
@ -1,36 +1,7 @@
|
|||||||
import { GatsbyImageProps, IGatsbyImageData } from 'gatsby-plugin-image'
|
import { GatsbyImageProps } from 'gatsby-plugin-image'
|
||||||
|
|
||||||
export interface ImageProps extends GatsbyImageProps {
|
export interface ImageProps extends GatsbyImageProps {
|
||||||
title?: string
|
title?: string
|
||||||
original?: { src: string }
|
original?: { src: string }
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageNode extends IGatsbyImageData {
|
|
||||||
fields?: {
|
|
||||||
exif?: Exif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExifFormatted {
|
|
||||||
iso: string
|
|
||||||
model: string
|
|
||||||
fstop: string
|
|
||||||
shutterspeed: string
|
|
||||||
focalLength: string
|
|
||||||
lensModel: string
|
|
||||||
exposure: string
|
|
||||||
gps: {
|
|
||||||
latitude: string
|
|
||||||
longitude: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Exif {
|
|
||||||
formatted: ExifFormatted
|
|
||||||
exif?: any
|
|
||||||
image?: any
|
|
||||||
thumbnail?: any
|
|
||||||
gps?: any
|
|
||||||
iptc?: any
|
|
||||||
}
|
|
||||||
|
35
src/@types/Post.d.ts
vendored
35
src/@types/Post.d.ts
vendored
@ -1,38 +1,3 @@
|
|||||||
import { ImageNode } from './Image'
|
|
||||||
|
|
||||||
export interface Fields {
|
|
||||||
slug: string
|
|
||||||
date: string
|
|
||||||
type: 'article' | 'photo' | 'link'
|
|
||||||
githubLink?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Frontmatter {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
image?: ImageNode
|
|
||||||
author?: string
|
|
||||||
updated?: string
|
|
||||||
tags?: string[]
|
|
||||||
linkurl?: string
|
|
||||||
style?: {
|
|
||||||
publicURL?: string
|
|
||||||
}
|
|
||||||
changelog?: string
|
|
||||||
toc?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Post {
|
|
||||||
id?: string
|
|
||||||
html?: string
|
|
||||||
excerpt?: string
|
|
||||||
frontmatter: Frontmatter
|
|
||||||
fields?: Fields
|
|
||||||
rawMarkdownBody?: string
|
|
||||||
fileAbsolutePath?: string
|
|
||||||
tableOfContents?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PageContext {
|
export interface PageContext {
|
||||||
tag?: string
|
tag?: string
|
||||||
slug: string
|
slug: string
|
||||||
|
31
src/@types/Site.d.ts
vendored
31
src/@types/Site.d.ts
vendored
@ -1,31 +0,0 @@
|
|||||||
export interface MenuItem {
|
|
||||||
title: string
|
|
||||||
link: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Author {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
uri: string
|
|
||||||
twitter: string
|
|
||||||
github: string
|
|
||||||
bitcoin: string
|
|
||||||
ether: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Site {
|
|
||||||
siteTitle: string
|
|
||||||
siteTitleShort: string
|
|
||||||
siteDescription: string
|
|
||||||
siteUrl: string
|
|
||||||
author: Author
|
|
||||||
menu: MenuItem[]
|
|
||||||
rss: string
|
|
||||||
jsonfeed: string
|
|
||||||
itemsPerPage: number
|
|
||||||
repoContentPath: string
|
|
||||||
darkModeConfig: {
|
|
||||||
classNameDark: string
|
|
||||||
classNameLight: string
|
|
||||||
}
|
|
||||||
}
|
|
5
src/@types/css.d.ts
vendored
5
src/@types/css.d.ts
vendored
@ -1,4 +1 @@
|
|||||||
declare module '*.module.css' {
|
declare module '*.module.css'
|
||||||
const classes: { [key: string]: string }
|
|
||||||
export default classes
|
|
||||||
}
|
|
||||||
|
9
src/@types/node_modules.d.ts
vendored
9
src/@types/node_modules.d.ts
vendored
@ -3,3 +3,12 @@ declare module 'pigeon-marker'
|
|||||||
declare module 'unified'
|
declare module 'unified'
|
||||||
declare module 'fast-exif'
|
declare module 'fast-exif'
|
||||||
declare module 'node-iptc'
|
declare module 'node-iptc'
|
||||||
|
|
||||||
|
declare module 'dms2dec' {
|
||||||
|
export default function dms2dec(
|
||||||
|
lat: readonly number[],
|
||||||
|
latRef: string,
|
||||||
|
lon: readonly number[],
|
||||||
|
lonRef: string
|
||||||
|
): [latDec: string, lonDec: string]
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import Typekit from './atoms/Typekit'
|
import Typekit from './atoms/Typekit'
|
||||||
import Header from './organisms/Header'
|
import Header from './organisms/Header'
|
||||||
import Footer from './organisms/Footer'
|
import Footer from './organisms/Footer'
|
||||||
import { document, content } from './Layout.module.css'
|
import * as styles from './Layout.module.css'
|
||||||
|
|
||||||
// if (process.env.NODE_ENV !== 'production') {
|
// if (process.env.NODE_ENV !== 'production') {
|
||||||
// // eslint-disable-next-line
|
// // eslint-disable-next-line
|
||||||
@ -16,8 +16,8 @@ export default function Layout({ children }: { children: any }): ReactElement {
|
|||||||
<Typekit />
|
<Typekit />
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main className={document} id="document">
|
<main className={styles.document} id="document">
|
||||||
<div className={content}>{children}</div>
|
<div className={styles.content}>{children}</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
|
@ -10,20 +10,19 @@ import { unified } from 'unified'
|
|||||||
import remarkParse from 'remark-parse'
|
import remarkParse from 'remark-parse'
|
||||||
import remarkRehype from 'remark-rehype'
|
import remarkRehype from 'remark-rehype'
|
||||||
import rehypeReact from 'rehype-react'
|
import rehypeReact from 'rehype-react'
|
||||||
import { content, source } from './Changelog.module.css'
|
import * as styles from './Changelog.module.css'
|
||||||
import { GitHub, GitHubRepo } from '../../@types/GitHub'
|
|
||||||
|
|
||||||
export function PureChangelog({
|
export function PureChangelog({
|
||||||
repo,
|
repo,
|
||||||
repos
|
repos
|
||||||
}: {
|
}: {
|
||||||
repo: string
|
repo: string
|
||||||
repos: [{ node: GitHubRepo }]
|
repos: Queries.GitHubReposQuery['github']['viewer']['repositories']['edges']
|
||||||
}): ReactElement {
|
}): ReactElement | null {
|
||||||
const [changelogHtml, setChangelogHtml] = useState()
|
const [changelogHtml, setChangelogHtml] = useState()
|
||||||
|
|
||||||
const repoFilteredArray = repos
|
const repoFilteredArray = repos
|
||||||
.map(({ node }: { node: GitHubRepo }) => {
|
.map(({ node }) => {
|
||||||
if (node.name === repo) return node
|
if (node.name === repo) return node
|
||||||
})
|
})
|
||||||
.filter((n: any) => n)
|
.filter((n: any) => n)
|
||||||
@ -31,24 +30,24 @@ export function PureChangelog({
|
|||||||
const repoMatch = repoFilteredArray[0]
|
const repoMatch = repoFilteredArray[0]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!repoMatch?.object?.text) return
|
if (!(repoMatch?.object as Queries.GitHub_Blob)?.text) return
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const changelogHtml = await unified()
|
const changelogHtml = await unified()
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
.use(remarkRehype)
|
.use(remarkRehype)
|
||||||
.use(rehypeReact, { createElement, Fragment })
|
.use(rehypeReact, { createElement, Fragment })
|
||||||
.processSync(repoMatch.object.text).result
|
.processSync((repoMatch?.object as Queries.GitHub_Blob).text).result
|
||||||
|
|
||||||
setChangelogHtml(changelogHtml)
|
setChangelogHtml(changelogHtml)
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
}, [repoMatch?.object?.text])
|
}, [(repoMatch?.object as Queries.GitHub_Blob)?.text])
|
||||||
|
|
||||||
return repoMatch ? (
|
return repoMatch ? (
|
||||||
<div className={content} id="changelog">
|
<div className={styles.content} id="changelog">
|
||||||
{changelogHtml}
|
{changelogHtml}
|
||||||
<p className={source}>
|
<p className={styles.source}>
|
||||||
sourced from{' '}
|
sourced from{' '}
|
||||||
<a href={`${repoMatch?.url}/tree/main/CHANGELOG.md`}>
|
<a href={`${repoMatch?.url}/tree/main/CHANGELOG.md`}>
|
||||||
<code>{`${repoMatch?.owner.login}/${repo}:CHANGELOG.md`}</code>
|
<code>{`${repoMatch?.owner.login}/${repo}:CHANGELOG.md`}</code>
|
||||||
@ -59,7 +58,7 @@ export function PureChangelog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryGithub = graphql`
|
const queryGithub = graphql`
|
||||||
query GitHubReposInfo {
|
query GitHubRepos {
|
||||||
github {
|
github {
|
||||||
viewer {
|
viewer {
|
||||||
repositories(first: 100, privacy: PUBLIC, isFork: false) {
|
repositories(first: 100, privacy: PUBLIC, isFork: false) {
|
||||||
@ -85,7 +84,7 @@ const queryGithub = graphql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default function Changelog({ repo }: { repo: string }): ReactElement {
|
export default function Changelog({ repo }: { repo: string }): ReactElement {
|
||||||
const data: GitHub = useStaticQuery(queryGithub)
|
const data = useStaticQuery<Queries.GitHubReposQuery>(queryGithub)
|
||||||
const repos: [{ node: GitHubRepo }] = data.github.viewer.repositories.edges
|
const repos = data.github.viewer.repositories.edges
|
||||||
return <PureChangelog repo={repo} repos={repos} />
|
return <PureChangelog repo={repo} repos={repos} />
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { copied, button } from './Copy.module.css'
|
import * as styles from './Copy.module.css'
|
||||||
import Icon from './Icon'
|
import Icon from './Icon'
|
||||||
import Clipboard from 'react-clipboard.js'
|
import Clipboard from 'react-clipboard.js'
|
||||||
|
|
||||||
const onCopySuccess = (e: any) => {
|
const onCopySuccess = (e: any) => {
|
||||||
e.trigger.classList.add(copied)
|
e.trigger.classList.add(styles.copied)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Copy({ text }: { text: string }): ReactElement {
|
export default function Copy({ text }: { text: string }): ReactElement {
|
||||||
@ -13,7 +13,7 @@ export default function Copy({ text }: { text: string }): ReactElement {
|
|||||||
data-clipboard-text={text}
|
data-clipboard-text={text}
|
||||||
button-title="Copy to clipboard"
|
button-title="Copy to clipboard"
|
||||||
onSuccess={(e: ClipboardJS.Event) => onCopySuccess(e)}
|
onSuccess={(e: ClipboardJS.Event) => onCopySuccess(e)}
|
||||||
className={button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
<Icon name="Copy" />
|
<Icon name="Copy" />
|
||||||
</Clipboard>
|
</Clipboard>
|
||||||
|
@ -3,7 +3,7 @@ import { render } from '@testing-library/react'
|
|||||||
|
|
||||||
import Exif from './Exif'
|
import Exif from './Exif'
|
||||||
|
|
||||||
const exif = {
|
const exif: Partial<Queries.ImageExif> = {
|
||||||
formatted: {
|
formatted: {
|
||||||
iso: '500',
|
iso: '500',
|
||||||
model: 'Canon',
|
model: 'Canon',
|
||||||
@ -12,13 +12,13 @@ const exif = {
|
|||||||
focalLength: '200',
|
focalLength: '200',
|
||||||
lensModel: 'Hello',
|
lensModel: 'Hello',
|
||||||
exposure: '200',
|
exposure: '200',
|
||||||
gps: { latitude: '41.89007222222222', longitude: '12.491516666666666' }
|
gps: { latitude: 41.89007222222222, longitude: 12.491516666666666 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Exif', () => {
|
describe('Exif', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(<Exif exif={exif} />)
|
const { container } = render(<Exif exif={exif as Queries.ImageExif} />)
|
||||||
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import ExifMap from './ExifMap'
|
import ExifMap from './ExifMap'
|
||||||
import { exif as styleExif, data, map } from './Exif.module.css'
|
import * as styles from './Exif.module.css'
|
||||||
import { Exif as ExifMeta } from '../../@types/Image'
|
|
||||||
import Icon from './Icon'
|
import Icon from './Icon'
|
||||||
|
|
||||||
const ExifData = ({
|
const ExifData = ({
|
||||||
@ -19,15 +18,19 @@ const ExifData = ({
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default function Exif({ exif }: { exif: ExifMeta }): ReactElement {
|
export default function Exif({
|
||||||
|
exif
|
||||||
|
}: {
|
||||||
|
exif: Queries.ImageExif
|
||||||
|
}): ReactElement {
|
||||||
const { iso, model, fstop, shutterspeed, focalLength, exposure, gps } =
|
const { iso, model, fstop, shutterspeed, focalLength, exposure, gps } =
|
||||||
exif.formatted
|
exif.formatted
|
||||||
|
|
||||||
const formattedModel = model === 'FC7203' ? 'DJI Mavic Mini' : model
|
const formattedModel = model === 'FC7203' ? 'DJI Mavic Mini' : model
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={styleExif}>
|
<aside className={styles.exif}>
|
||||||
<div className={data}>
|
<div className={styles.data}>
|
||||||
{formattedModel && (
|
{formattedModel && (
|
||||||
<ExifData title="Camera model" value={formattedModel} icon="Camera" />
|
<ExifData title="Camera model" value={formattedModel} icon="Camera" />
|
||||||
)}
|
)}
|
||||||
@ -45,8 +48,8 @@ export default function Exif({ exif }: { exif: ExifMeta }): ReactElement {
|
|||||||
{exposure && <ExifData title="Exposure" value={exposure} icon="Sun" />}
|
{exposure && <ExifData title="Exposure" value={exposure} icon="Sun" />}
|
||||||
{iso && <ExifData title="ISO" value={iso} icon="Maximize" />}
|
{iso && <ExifData title="ISO" value={iso} icon="Maximize" />}
|
||||||
</div>
|
</div>
|
||||||
{gps && gps.latitude && (
|
{gps?.latitude && (
|
||||||
<div className={map}>
|
<div className={styles.map}>
|
||||||
<ExifMap gps={gps} />
|
<ExifMap gps={gps} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -17,7 +17,7 @@ const providers = {
|
|||||||
export default function ExifMap({
|
export default function ExifMap({
|
||||||
gps
|
gps
|
||||||
}: {
|
}: {
|
||||||
gps: { latitude: string; longitude: string }
|
gps: { latitude: number; longitude: number }
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { value } = useDarkMode()
|
const { value } = useDarkMode()
|
||||||
const isDarkMode = value
|
const isDarkMode = value
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { button, hamburger, line } from './Hamburger.module.css'
|
import * as styles from './Hamburger.module.css'
|
||||||
|
|
||||||
export default function Hamburger({
|
export default function Hamburger({
|
||||||
onClick
|
onClick
|
||||||
@ -7,11 +7,16 @@ export default function Hamburger({
|
|||||||
onClick(): void
|
onClick(): void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<button type="button" title="Menu" className={button} onClick={onClick}>
|
<button
|
||||||
<span className={hamburger}>
|
type="button"
|
||||||
<span className={line} />
|
title="Menu"
|
||||||
<span className={line} />
|
className={styles.button}
|
||||||
<span className={line} />
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<span className={styles.hamburger}>
|
||||||
|
<span className={styles.line} />
|
||||||
|
<span className={styles.line} />
|
||||||
|
<span className={styles.line} />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
import { ReactComponent as Jsonfeed } from '../../images/jsonfeed.svg'
|
import { ReactComponent as Jsonfeed } from '../../images/jsonfeed.svg'
|
||||||
import { ReactComponent as Bitcoin } from '../../images/bitcoin.svg'
|
import { ReactComponent as Bitcoin } from '../../images/bitcoin.svg'
|
||||||
import { ReactComponent as Stopwatch } from '../../images/stopwatch.svg'
|
import { ReactComponent as Stopwatch } from '../../images/stopwatch.svg'
|
||||||
import { icon } from './Icon.module.css'
|
import * as styles from './Icon.module.css'
|
||||||
|
|
||||||
const components: {
|
const components: {
|
||||||
[key: string]: FunctionComponent<React.SVGProps<SVGSVGElement>>
|
[key: string]: FunctionComponent<React.SVGProps<SVGSVGElement>>
|
||||||
@ -62,7 +62,7 @@ const Icon = ({ name, ...props }: { name: string }): ReactElement => {
|
|||||||
// const IconFeather = (Feather as any)[name]
|
// const IconFeather = (Feather as any)[name]
|
||||||
if (!IconMapped) return null
|
if (!IconMapped) return null
|
||||||
|
|
||||||
return <IconMapped className={icon} {...props} />
|
return <IconMapped className={styles.icon} {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Icon
|
export default Icon
|
||||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import { graphql } from 'gatsby'
|
import { graphql } from 'gatsby'
|
||||||
import { GatsbyImage } from 'gatsby-plugin-image'
|
import { GatsbyImage } from 'gatsby-plugin-image'
|
||||||
import { ImageProps } from '../../@types/Image'
|
import { ImageProps } from '../../@types/Image'
|
||||||
import { image as styleImage, imageTitle } from './Image.module.css'
|
import * as styles from './Image.module.css'
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
title,
|
title,
|
||||||
@ -12,11 +12,11 @@ export const Image = ({
|
|||||||
className
|
className
|
||||||
}: ImageProps): ReactElement => (
|
}: ImageProps): ReactElement => (
|
||||||
<figure
|
<figure
|
||||||
className={`${styleImage} ${className ? className : ''}`}
|
className={`${styles.image} ${className ? className : ''}`}
|
||||||
data-original={original?.src}
|
data-original={original?.src}
|
||||||
>
|
>
|
||||||
<GatsbyImage image={image} alt={alt} objectFit="contain" />
|
<GatsbyImage image={image} alt={alt} objectFit="contain" />
|
||||||
{title && <figcaption className={imageTitle}>{title}</figcaption>}
|
{title && <figcaption className={styles.imageTitle}>{title}</figcaption>}
|
||||||
</figure>
|
</figure>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { ReactElement, InputHTMLAttributes } from 'react'
|
import React, { ReactElement, InputHTMLAttributes } from 'react'
|
||||||
import { input } from './Input.module.css'
|
import * as styles from './Input.module.css'
|
||||||
|
|
||||||
export default function Input({
|
export default function Input({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: InputHTMLAttributes<HTMLInputElement>): ReactElement {
|
}: InputHTMLAttributes<HTMLInputElement>): ReactElement {
|
||||||
return <input className={`${input} ${className || ''}`} {...props} />
|
return <input className={`${styles.input} ${className || ''}`} {...props} />
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,10 @@ import React, { ReactElement } from 'react'
|
|||||||
import { graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import { getSrc } from 'gatsby-plugin-image'
|
import { getSrc } from 'gatsby-plugin-image'
|
||||||
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
||||||
import { Post } from '../../../@types/Post'
|
|
||||||
import MetaTags from './MetaTags'
|
import MetaTags from './MetaTags'
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query Logo {
|
||||||
logo: allFile(filter: { name: { eq: "apple-touch-icon" } }) {
|
logo: allFile(filter: { name: { eq: "apple-touch-icon" } }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -17,25 +16,36 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export interface SeoPost {
|
||||||
|
frontmatter: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
image?: any
|
||||||
|
updated?: string
|
||||||
|
}
|
||||||
|
fields?: {
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
excerpt?: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function SEO({
|
export default function SEO({
|
||||||
post,
|
post,
|
||||||
slug,
|
slug
|
||||||
postSEO
|
|
||||||
}: {
|
}: {
|
||||||
post?: Post
|
post?: SeoPost
|
||||||
slug?: string
|
slug?: string
|
||||||
postSEO?: boolean
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery<Queries.LogoQuery>(query)
|
||||||
const logo = data.logo.edges[0].node.relativePath
|
const logo = data.logo.edges[0].node.relativePath
|
||||||
const { siteTitle, siteUrl, siteDescription } = useSiteMetadata()
|
const { siteTitle, siteUrl, siteDescription } = useSiteMetadata()
|
||||||
|
|
||||||
let title
|
let title: string
|
||||||
let description
|
let description: string
|
||||||
let image
|
let image: string
|
||||||
let postURL
|
let postURL: string
|
||||||
|
|
||||||
if (postSEO) {
|
if (post) {
|
||||||
const postMeta = post.frontmatter
|
const postMeta = post.frontmatter
|
||||||
title = `${postMeta.title} ¦ ${siteTitle}`
|
title = `${postMeta.title} ¦ ${siteTitle}`
|
||||||
description = postMeta.description ? postMeta.description : post.excerpt
|
description = postMeta.description ? postMeta.description : post.excerpt
|
||||||
@ -49,17 +59,17 @@ export default function SEO({
|
|||||||
|
|
||||||
image = `${siteUrl}${image}`
|
image = `${siteUrl}${image}`
|
||||||
const blogURL = siteUrl
|
const blogURL = siteUrl
|
||||||
const url = postSEO ? postURL : blogURL
|
const url = post ? postURL : blogURL
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MetaTags
|
<MetaTags
|
||||||
description={description}
|
description={description}
|
||||||
image={image}
|
image={image}
|
||||||
url={url}
|
url={url || ''}
|
||||||
postSEO={postSEO}
|
postSEO={Boolean(post)}
|
||||||
title={title}
|
title={title}
|
||||||
datePublished={post && post.fields && post.fields.date}
|
datePublished={post?.fields && post.fields.date}
|
||||||
dateModified={post && post.frontmatter.updated}
|
dateModified={post?.frontmatter.updated}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import { tag, count as styleCount } from './Tag.module.css'
|
import * as styles from './Tag.module.css'
|
||||||
|
|
||||||
export default function Tag({
|
export default function Tag({
|
||||||
name,
|
name,
|
||||||
@ -14,9 +14,9 @@ export default function Tag({
|
|||||||
style?: any
|
style?: any
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Link className={tag} to={url} style={style}>
|
<Link className={styles.tag} to={url} style={style}>
|
||||||
{name}
|
{name}
|
||||||
{count && <span className={styleCount}>{count}</span>}
|
{count && <span className={styles.count}>{count}</span>}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,8 @@ import React, { ReactElement, useState } from 'react'
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import Hamburger from '../atoms/Hamburger'
|
import Hamburger from '../atoms/Hamburger'
|
||||||
import { menu as styleMenu } from './Menu.module.css'
|
import * as styles from './Menu.module.css'
|
||||||
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
||||||
import { MenuItem } from '../../@types/Site'
|
|
||||||
|
|
||||||
export default function Menu(): ReactElement {
|
export default function Menu(): ReactElement {
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
@ -14,15 +13,13 @@ export default function Menu(): ReactElement {
|
|||||||
setMenuOpen(!menuOpen)
|
setMenuOpen(!menuOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItems = menu.map(
|
const MenuItems = menu.map((item) => (
|
||||||
(item: MenuItem): JSX.Element => (
|
<li key={item.title}>
|
||||||
<li key={item.title}>
|
<Link onClick={toggleMenu} to={item.link}>
|
||||||
<Link onClick={toggleMenu} to={item.link}>
|
{item.title}
|
||||||
{item.title}
|
</Link>
|
||||||
</Link>
|
</li>
|
||||||
</li>
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -30,7 +27,7 @@ export default function Menu(): ReactElement {
|
|||||||
<html className={menuOpen ? 'has-menu-open' : undefined} lang="en" />
|
<html className={menuOpen ? 'has-menu-open' : undefined} lang="en" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Hamburger onClick={toggleMenu} />
|
<Hamburger onClick={toggleMenu} />
|
||||||
<nav className={styleMenu}>
|
<nav className={styles.menu}>
|
||||||
<ul>{MenuItems}</ul>
|
<ul>{MenuItems}</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Icon from '../atoms/Icon'
|
import Icon from '../atoms/Icon'
|
||||||
import { link as styleLink } from './Networks.module.css'
|
import * as styles from './Networks.module.css'
|
||||||
|
|
||||||
function NetworkIcon({ link }: { link: string }) {
|
function NetworkIcon({ link }: { link: string }) {
|
||||||
let IconComp
|
let IconComp
|
||||||
@ -28,7 +28,7 @@ export default function IconLinks({
|
|||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{links.map((link: string) => (
|
{links.map((link: string) => (
|
||||||
<a key={link} className={styleLink} href={link} title={link}>
|
<a key={link} className={styles.link} href={link} title={link}>
|
||||||
<NetworkIcon link={link} />
|
<NetworkIcon link={link} />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
@ -2,11 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import { PageContext } from '../../@types/Post'
|
import { PageContext } from '../../@types/Post'
|
||||||
import Icon from '../atoms/Icon'
|
import Icon from '../atoms/Icon'
|
||||||
import {
|
import * as styles from './Pagination.module.css'
|
||||||
current as styleCurrent,
|
|
||||||
number as styleNumber,
|
|
||||||
pagination
|
|
||||||
} from './Pagination.module.css'
|
|
||||||
|
|
||||||
function PageNumber({
|
function PageNumber({
|
||||||
i,
|
i,
|
||||||
@ -17,7 +13,7 @@ function PageNumber({
|
|||||||
slug: string
|
slug: string
|
||||||
current?: boolean
|
current?: boolean
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const classes = current ? styleCurrent : styleNumber
|
const classes = current ? styles.current : styles.number
|
||||||
const link = i === 0 ? slug : `${slug}page/${i + 1}`
|
const link = i === 0 ? slug : `${slug}page/${i + 1}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -39,7 +35,7 @@ function PrevNext({
|
|||||||
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
|
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link} rel={rel} title={title} className={styleNumber}>
|
<Link to={link} rel={rel} title={title} className={styles.number}>
|
||||||
{prevPagePath ? (
|
{prevPagePath ? (
|
||||||
<Icon name="ChevronLeft" />
|
<Icon name="ChevronLeft" />
|
||||||
) : (
|
) : (
|
||||||
@ -60,7 +56,7 @@ export default function Pagination({
|
|||||||
const isLast = currentPageNumber === numPages
|
const isLast = currentPageNumber === numPages
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={pagination}>
|
<div className={styles.pagination}>
|
||||||
{!isFirst && <PrevNext prevPagePath={prevPagePath} />}
|
{!isFirst && <PrevNext prevPagePath={prevPagePath} />}
|
||||||
{Array.from({ length: numPages }, (_, i) => (
|
{Array.from({ length: numPages }, (_, i) => (
|
||||||
<PageNumber
|
<PageNumber
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Time from '../atoms/Time'
|
import Time from '../atoms/Time'
|
||||||
import { time } from './PostDate.module.css'
|
import * as styles from './PostDate.module.css'
|
||||||
|
|
||||||
export default function PostDate({
|
export default function PostDate({
|
||||||
date,
|
date,
|
||||||
@ -10,7 +10,7 @@ export default function PostDate({
|
|||||||
updated?: string
|
updated?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className={time}>
|
<div className={styles.time}>
|
||||||
<Time date={date} />
|
<Time date={date} />
|
||||||
{updated && ' • updated '}
|
{updated && ' • updated '}
|
||||||
{updated && <Time date={updated} />}
|
{updated && <Time date={updated} />}
|
||||||
|
@ -5,9 +5,16 @@ import post from '../../../.jest/__fixtures__/post.json'
|
|||||||
|
|
||||||
describe('PostTeaser', () => {
|
describe('PostTeaser', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const { container, rerender } = render(<PostTeaser post={post.post} />)
|
const { container, rerender } = render(
|
||||||
|
<PostTeaser post={post.post as unknown as Queries.PostTeaserFragment} />
|
||||||
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
|
||||||
rerender(<PostTeaser post={post.post} toggleSearch={() => null} />)
|
rerender(
|
||||||
|
<PostTeaser
|
||||||
|
post={post.post as unknown as Queries.PostTeaserFragment}
|
||||||
|
toggleSearch={() => null}
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link, graphql } from 'gatsby'
|
import { Link, graphql } from 'gatsby'
|
||||||
import { Image } from '../atoms/Image'
|
import { Image } from '../atoms/Image'
|
||||||
import { Post } from '../../@types/Post'
|
|
||||||
import PostTitle from '../templates/Post/Title'
|
import PostTitle from '../templates/Post/Title'
|
||||||
import {
|
import * as styles from './PostTeaser.module.css'
|
||||||
post as stylePost,
|
|
||||||
empty,
|
|
||||||
title as styleTitle
|
|
||||||
} from './PostTeaser.module.css'
|
|
||||||
|
|
||||||
export const postTeaserQuery = graphql`
|
export const postTeaserQuery = graphql`
|
||||||
fragment PostTeaser on MarkdownRemark {
|
fragment PostTeaser on MarkdownRemark {
|
||||||
@ -37,7 +32,7 @@ export default function PostTeaser({
|
|||||||
toggleSearch,
|
toggleSearch,
|
||||||
hideDate
|
hideDate
|
||||||
}: {
|
}: {
|
||||||
post: Partial<Post>
|
post: Queries.PostTeaserFragment
|
||||||
toggleSearch?: () => void
|
toggleSearch?: () => void
|
||||||
hideDate?: boolean
|
hideDate?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
@ -46,7 +41,7 @@ export default function PostTeaser({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={stylePost}
|
className={styles.post}
|
||||||
to={slug}
|
to={slug}
|
||||||
onClick={toggleSearch && toggleSearch}
|
onClick={toggleSearch && toggleSearch}
|
||||||
>
|
>
|
||||||
@ -56,14 +51,14 @@ export default function PostTeaser({
|
|||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className={empty} />
|
<span className={styles.empty} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PostTitle
|
<PostTitle
|
||||||
title={title}
|
title={title}
|
||||||
date={hideDate ? null : date}
|
date={hideDate ? null : date}
|
||||||
updated={updated}
|
updated={updated}
|
||||||
className={styleTitle}
|
className={styles.title}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useState } from 'react'
|
||||||
import { graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import PostTeaser from './PostTeaser'
|
import PostTeaser from './PostTeaser'
|
||||||
import { relatedPosts, title, button } from './RelatedPosts.module.css'
|
import * as styles from './RelatedPosts.module.css'
|
||||||
import { Post, Frontmatter } from '../../@types/Post'
|
|
||||||
import { PhotoThumb } from '../templates/Photos'
|
import { PhotoThumb } from '../templates/Photos'
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
{
|
query RelatedPosts {
|
||||||
allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
allMarkdownRemark(sort: { fields: { date: DESC } }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -18,12 +17,12 @@ const query = graphql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
function postsWithDataFilter(
|
function postsWithDataFilter(
|
||||||
posts: [{ node: Post }],
|
posts: Queries.RelatedPostsQuery['allMarkdownRemark']['edges'],
|
||||||
key: keyof Frontmatter,
|
key: keyof Queries.MarkdownRemarkFrontmatter,
|
||||||
valuesToFind: string[]
|
valuesToFind: string[]
|
||||||
): { node: Post }[] {
|
) {
|
||||||
const newArray = posts
|
const newArray = posts
|
||||||
.filter(({ node }: { node: Post }) => {
|
.filter(({ node }) => {
|
||||||
const frontmatterKey = node.frontmatter[key] as []
|
const frontmatterKey = node.frontmatter[key] as []
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -39,9 +38,11 @@ function postsWithDataFilter(
|
|||||||
return newArray
|
return newArray
|
||||||
}
|
}
|
||||||
|
|
||||||
function photosWithDataFilter(posts: [{ node: Post }]): { node: Post }[] {
|
function photosWithDataFilter(
|
||||||
|
posts: Queries.RelatedPostsQuery['allMarkdownRemark']['edges']
|
||||||
|
) {
|
||||||
const newArray = posts
|
const newArray = posts
|
||||||
.filter((post: { node: Post }) => {
|
.filter((post) => {
|
||||||
const { fileAbsolutePath } = post.node
|
const { fileAbsolutePath } = post.node
|
||||||
|
|
||||||
if (fileAbsolutePath.includes('content/photos')) {
|
if (fileAbsolutePath.includes('content/photos')) {
|
||||||
@ -61,7 +62,7 @@ export default function RelatedPosts({
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
isPhotos?: boolean
|
isPhotos?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery<Queries.RelatedPostsQuery>(query)
|
||||||
const posts = data.allMarkdownRemark.edges
|
const posts = data.allMarkdownRemark.edges
|
||||||
|
|
||||||
function getPosts() {
|
function getPosts() {
|
||||||
@ -78,15 +79,15 @@ export default function RelatedPosts({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={relatedPosts}>
|
<aside className={styles.relatedPosts}>
|
||||||
<h1 className={title}>
|
<h1 className={styles.title}>
|
||||||
Related {isPhotos ? 'Photos' : 'Posts'}{' '}
|
Related {isPhotos ? 'Photos' : 'Posts'}{' '}
|
||||||
<button className={button} onClick={() => refreshPosts()}>
|
<button className={styles.button} onClick={() => refreshPosts()}>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
</h1>
|
</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{filteredPosts?.map(({ node }: { node: Post }) => (
|
{filteredPosts?.map(({ node }) => (
|
||||||
<li key={node.id}>
|
<li key={node.id}>
|
||||||
{isPhotos ? (
|
{isPhotos ? (
|
||||||
<PhotoThumb photo={node} />
|
<PhotoThumb photo={node} />
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { searchButton } from './SearchButton.module.css'
|
import * as styles from './SearchButton.module.css'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
|
|
||||||
const SearchButton = ({ onClick }: { onClick: () => void }): ReactElement => (
|
const SearchButton = ({ onClick }: { onClick: () => void }): ReactElement => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Search"
|
title="Search"
|
||||||
className={searchButton}
|
className={styles.searchButton}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Icon name="Search" />
|
<Icon name="Search" />
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ChangeEvent, ReactElement } from 'react'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
import { searchInput, searchInputClose } from './SearchInput.module.css'
|
import * as styles from './SearchInput.module.css'
|
||||||
|
|
||||||
export default function SearchInput({
|
export default function SearchInput({
|
||||||
value,
|
value,
|
||||||
@ -10,12 +10,12 @@ export default function SearchInput({
|
|||||||
}: {
|
}: {
|
||||||
value: string
|
value: string
|
||||||
onToggle(): void
|
onToggle(): void
|
||||||
onChange(e: Event): void
|
onChange(e: ChangeEvent<HTMLInputElement>): void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
className={searchInput}
|
className={styles.searchInput}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search everything"
|
placeholder="Search everything"
|
||||||
autoFocus // eslint-disable-line
|
autoFocus // eslint-disable-line
|
||||||
@ -23,7 +23,7 @@ export default function SearchInput({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className={searchInputClose}
|
className={styles.searchInputClose}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
title="Close search"
|
title="Close search"
|
||||||
>
|
>
|
||||||
|
@ -3,18 +3,14 @@ import ReactDOM from 'react-dom'
|
|||||||
import { graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import PostTeaser from '../PostTeaser'
|
import PostTeaser from '../PostTeaser'
|
||||||
import SearchResultsEmpty from './SearchResultsEmpty'
|
import SearchResultsEmpty from './SearchResultsEmpty'
|
||||||
import {
|
import * as styles from './SearchResults.module.css'
|
||||||
searchResults,
|
|
||||||
results as styleResults
|
|
||||||
} from './SearchResults.module.css'
|
|
||||||
import { Post } from '../../../@types/Post'
|
|
||||||
|
|
||||||
export interface Results {
|
export interface Results {
|
||||||
slug: string
|
slug: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query SearchResults {
|
||||||
allMarkdownRemark {
|
allMarkdownRemark {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -31,21 +27,19 @@ function SearchResultsPure({
|
|||||||
toggleSearch,
|
toggleSearch,
|
||||||
posts
|
posts
|
||||||
}: {
|
}: {
|
||||||
posts: [{ node: Post }]
|
posts: Queries.SearchResultsQuery['allMarkdownRemark']['edges']
|
||||||
searchQuery: string
|
searchQuery: string
|
||||||
results: Results[]
|
results: Results[]
|
||||||
toggleSearch(): void
|
toggleSearch(): void
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={searchResults}>
|
<div className={styles.searchResults}>
|
||||||
{results.length > 0 ? (
|
{results.length > 0 ? (
|
||||||
<ul className={styleResults}>
|
<ul className={styles.results}>
|
||||||
{results.map((page: { slug: string }) =>
|
{results.map((page: { slug: string }) =>
|
||||||
posts
|
posts
|
||||||
.filter(
|
.filter(({ node }) => node.fields.slug === page.slug)
|
||||||
({ node }: { node: Post }) => node.fields.slug === page.slug
|
.map(({ node }) => (
|
||||||
)
|
|
||||||
.map(({ node }: { node: Post }) => (
|
|
||||||
<li key={page.slug}>
|
<li key={page.slug}>
|
||||||
<PostTeaser post={node} toggleSearch={toggleSearch} />
|
<PostTeaser post={node} toggleSearch={toggleSearch} />
|
||||||
</li>
|
</li>
|
||||||
@ -68,7 +62,7 @@ export default function SearchResults({
|
|||||||
results: Results[]
|
results: Results[]
|
||||||
toggleSearch(): void
|
toggleSearch(): void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery<Queries.SearchResultsQuery>(query)
|
||||||
const posts = data.allMarkdownRemark.edges
|
const posts = data.allMarkdownRemark.edges
|
||||||
|
|
||||||
// creating portal to break out of DOM node we're in
|
// creating portal to break out of DOM node we're in
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import {
|
import * as styles from './SearchResultsEmpty.module.css'
|
||||||
empty,
|
|
||||||
emptyMessage,
|
|
||||||
emptyMessageText
|
|
||||||
} from './SearchResultsEmpty.module.css'
|
|
||||||
import { Results } from './SearchResults'
|
import { Results } from './SearchResults'
|
||||||
|
|
||||||
const SearchResultsEmpty = ({
|
const SearchResultsEmpty = ({
|
||||||
@ -13,9 +9,9 @@ const SearchResultsEmpty = ({
|
|||||||
searchQuery: string
|
searchQuery: string
|
||||||
results: Results[]
|
results: Results[]
|
||||||
}): ReactElement => (
|
}): ReactElement => (
|
||||||
<div className={empty}>
|
<div className={styles.empty}>
|
||||||
<header className={emptyMessage}>
|
<header className={styles.emptyMessage}>
|
||||||
<p className={emptyMessageText}>
|
<p className={styles.emptyMessageText}>
|
||||||
{searchQuery.length > 0 && results.length === 0
|
{searchQuery.length > 0 && results.length === 0
|
||||||
? 'No results found'
|
? 'No results found'
|
||||||
: 'Awaiting your input'}
|
: 'Awaiting your input'}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { themeSwitch, checkbox, label } 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'
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ const HeadMarkup = ({
|
|||||||
|
|
||||||
export default function ThemeSwitch(): ReactElement {
|
export default function ThemeSwitch(): ReactElement {
|
||||||
const { value, toggle } = useDarkMode()
|
const { value, toggle } = useDarkMode()
|
||||||
const [themeColor, setThemeColor] = useState('')
|
const [themeColor, setThemeColor] = useState<string>()
|
||||||
const [bodyClass, setBodyClass] = useState('')
|
const [bodyClass, setBodyClass] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBodyClass(value ? 'dark' : null)
|
setBodyClass(value ? 'dark' : null)
|
||||||
@ -51,15 +51,15 @@ export default function ThemeSwitch(): ReactElement {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeadMarkup themeColor={themeColor} bodyClass={bodyClass} />
|
<HeadMarkup themeColor={themeColor} bodyClass={bodyClass} />
|
||||||
<aside className={themeSwitch} title="Toggle Dark Mode">
|
<aside className={styles.themeSwitch} title="Toggle Dark Mode">
|
||||||
<label
|
<label
|
||||||
htmlFor="toggle"
|
htmlFor="toggle"
|
||||||
className={checkbox}
|
className={styles.checkbox}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
onKeyPress={toggle}
|
onKeyPress={toggle}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<span className={label}>Toggle Dark Mode</span>
|
<span className={styles.label}>Toggle Dark Mode</span>
|
||||||
<ThemeToggleInput isDark={value} toggleDark={toggle} />
|
<ThemeToggleInput isDark={value} toggleDark={toggle} />
|
||||||
<div aria-live="assertive">
|
<div aria-live="assertive">
|
||||||
{value ? <Icon name="Sun" /> : <Icon name="Moon" />}
|
{value ? <Icon name="Sun" /> : <Icon name="Moon" />}
|
||||||
|
@ -2,11 +2,11 @@ import React, { ReactElement } from 'react'
|
|||||||
import { graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import { getSrc } from 'gatsby-plugin-image'
|
import { getSrc } from 'gatsby-plugin-image'
|
||||||
import IconLinks from './Networks'
|
import IconLinks from './Networks'
|
||||||
import { avatar as styleAvatar, description } from './Vcard.module.css'
|
import * as styles from './Vcard.module.css'
|
||||||
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query Avatar {
|
||||||
avatar: allFile(filter: { name: { eq: "avatar" } }) {
|
avatar: allFile(filter: { name: { eq: "avatar" } }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -25,7 +25,7 @@ const query = graphql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default function Vcard(): ReactElement {
|
export default function Vcard(): ReactElement {
|
||||||
const data = useStaticQuery(query)
|
const data = useStaticQuery<Queries.AvatarQuery>(query)
|
||||||
const { author, rss, jsonfeed } = useSiteMetadata()
|
const { author, rss, jsonfeed } = useSiteMetadata()
|
||||||
const { twitter, github, name, uri } = author
|
const { twitter, github, name, uri } = author
|
||||||
const avatar = getSrc(data.avatar.edges[0].node)
|
const avatar = getSrc(data.avatar.edges[0].node)
|
||||||
@ -34,13 +34,13 @@ export default function Vcard(): ReactElement {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
className={styleAvatar}
|
className={styles.avatar}
|
||||||
src={avatar}
|
src={avatar}
|
||||||
width="80"
|
width="80"
|
||||||
height="80"
|
height="80"
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
/>
|
/>
|
||||||
<p className={description}>
|
<p className={styles.description}>
|
||||||
Blog of designer & developer{' '}
|
Blog of designer & developer{' '}
|
||||||
<a className="fn" rel="author" href={uri}>
|
<a className="fn" rel="author" href={uri}>
|
||||||
{name}
|
{name}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { success, error, alert } from './Alert.module.css'
|
import * as styles from './Alert.module.css'
|
||||||
|
|
||||||
export function getTransactionMessage(transactionHash?: string): {
|
export function getTransactionMessage(transactionHash?: string): {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
@ -23,7 +23,11 @@ const constructMessage = (
|
|||||||
: message && message.text
|
: message && message.text
|
||||||
|
|
||||||
const classes = (status: string) =>
|
const classes = (status: string) =>
|
||||||
status === 'success' ? success : status === 'error' ? error : alert
|
status === 'success'
|
||||||
|
? styles.success
|
||||||
|
: status === 'error'
|
||||||
|
? styles.error
|
||||||
|
: styles.alert
|
||||||
|
|
||||||
export default function Alert({
|
export default function Alert({
|
||||||
transactionHash,
|
transactionHash,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, ReactElement } from 'react'
|
import React, { useState, useEffect, ReactElement } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { conversion as styleConversion } from './Conversion.module.css'
|
import * as styles from './Conversion.module.css'
|
||||||
import { useNetwork } from 'wagmi'
|
import { useNetwork } from 'wagmi'
|
||||||
|
|
||||||
export async function getFiat(
|
export async function getFiat(
|
||||||
@ -23,7 +23,7 @@ export default function Conversion({
|
|||||||
}: {
|
}: {
|
||||||
amount: string
|
amount: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { activeChain } = useNetwork()
|
const { chain } = useNetwork()
|
||||||
|
|
||||||
const [conversion, setConversion] = useState({
|
const [conversion, setConversion] = useState({
|
||||||
euro: '0.00',
|
euro: '0.00',
|
||||||
@ -32,12 +32,12 @@ export default function Conversion({
|
|||||||
const { dollar, euro } = conversion
|
const { dollar, euro } = conversion
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeChain?.nativeCurrency?.symbol) return
|
if (!chain?.nativeCurrency?.symbol) return
|
||||||
|
|
||||||
async function getFiatResponse() {
|
async function getFiatResponse() {
|
||||||
try {
|
try {
|
||||||
const tokenId =
|
const tokenId =
|
||||||
activeChain?.nativeCurrency?.symbol === 'MATIC'
|
chain?.nativeCurrency?.symbol === 'MATIC'
|
||||||
? 'matic-network'
|
? 'matic-network'
|
||||||
: 'ethereum'
|
: 'ethereum'
|
||||||
const { dollar, euro } = await getFiat(Number(amount), tokenId)
|
const { dollar, euro } = await getFiat(Number(amount), tokenId)
|
||||||
@ -48,10 +48,10 @@ export default function Conversion({
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFiatResponse()
|
getFiatResponse()
|
||||||
}, [amount, activeChain?.nativeCurrency?.symbol])
|
}, [amount, chain?.nativeCurrency?.symbol])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styleConversion}>
|
<div className={styles.conversion}>
|
||||||
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
||||||
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import { useAccount, useNetwork } from 'wagmi'
|
import { useAccount, useNetwork } from 'wagmi'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Conversion from './Conversion'
|
import Conversion from './Conversion'
|
||||||
import {
|
import * as styles from './InputGroup.module.css'
|
||||||
inputGroup,
|
|
||||||
input,
|
|
||||||
inputInput,
|
|
||||||
currency
|
|
||||||
} from './InputGroup.module.css'
|
|
||||||
|
|
||||||
export default function InputGroup({
|
export default function InputGroup({
|
||||||
amount,
|
amount,
|
||||||
@ -21,18 +16,18 @@ export default function InputGroup({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={inputGroup}>
|
<div className={styles.inputGroup}>
|
||||||
<div className={input}>
|
<div className={styles.input}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
pattern="[0-9.]*"
|
pattern="[0-9.]*"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
className={inputInput}
|
className={styles.inputInput}
|
||||||
disabled={!address}
|
disabled={!address}
|
||||||
/>
|
/>
|
||||||
<div className={currency}>
|
<div className={styles.currency}>
|
||||||
<span>{chain?.nativeCurrency?.symbol || 'ETH'}</span>
|
<span>{chain?.nativeCurrency?.symbol || 'ETH'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { parseEther } from '@ethersproject/units'
|
|||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import InputGroup from './InputGroup'
|
import InputGroup from './InputGroup'
|
||||||
import Alert, { getTransactionMessage } from './Alert'
|
import Alert, { getTransactionMessage } from './Alert'
|
||||||
import { web3 as styleWeb3 } from './index.module.css'
|
import * as styles from './index.module.css'
|
||||||
import { useSendTransaction, usePrepareSendTransaction } from 'wagmi'
|
import { useSendTransaction, usePrepareSendTransaction } from 'wagmi'
|
||||||
import { ConnectButton } from '@rainbow-me/rainbowkit'
|
import { ConnectButton } from '@rainbow-me/rainbowkit'
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ export default function Web3Donation({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className={styleWeb3}
|
className={styles.web3}
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
handleSendTransaction()
|
handleSendTransaction()
|
||||||
|
@ -3,14 +3,14 @@ import { Link } from 'gatsby'
|
|||||||
import Icon from '../atoms/Icon'
|
import Icon from '../atoms/Icon'
|
||||||
import Vcard from '../molecules/Vcard'
|
import Vcard from '../molecules/Vcard'
|
||||||
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
||||||
import { copyright, btc, footer } from './Footer.module.css'
|
import * as styles from './Footer.module.css'
|
||||||
|
|
||||||
function Copyright() {
|
function Copyright() {
|
||||||
const { name, uri, github } = useSiteMetadata().author
|
const { name, uri, github } = useSiteMetadata().author
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={copyright}>
|
<section className={styles.copyright}>
|
||||||
<p>
|
<p>
|
||||||
© 2005–
|
© 2005–
|
||||||
{year + ' '}
|
{year + ' '}
|
||||||
@ -21,7 +21,7 @@ function Copyright() {
|
|||||||
<Icon name="GitHub" />
|
<Icon name="GitHub" />
|
||||||
View source
|
View source
|
||||||
</a>
|
</a>
|
||||||
<Link to="/thanks" className={btc}>
|
<Link to="/thanks" className={styles.btc}>
|
||||||
<Icon name="Bitcoin" />
|
<Icon name="Bitcoin" />
|
||||||
Say Thanks
|
Say Thanks
|
||||||
</Link>
|
</Link>
|
||||||
@ -32,7 +32,7 @@ function Copyright() {
|
|||||||
|
|
||||||
export default function Footer(): JSX.Element {
|
export default function Footer(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<footer role="contentinfo" className={footer}>
|
<footer role="contentinfo" className={styles.footer}>
|
||||||
<Vcard />
|
<Vcard />
|
||||||
<Copyright />
|
<Copyright />
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -4,19 +4,19 @@ import Search from '../molecules/Search'
|
|||||||
import Menu from '../molecules/Menu'
|
import Menu from '../molecules/Menu'
|
||||||
import ThemeSwitch from '../molecules/ThemeSwitch'
|
import ThemeSwitch from '../molecules/ThemeSwitch'
|
||||||
import { ReactComponent as Logo } from '../../images/logo.svg'
|
import { ReactComponent as Logo } from '../../images/logo.svg'
|
||||||
import { header, headerContent, title, logo, nav } from './Header.module.css'
|
import * as styles from './Header.module.css'
|
||||||
|
|
||||||
export default function Header(): JSX.Element {
|
export default function Header(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<header role="banner" className={header}>
|
<header role="banner" className={styles.header}>
|
||||||
<div className={headerContent}>
|
<div className={styles.headerContent}>
|
||||||
<h1 className={title}>
|
<h1 className={styles.title}>
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Logo className={logo} /> kremalicious
|
<Logo className={styles.logo} /> kremalicious
|
||||||
</Link>
|
</Link>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<nav role="navigation" className={nav}>
|
<nav role="navigation" className={styles.nav}>
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
<Search />
|
<Search />
|
||||||
<Menu />
|
<Menu />
|
||||||
|
@ -14,7 +14,10 @@ describe('Archive', () => {
|
|||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Archive data={data} pageContext={pageContext} />
|
<Archive
|
||||||
|
data={data as unknown as Queries.ArchiveTemplateQuery}
|
||||||
|
pageContext={pageContext}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { graphql } from 'gatsby'
|
import { graphql } from 'gatsby'
|
||||||
import { Post, PageContext } from '../../@types/Post'
|
import { PageContext } from '../../@types/Post'
|
||||||
import Pagination from '../molecules/Pagination'
|
import Pagination from '../molecules/Pagination'
|
||||||
import PostTeaser from '../molecules/PostTeaser'
|
import PostTeaser from '../molecules/PostTeaser'
|
||||||
import Page from './Page'
|
import Page from './Page'
|
||||||
import { posts } from './Archive.module.css'
|
import * as styles from './Archive.module.css'
|
||||||
|
|
||||||
export default function Archive({
|
export default function Archive({
|
||||||
data,
|
data,
|
||||||
pageContext
|
pageContext
|
||||||
}: {
|
}: {
|
||||||
data: any
|
data: Queries.ArchiveTemplateQuery
|
||||||
pageContext: PageContext
|
pageContext: PageContext
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const edges = data.allMarkdownRemark.edges
|
const edges = data.allMarkdownRemark.edges
|
||||||
const { tag, currentPageNumber, numPages } = pageContext
|
const { tag, currentPageNumber, numPages } = pageContext
|
||||||
|
|
||||||
const PostsList = edges.map(({ node }: { node: Post }) => (
|
const PostsList = edges.map(({ node }) => (
|
||||||
<PostTeaser key={node.id} post={node} />
|
<PostTeaser key={node.id} post={node} />
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -40,14 +40,14 @@ export default function Archive({
|
|||||||
post={page}
|
post={page}
|
||||||
pathname={pageContext.slug}
|
pathname={pageContext.slug}
|
||||||
>
|
>
|
||||||
<div className={posts}>{PostsList}</div>
|
<div className={styles.posts}>{PostsList}</div>
|
||||||
{numPages > 1 && <Pagination pageContext={pageContext} />}
|
{numPages > 1 && <Pagination pageContext={pageContext} />}
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const archiveQuery = graphql`
|
export const archiveQuery = graphql`
|
||||||
query ($tag: String, $skip: Int, $limit: Int) {
|
query ArchiveTemplate($tag: String, $skip: Int, $limit: Int) {
|
||||||
allMarkdownRemark(
|
allMarkdownRemark(
|
||||||
filter: {
|
filter: {
|
||||||
fields: { type: { nin: "photo" } }
|
fields: { type: { nin: "photo" } }
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { ReactElement, ReactNode } from 'react'
|
import React, { ReactElement, ReactNode } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Post } from '../../@types/Post'
|
import SEO, { SeoPost } from '../atoms/SEO'
|
||||||
import SEO from '../atoms/SEO'
|
import * as styles from './Page.module.css'
|
||||||
import { pagetitle } from './Page.module.css'
|
|
||||||
|
|
||||||
export default function Page({
|
export default function Page({
|
||||||
title,
|
title,
|
||||||
@ -15,14 +14,14 @@ export default function Page({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
pathname: string
|
pathname: string
|
||||||
section?: string
|
section?: string
|
||||||
post?: Post
|
post?: SeoPost
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet title={title} />
|
<Helmet title={title} />
|
||||||
<SEO slug={pathname} postSEO post={post} />
|
<SEO slug={pathname} post={post} />
|
||||||
|
|
||||||
<h1 className={pagetitle}>{title}</h1>
|
<h1 className={styles.pagetitle}>{title}</h1>
|
||||||
{section ? <section className={section}>{children}</section> : children}
|
{section ? <section className={section}>{children}</section> : children}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -13,8 +13,9 @@ describe('/photos', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
// @ts-expect-error: only testing first render
|
||||||
<Photos
|
<Photos
|
||||||
data={data}
|
data={data as unknown as Queries.PhotosTemplateQuery}
|
||||||
pageContext={pageContext}
|
pageContext={pageContext}
|
||||||
location={{ pathname: '/photos' } as any}
|
location={{ pathname: '/photos' } as any}
|
||||||
/>
|
/>
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { graphql, Link, PageProps } from 'gatsby'
|
import { graphql, Link, PageProps } from 'gatsby'
|
||||||
import { Post, PageContext } from '../../@types/Post'
|
import { PageContext } from '../../@types/Post'
|
||||||
import { Image } from '../atoms/Image'
|
import { Image } from '../atoms/Image'
|
||||||
import Pagination from '../molecules/Pagination'
|
import Pagination from '../molecules/Pagination'
|
||||||
import Page from './Page'
|
import Page from './Page'
|
||||||
import { photo as stylePhoto, photos as stylePhotos } from './Photos.module.css'
|
import * as styles from './Photos.module.css'
|
||||||
|
|
||||||
export const PhotoThumb = ({ photo }: { photo: Post }): ReactElement => {
|
export const PhotoThumb = ({
|
||||||
|
photo
|
||||||
|
}: {
|
||||||
|
photo: Queries.PhotosTemplateQuery['allMarkdownRemark']['edges'][0]['node']
|
||||||
|
}): ReactElement => {
|
||||||
const { title, image } = photo.frontmatter
|
const { title, image } = photo.frontmatter
|
||||||
const { slug } = photo.fields
|
const { slug } = photo.fields
|
||||||
const { gatsbyImageData } = (image as any).childImageSharp
|
const { gatsbyImageData } = (image as any).childImageSharp
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={stylePhoto}>
|
<article className={styles.photo}>
|
||||||
{image && (
|
{image && (
|
||||||
<Link to={slug}>
|
<Link to={slug}>
|
||||||
<Image title={title} image={gatsbyImageData} alt={title} />
|
<Image title={title} image={gatsbyImageData} alt={title} />
|
||||||
@ -23,9 +27,7 @@ export const PhotoThumb = ({ photo }: { photo: Post }): ReactElement => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PhotosPageProps extends PageProps {
|
interface PhotosPageProps extends PageProps {
|
||||||
data: {
|
data: Queries.PhotosTemplateQuery
|
||||||
allMarkdownRemark: { edges: { node: Post }[] }
|
|
||||||
}
|
|
||||||
pageContext: PageContext
|
pageContext: PageContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +57,8 @@ export default function Photos(props: PhotosPageProps): ReactElement {
|
|||||||
post={page}
|
post={page}
|
||||||
pathname={props.location.pathname}
|
pathname={props.location.pathname}
|
||||||
>
|
>
|
||||||
<section className={stylePhotos}>
|
<section className={styles.photos}>
|
||||||
{photos.map(({ node }: { node: Post }) => (
|
{photos.map(({ node }) => (
|
||||||
<PhotoThumb key={node.id} photo={node} />
|
<PhotoThumb key={node.id} photo={node} />
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
@ -67,7 +69,7 @@ export default function Photos(props: PhotosPageProps): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const photosQuery = graphql`
|
export const photosQuery = graphql`
|
||||||
query ($skip: Int, $limit: Int) {
|
query PhotosTemplate($skip: Int, $limit: Int) {
|
||||||
allMarkdownRemark(
|
allMarkdownRemark(
|
||||||
filter: { fields: { type: { eq: "photo" } } }
|
filter: { fields: { type: { eq: "photo" } } }
|
||||||
sort: { fields: { date: DESC } }
|
sort: { fields: { date: DESC } }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
||||||
import { action, actionTitle, actionText, actions } from './Actions.module.css'
|
import * as styles from './Actions.module.css'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
|
|
||||||
interface ActionProps {
|
interface ActionProps {
|
||||||
@ -13,10 +13,10 @@ interface ActionProps {
|
|||||||
|
|
||||||
const Action = ({ title, text, url, icon, onClick }: ActionProps) => {
|
const Action = ({ title, text, url, icon, onClick }: ActionProps) => {
|
||||||
return (
|
return (
|
||||||
<a className={action} href={url} onClick={onClick}>
|
<a className={styles.action} href={url} onClick={onClick}>
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
<h1 className={actionTitle}>{title}</h1>
|
<h1 className={styles.actionTitle}>{title}</h1>
|
||||||
<p className={actionText}>{text}</p>
|
<p className={styles.actionText}>{text}</p>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ export default function PostActions({
|
|||||||
const urlTwitter = `https://twitter.com/intent/tweet?text=@kremalicious&url=${siteUrl}${slug}`
|
const urlTwitter = `https://twitter.com/intent/tweet?text=@kremalicious&url=${siteUrl}${slug}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={actions}>
|
<aside className={styles.actions}>
|
||||||
<Action
|
<Action
|
||||||
title="Have a comment?"
|
title="Have a comment?"
|
||||||
text="Hit me up @kremalicious"
|
text="Hit me up @kremalicious"
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Changelog from '../../atoms/Changelog'
|
import Changelog from '../../atoms/Changelog'
|
||||||
import { Post } from '../../../@types/Post'
|
|
||||||
import PostToc from './Toc'
|
import PostToc from './Toc'
|
||||||
import { content as styleContent } from './Content.module.css'
|
import * as styles from './Content.module.css'
|
||||||
|
|
||||||
export default function PostContent({ post }: { post: Post }): ReactElement {
|
export default function PostContent({
|
||||||
|
post
|
||||||
|
}: {
|
||||||
|
post: Queries.BlogPostBySlugQuery['post']
|
||||||
|
}): ReactElement {
|
||||||
const separator = '<!-- more -->'
|
const separator = '<!-- more -->'
|
||||||
const changelog = post.frontmatter.changelog
|
const changelog = post.frontmatter.changelog
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ export default function PostContent({ post }: { post: Post }): ReactElement {
|
|||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: content }}
|
dangerouslySetInnerHTML={{ __html: content }}
|
||||||
className={styleContent}
|
className={styles.content}
|
||||||
/>
|
/>
|
||||||
{changelog && <Changelog repo={changelog} />}
|
{changelog && <Changelog repo={changelog} />}
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { lead as styleLead } from './Lead.module.css'
|
import * as styles from './Lead.module.css'
|
||||||
import { Post } from '../../../@types/Post'
|
|
||||||
|
|
||||||
// Extract lead paragraph from content
|
// Extract lead paragraph from content
|
||||||
// Grab everything before more tag, or just first paragraph
|
// Grab everything before more tag, or just first paragraph
|
||||||
@ -8,7 +7,7 @@ const PostLead = ({
|
|||||||
post,
|
post,
|
||||||
className
|
className
|
||||||
}: {
|
}: {
|
||||||
post: Partial<Post>
|
post: Queries.BlogPostBySlugQuery['post']
|
||||||
className?: string
|
className?: string
|
||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
let lead
|
let lead
|
||||||
@ -23,7 +22,7 @@ const PostLead = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styleLead} ${className && className}`}
|
className={`${styles.lead} ${className && className}`}
|
||||||
dangerouslySetInnerHTML={{ __html: lead }}
|
dangerouslySetInnerHTML={{ __html: lead }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import { postMore } from './More.module.css'
|
import * as stylesMore from './More.module.css'
|
||||||
import { postLinkActions } from './LinkActions.module.css'
|
import * as styles from './LinkActions.module.css'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
|
|
||||||
const PostLinkActions = ({
|
const PostLinkActions = ({
|
||||||
@ -11,8 +11,8 @@ const PostLinkActions = ({
|
|||||||
linkurl?: string
|
linkurl?: string
|
||||||
slug: string
|
slug: string
|
||||||
}): ReactElement => (
|
}): ReactElement => (
|
||||||
<aside className={postLinkActions}>
|
<aside className={styles.postLinkActions}>
|
||||||
<a className={postMore} href={linkurl}>
|
<a className={stylesMore.postMore} href={linkurl}>
|
||||||
Go to source <Icon name="ExternalLink" />
|
Go to source <Icon name="ExternalLink" />
|
||||||
</a>
|
</a>
|
||||||
<Link to={slug} rel="tooltip" title="Permalink">
|
<Link to={slug} rel="tooltip" title="Permalink">
|
||||||
|
@ -3,25 +3,22 @@ import { Link } from 'gatsby'
|
|||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
import Tag from '../../atoms/Tag'
|
import Tag from '../../atoms/Tag'
|
||||||
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
||||||
import {
|
import * as styles from './Meta.module.css'
|
||||||
entryMeta,
|
|
||||||
byline,
|
|
||||||
by,
|
|
||||||
type as styleType,
|
|
||||||
tags as styleTags
|
|
||||||
} from './Meta.module.css'
|
|
||||||
import { Post } from '../../../@types/Post'
|
|
||||||
import PostDate from '../../molecules/PostDate'
|
import PostDate from '../../molecules/PostDate'
|
||||||
|
|
||||||
export default function PostMeta({ post }: { post: Post }): ReactElement {
|
export default function PostMeta({
|
||||||
|
post
|
||||||
|
}: {
|
||||||
|
post: Queries.BlogPostBySlugQuery['post']
|
||||||
|
}): ReactElement {
|
||||||
const siteMeta = useSiteMetadata()
|
const siteMeta = useSiteMetadata()
|
||||||
const { author, updated, tags } = post.frontmatter
|
const { author, updated, tags } = post.frontmatter
|
||||||
const { date, type } = post.fields
|
const { date, type } = post.fields
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className={entryMeta}>
|
<footer className={styles.entryMeta}>
|
||||||
<div className={byline}>
|
<div className={styles.byline}>
|
||||||
<span className={by}>by</span>
|
<span className={styles.by}>by</span>
|
||||||
<a className="fn" rel="author" href={siteMeta.author.uri}>
|
<a className="fn" rel="author" href={siteMeta.author.uri}>
|
||||||
{author || siteMeta.author.name}
|
{author || siteMeta.author.name}
|
||||||
</a>
|
</a>
|
||||||
@ -30,14 +27,14 @@ export default function PostMeta({ post }: { post: Post }): ReactElement {
|
|||||||
<PostDate date={date} updated={updated} />
|
<PostDate date={date} updated={updated} />
|
||||||
|
|
||||||
{type && type === 'photo' && (
|
{type && type === 'photo' && (
|
||||||
<div className={styleType}>
|
<div className={styles.type}>
|
||||||
<Link to={`/${slugify(type)}s/`}>{type}s</Link>
|
<Link to={`/${slugify(type)}s/`}>{type}s</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tags && (
|
{tags && (
|
||||||
<div className={styleTags}>
|
<div className={styles.tags}>
|
||||||
{tags.map((tag: string) => {
|
{tags.map((tag) => {
|
||||||
const url = `/archive/${slugify(tag)}/`
|
const url = `/archive/${slugify(tag)}/`
|
||||||
return <Tag key={tag} name={tag} url={url} />
|
return <Tag key={tag} name={tag} url={url} />
|
||||||
})}
|
})}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
import { postMore } from './More.module.css'
|
import * as styles from './More.module.css'
|
||||||
|
|
||||||
const PostMore = ({
|
const PostMore = ({
|
||||||
to,
|
to,
|
||||||
@ -10,7 +10,7 @@ const PostMore = ({
|
|||||||
to: string
|
to: string
|
||||||
children: string
|
children: string
|
||||||
}): ReactElement => (
|
}): ReactElement => (
|
||||||
<Link className={postMore} to={to}>
|
<Link className={styles.postMore} to={to}>
|
||||||
{children}
|
{children}
|
||||||
<Icon name="ChevronRight" />
|
<Icon name="ChevronRight" />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
import { prevnext, label, title } from './PrevNext.module.css'
|
import * as styles from './PrevNext.module.css'
|
||||||
|
|
||||||
interface Node {
|
interface Node {
|
||||||
title: string
|
title: string
|
||||||
@ -14,21 +14,21 @@ interface PrevNextProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PrevNext = ({ prev, next }: PrevNextProps): ReactElement => (
|
const PrevNext = ({ prev, next }: PrevNextProps): ReactElement => (
|
||||||
<nav className={prevnext}>
|
<nav className={styles.prevnext}>
|
||||||
<div>
|
<div>
|
||||||
{prev && (
|
{prev && (
|
||||||
<Link to={prev.slug}>
|
<Link to={prev.slug}>
|
||||||
<Icon name="ChevronLeft" />
|
<Icon name="ChevronLeft" />
|
||||||
<p className={label}>Newer</p>
|
<p className={styles.label}>Newer</p>
|
||||||
<h3 className={title}>{prev.title}</h3>
|
<h3 className={styles.title}>{prev.title}</h3>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{next && (
|
{next && (
|
||||||
<Link to={next.slug}>
|
<Link to={next.slug}>
|
||||||
<p className={label}>Older</p>
|
<p className={styles.label}>Older</p>
|
||||||
<h3 className={title}>{next.title}</h3>
|
<h3 className={styles.title}>{next.title}</h3>
|
||||||
<Icon name="ChevronRight" />
|
<Icon name="ChevronRight" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import {
|
import * as styles from './Title.module.css'
|
||||||
title as styleTitle,
|
|
||||||
titleLink,
|
|
||||||
linkurl as styleLinkurl
|
|
||||||
} from './Title.module.css'
|
|
||||||
import Icon from '../../atoms/Icon'
|
import Icon from '../../atoms/Icon'
|
||||||
import PostDate from '../../molecules/PostDate'
|
import PostDate from '../../molecules/PostDate'
|
||||||
|
|
||||||
@ -24,16 +20,20 @@ export default function PostTitle({
|
|||||||
|
|
||||||
return linkurl ? (
|
return linkurl ? (
|
||||||
<>
|
<>
|
||||||
<h1 className={`${styleTitle} ${titleLink} ${className && className}`}>
|
<h1
|
||||||
|
className={`${styles.title} ${styles.titleLink} ${
|
||||||
|
className && className
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<a href={linkurl} title={`Go to source: ${linkurl}`}>
|
<a href={linkurl} title={`Go to source: ${linkurl}`}>
|
||||||
{title} <Icon name="ExternalLink" />
|
{title} <Icon name="ExternalLink" />
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<div className={styleLinkurl}>{linkHostname}</div>
|
<div className={styles.linkurl}>{linkHostname}</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h1 className={`${styleTitle} ${className && className}`}>{title}</h1>
|
<h1 className={`${styles.title} ${className && className}`}>{title}</h1>
|
||||||
{date && <PostDate date={date} updated={updated} />}
|
{date && <PostDate date={date} updated={updated} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { toc } from './Toc.module.css'
|
import * as styles from './Toc.module.css'
|
||||||
|
|
||||||
const PostToc = ({
|
const PostToc = ({
|
||||||
tableOfContents
|
tableOfContents
|
||||||
@ -8,7 +8,7 @@ const PostToc = ({
|
|||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={toc}
|
className={styles.toc}
|
||||||
dangerouslySetInnerHTML={{ __html: tableOfContents }}
|
dangerouslySetInnerHTML={{ __html: tableOfContents }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -14,11 +14,19 @@ describe('Post', () => {
|
|||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container, rerender } = render(
|
const { container, rerender } = render(
|
||||||
<Post data={post} pageContext={pageContext} />
|
<Post
|
||||||
|
data={post as unknown as Queries.BlogPostBySlugQuery}
|
||||||
|
pageContext={pageContext}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
|
||||||
rerender(<Post data={postWithMore} pageContext={pageContext} />)
|
rerender(
|
||||||
|
<Post
|
||||||
|
data={postWithMore as unknown as Queries.BlogPostBySlugQuery}
|
||||||
|
pageContext={pageContext}
|
||||||
|
/>
|
||||||
|
)
|
||||||
rerender(<Post data={link} pageContext={pageContext} />)
|
rerender(<Post data={link} pageContext={pageContext} />)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { graphql } from 'gatsby'
|
import { graphql } from 'gatsby'
|
||||||
import { Post as PostMetadata } from '../../../@types/Post'
|
|
||||||
import Exif from '../../atoms/Exif'
|
import Exif from '../../atoms/Exif'
|
||||||
import SEO from '../../atoms/SEO'
|
import SEO from '../../atoms/SEO'
|
||||||
import RelatedPosts from '../../molecules/RelatedPosts'
|
import RelatedPosts from '../../molecules/RelatedPosts'
|
||||||
@ -12,14 +11,14 @@ import PostActions from './Actions'
|
|||||||
import PostLinkActions from './LinkActions'
|
import PostLinkActions from './LinkActions'
|
||||||
import PostMeta from './Meta'
|
import PostMeta from './Meta'
|
||||||
import PrevNext from './PrevNext'
|
import PrevNext from './PrevNext'
|
||||||
import { hentry, image as styleImage } from './index.module.css'
|
import * as styles from './index.module.css'
|
||||||
import { Image } from '../../atoms/Image'
|
import { Image } from '../../atoms/Image'
|
||||||
|
|
||||||
export default function Post({
|
export default function Post({
|
||||||
data,
|
data,
|
||||||
pageContext: { next, prev }
|
pageContext: { next, prev }
|
||||||
}: {
|
}: {
|
||||||
data: { post: PostMetadata }
|
data: Queries.BlogPostBySlugQuery
|
||||||
pageContext: {
|
pageContext: {
|
||||||
next: { title: string; slug: string }
|
next: { title: string; slug: string }
|
||||||
prev: { title: string; slug: string }
|
prev: { title: string; slug: string }
|
||||||
@ -35,9 +34,9 @@ export default function Post({
|
|||||||
{style && <link rel="stylesheet" href={style.publicURL} />}
|
{style && <link rel="stylesheet" href={style.publicURL} />}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<SEO slug={slug} post={post} postSEO />
|
<SEO slug={slug} post={post} />
|
||||||
|
|
||||||
<article className={hentry}>
|
<article className={styles.hentry}>
|
||||||
<header>
|
<header>
|
||||||
<PostTitle
|
<PostTitle
|
||||||
linkurl={linkurl}
|
linkurl={linkurl}
|
||||||
@ -52,14 +51,16 @@ export default function Post({
|
|||||||
|
|
||||||
{image && (
|
{image && (
|
||||||
<Image
|
<Image
|
||||||
className={styleImage}
|
className={styles.image}
|
||||||
image={(image as any).childImageSharp.gatsbyImageData}
|
image={(image as any).childImageSharp.gatsbyImageData}
|
||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{type === 'photo' ? (
|
{type === 'photo' ? (
|
||||||
image?.fields && <Exif exif={image.fields.exif} />
|
image?.fields && (
|
||||||
|
<Exif exif={image.fields.exif as Queries.ImageExif} />
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<PostContent post={post} />
|
<PostContent post={post} />
|
||||||
)}
|
)}
|
||||||
@ -69,7 +70,7 @@ export default function Post({
|
|||||||
<PostActions slug={slug} githubLink={githubLink} />
|
<PostActions slug={slug} githubLink={githubLink} />
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<RelatedPosts isPhotos={type === 'photo'} tags={tags} />
|
<RelatedPosts isPhotos={type === 'photo'} tags={tags as string[]} />
|
||||||
|
|
||||||
<PrevNext prev={prev} next={next} />
|
<PrevNext prev={prev} next={next} />
|
||||||
</>
|
</>
|
||||||
|
@ -5,6 +5,7 @@ import WrapPageElement from './wrapPageElement'
|
|||||||
describe('wrapPageElement', () => {
|
describe('wrapPageElement', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
// @ts-expect-error: only testing first render
|
||||||
<WrapPageElement element={'Hello'} props={'hello'} />
|
<WrapPageElement element={'Hello'} props={'hello'} />
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
import type { GatsbyBrowser, GatsbySSR } from 'gatsby'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Layout from '../components/Layout'
|
import Layout from '../components/Layout'
|
||||||
|
|
||||||
const wrapPageElement = ({
|
const wrapPageElement:
|
||||||
element,
|
| GatsbyBrowser['wrapPageElement']
|
||||||
props
|
| GatsbySSR['wrapPageElement'] = ({ element, props }): ReactElement => (
|
||||||
}: {
|
<Layout {...props}>{element}</Layout>
|
||||||
element: any
|
)
|
||||||
props: any
|
|
||||||
}): ReactElement => <Layout {...props}>{element}</Layout>
|
|
||||||
|
|
||||||
export default wrapPageElement
|
export default wrapPageElement
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useStaticQuery, graphql } from 'gatsby'
|
import { useStaticQuery, graphql } from 'gatsby'
|
||||||
import { Site } from '../@types/Site'
|
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
query {
|
query SiteMetadata {
|
||||||
site {
|
site {
|
||||||
siteMetadata {
|
siteMetadata {
|
||||||
siteTitle
|
siteTitle
|
||||||
@ -31,7 +30,7 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export function useSiteMetadata(): Site {
|
export function useSiteMetadata() {
|
||||||
const { site } = useStaticQuery(query)
|
const { site } = useStaticQuery<Queries.SiteMetadataQuery>(query)
|
||||||
return site.siteMetadata
|
return site.siteMetadata
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React, { ReactElement } from 'react'
|
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 { hal9000, wrapper, title, text } from './404.module.css'
|
import * as styles from './404.module.css'
|
||||||
|
import { SeoPost } from '../components/atoms/SEO'
|
||||||
|
|
||||||
const page = {
|
const page: SeoPost = {
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: '404 - Not Found'
|
title: '404 - Not Found'
|
||||||
}
|
}
|
||||||
@ -11,15 +12,15 @@ const page = {
|
|||||||
|
|
||||||
const NotFound = (props: PageProps): ReactElement => (
|
const NotFound = (props: PageProps): ReactElement => (
|
||||||
<Page
|
<Page
|
||||||
title={page.frontmatter.title}
|
title={page.frontmatter?.title}
|
||||||
post={page}
|
post={page}
|
||||||
pathname={props.location.pathname}
|
pathname={props.location.pathname}
|
||||||
>
|
>
|
||||||
<div className={hal9000} />
|
<div className={styles.hal9000} />
|
||||||
|
|
||||||
<div className={wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<h1 className={title}>{"I'm sorry Dave"}</h1>{' '}
|
<h1 className={styles.title}>{"I'm sorry Dave"}</h1>{' '}
|
||||||
<p className={text}>{"I'm afraid I can't do that"}</p>
|
<p className={styles.text}>{"I'm afraid I can't do that"}</p>
|
||||||
<Link to={'/'}>Back to homepage</Link>
|
<Link to={'/'}>Back to homepage</Link>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
import NotFound from '../404'
|
import NotFound from '../404'
|
||||||
|
|
||||||
describe('/404', () => {
|
describe('/404', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
// @ts-expect-error: only testing first render
|
||||||
<NotFound location={{ pathname: '/tags' } as any} />
|
<NotFound location={{ pathname: '/tags' } as any} />
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -6,7 +6,8 @@ import data from '../../../.jest/__fixtures__/home.json'
|
|||||||
|
|
||||||
describe('/', () => {
|
describe('/', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(<Home data={data} />)
|
// @ts-expect-error: only testing first render
|
||||||
|
const { container } = render(<Home data={data as any} />)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -15,6 +15,7 @@ describe('/tags', () => {
|
|||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
// @ts-expect-error: only testing first render
|
||||||
<Tags data={data} location={{ pathname: '/tags' } as any} />
|
<Tags data={data} location={{ pathname: '/tags' } as any} />
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -1,38 +1,35 @@
|
|||||||
import { graphql, PageProps } from 'gatsby'
|
import { graphql, PageProps } from 'gatsby'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { Post } from '../@types/Post'
|
|
||||||
import SEO from '../components/atoms/SEO'
|
import SEO from '../components/atoms/SEO'
|
||||||
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'
|
||||||
import { section, articles, articlesLast, photos } from './index.module.css'
|
import * as styles from './index.module.css'
|
||||||
|
|
||||||
export default function Home({ data }: PageProps): ReactElement {
|
export default function Home(
|
||||||
|
props: PageProps<Queries.HomePageQuery>
|
||||||
|
): ReactElement {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SEO />
|
<SEO />
|
||||||
<section className={section}>
|
<section className={styles.section}>
|
||||||
<div className={articles}>
|
<div className={styles.articles}>
|
||||||
{(data as any).latestArticles.edges
|
{props.data.latestArticles.edges.slice(0, 2).map(({ node }) => (
|
||||||
.slice(0, 2)
|
<PostTeaser key={node.id} post={node} hideDate />
|
||||||
.map(({ node }: { node: Post }) => (
|
))}
|
||||||
<PostTeaser key={node.id} post={node} hideDate />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`${articles} ${articlesLast}`}>
|
<div className={`${styles.articles} ${styles.articlesLast}`}>
|
||||||
{(data as any).latestArticles.edges
|
{props.data.latestArticles.edges.slice(2, 8).map(({ node }) => (
|
||||||
.slice(2, 8)
|
<PostTeaser key={node.id} post={node} hideDate />
|
||||||
.map(({ node }: { node: Post }) => (
|
))}
|
||||||
<PostTeaser key={node.id} post={node} hideDate />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PostMore to="/archive">All Articles</PostMore>
|
<PostMore to="/archive">All Articles</PostMore>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={section}>
|
<section className={styles.section}>
|
||||||
<div className={photos}>
|
<div className={styles.photos}>
|
||||||
{(data as any).latestPhotos.edges.map(({ node }: { node: Post }) => (
|
{props.data.latestPhotos.edges.map(({ node }) => (
|
||||||
<PhotoThumb key={node.id} photo={node} />
|
<PhotoThumb key={node.id} photo={node} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -44,7 +41,7 @@ export default function Home({ data }: PageProps): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const homeQuery = graphql`
|
export const homeQuery = graphql`
|
||||||
{
|
query HomePage {
|
||||||
latestArticles: allMarkdownRemark(
|
latestArticles: allMarkdownRemark(
|
||||||
filter: { fields: { type: { ne: "photo" } } }
|
filter: { fields: { type: { ne: "photo" } } }
|
||||||
sort: { fields: { date: DESC } }
|
sort: { fields: { date: DESC } }
|
||||||
|
@ -2,39 +2,28 @@ import React, { ReactElement } from 'react'
|
|||||||
import { graphql, PageProps } from 'gatsby'
|
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 { tags } from './tags.module.css'
|
import * as styles from './tags.module.css'
|
||||||
|
import { SeoPost } from '../components/atoms/SEO'
|
||||||
|
|
||||||
const page = {
|
const page: SeoPost = {
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
description: 'All the tags being used.'
|
description: 'All the tags being used.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Tag {
|
const TagsPage = ({
|
||||||
fieldValue: string
|
location,
|
||||||
totalCount: number
|
data
|
||||||
}
|
}: PageProps<Queries.TagsPageQuery>): ReactElement => (
|
||||||
|
<Page title={page.frontmatter.title} post={page} pathname={location.pathname}>
|
||||||
interface TagsPageProps extends PageProps {
|
<ul className={styles.tags}>
|
||||||
data: {
|
{Array.from(data.allMarkdownRemark.group)
|
||||||
allMarkdownRemark: { group: Tag[] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TagsPage = (props: TagsPageProps): ReactElement => (
|
|
||||||
<Page
|
|
||||||
title={page.frontmatter.title}
|
|
||||||
post={page}
|
|
||||||
pathname={props.location.pathname}
|
|
||||||
>
|
|
||||||
<ul className={tags}>
|
|
||||||
{props.data.allMarkdownRemark.group
|
|
||||||
.sort((a, b) => b.totalCount - a.totalCount)
|
.sort((a, b) => b.totalCount - a.totalCount)
|
||||||
.map((tag: Tag) => (
|
.map((tag) => (
|
||||||
<li key={tag.fieldValue}>
|
<li key={tag.fieldValue}>
|
||||||
<Tag
|
<Tag
|
||||||
name={tag.fieldValue}
|
name={tag.fieldValue || ''}
|
||||||
url={`/archive/${tag.fieldValue}/`}
|
url={`/archive/${tag.fieldValue}/`}
|
||||||
count={tag.totalCount}
|
count={tag.totalCount}
|
||||||
style={{ fontSize: `${100 + tag.totalCount * 2}%` }}
|
style={{ fontSize: `${100 + tag.totalCount * 2}%` }}
|
||||||
@ -48,7 +37,7 @@ const TagsPage = (props: TagsPageProps): ReactElement => (
|
|||||||
export default TagsPage
|
export default TagsPage
|
||||||
|
|
||||||
export const tagsPageQuery = graphql`
|
export const tagsPageQuery = graphql`
|
||||||
{
|
query TagsPage {
|
||||||
allMarkdownRemark {
|
allMarkdownRemark {
|
||||||
group(field: { frontmatter: { tags: SELECT } }) {
|
group(field: { frontmatter: { tags: SELECT } }) {
|
||||||
fieldValue
|
fieldValue
|
||||||
|
@ -2,16 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import { Helmet } from 'react-helmet'
|
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 {
|
import * as styles from './thanks.module.css'
|
||||||
thanks,
|
|
||||||
title,
|
|
||||||
coins as styleCoins,
|
|
||||||
coin,
|
|
||||||
code,
|
|
||||||
buttonBack,
|
|
||||||
titleCoin,
|
|
||||||
subTitle
|
|
||||||
} from './thanks.module.css'
|
|
||||||
import Web3Donation from '../components/molecules/Web3Donation'
|
import Web3Donation from '../components/molecules/Web3Donation'
|
||||||
import Copy from '../components/atoms/Copy'
|
import Copy from '../components/atoms/Copy'
|
||||||
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
|
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
|
||||||
@ -20,9 +11,9 @@ import { chains, theme, wagmiClient } from '../helpers/rainbowkit'
|
|||||||
|
|
||||||
function Coin({ address, title }: { address: string; title: string }) {
|
function Coin({ address, title }: { address: string; title: string }) {
|
||||||
return (
|
return (
|
||||||
<div className={coin}>
|
<div className={styles.coin}>
|
||||||
<h4 className={titleCoin}>{title}</h4>
|
<h4 className={styles.titleCoin}>{title}</h4>
|
||||||
<pre className={code}>
|
<pre className={styles.code}>
|
||||||
<code>{address}</code>
|
<code>{address}</code>
|
||||||
<Copy text={address} />
|
<Copy text={address} />
|
||||||
</pre>
|
</pre>
|
||||||
@ -32,7 +23,7 @@ function Coin({ address, title }: { address: string; title: string }) {
|
|||||||
|
|
||||||
const BackButton = () => (
|
const BackButton = () => (
|
||||||
<button
|
<button
|
||||||
className={`link ${buttonBack}`}
|
className={`link ${styles.buttonBack}`}
|
||||||
onClick={() => window.history.back()}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
<Icon name="ChevronLeft" /> Go Back
|
<Icon name="ChevronLeft" /> Go Back
|
||||||
@ -52,10 +43,10 @@ export default function Thanks(): ReactElement {
|
|||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<article className={thanks}>
|
<article className={styles.thanks}>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<header>
|
<header>
|
||||||
<h1 className={title}>Say Thanks</h1>
|
<h1 className={styles.title}>Say Thanks</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<WagmiConfig client={wagmiClient}>
|
<WagmiConfig client={wagmiClient}>
|
||||||
@ -64,8 +55,8 @@ export default function Thanks(): ReactElement {
|
|||||||
</RainbowKitProvider>
|
</RainbowKitProvider>
|
||||||
</WagmiConfig>
|
</WagmiConfig>
|
||||||
|
|
||||||
<div className={styleCoins}>
|
<div className={styles.coins}>
|
||||||
<h3 className={subTitle}>
|
<h3 className={styles.subTitle}>
|
||||||
Send Bitcoin or ERC-20 tokens from any wallet.
|
Send Bitcoin or ERC-20 tokens from any wallet.
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"lib": ["dom", "es2017"],
|
"lib": ["dom", "esnext"],
|
||||||
// "allowJs": true,
|
|
||||||
// "checkJs": true,
|
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
// "strict": true,
|
// "strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@ -18,5 +16,12 @@
|
|||||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "public", ".cache", "*.js"],
|
"exclude": ["node_modules", "public", ".cache", "*.js"],
|
||||||
"include": ["./src/**/*", "./scripts/*.ts", "./.jest/**/*"]
|
"include": [
|
||||||
|
"./*.ts",
|
||||||
|
"./src/**/*",
|
||||||
|
"./gatsby*",
|
||||||
|
"./gatsby/**/*",
|
||||||
|
"./scripts/*.ts",
|
||||||
|
"./.jest/**/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user