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

Merge pull request #175 from kremalicious/feature/photos-layout

new photos layout
This commit is contained in:
Matthias Kretschmann 2019-10-12 00:30:05 +02:00 committed by GitHub
commit d011dd5870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 312 additions and 275 deletions

View File

@ -19,6 +19,7 @@
"plugin:prettier/recommended",
"plugin:react/recommended"
],
"plugins": ["@typescript-eslint", "react"],
"rules": {
"object-curly-spacing": ["error", "always"],
"react/prop-types": "off",

View File

@ -1,13 +1,17 @@
module.exports = {
transform: {
'^.+\\.tsx?$': '<rootDir>/jest/jest-preprocess.js'
'^.+\\.(tsx?|jsx?)$': 'ts-jest',
'^.+\\.jsx?$': '<rootDir>/jest/jest-preprocess.js'
},
testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$',
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy',
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/jest/__mocks__/file-mock.js',
'\\.svg': '<rootDir>/jest/__mocks__/svgr-mock.js'
'<rootDir>/jest/__mocks__/file-mock.ts',
'\\.svg': '<rootDir>/jest/__mocks__/svgr-mock.ts'
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testPathIgnorePatterns: ['node_modules', '.cache', 'public', 'coverage'],
transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'],
globals: {
@ -15,6 +19,6 @@ module.exports = {
},
testURL: 'http://localhost',
setupFiles: ['<rootDir>/jest/loadershim.js'],
setupFilesAfterEnv: ['<rootDir>/jest/setup-test-env.js'],
setupFilesAfterEnv: ['<rootDir>/jest/setup-test-env.ts'],
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/@types/**/*']
}

View File

@ -1 +0,0 @@
module.exports = 'test-file-stub'

View File

@ -0,0 +1 @@
export default 'test-file-stub'

View File

@ -1,13 +1,13 @@
const React = require('react')
import React from 'react'
const gatsby = jest.requireActual('gatsby')
module.exports = {
export default {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(
// these props are invalid for an `a` tag
({
/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-unused-vars */
activeClassName,
activeStyle,
getProps,
@ -15,7 +15,7 @@ module.exports = {
ref,
replace,
to,
/* eslint-enable no-unused-vars */
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest
}) =>
React.createElement('a', {

View File

@ -1 +0,0 @@
module.exports = { ReactComponent: 'svg' }

View File

@ -0,0 +1,3 @@
const content = 'svg'
export const ReactComponent = content
export default content

View File

@ -1 +0,0 @@
require('@testing-library/jest-dom/extend-expect')

1
jest/setup-test-env.ts Normal file
View File

@ -0,0 +1 @@
import '@testing-library/jest-dom/extend-expect'

View File

@ -18,6 +18,7 @@
"lint:css": "stylelint 'src/**/*.{css,scss}'",
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git,coverage}/**/*'",
"format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,md,json,css,scss}'",
"tsc": "tsc --noEmit",
"deploy": "./scripts/deploy.sh",
"new": "babel-node ./scripts/new.js"
},
@ -90,6 +91,7 @@
"@testing-library/react": "^9.2.0",
"@types/classnames": "^2.2.9",
"@types/jest": "^24.0.18",
"@types/lunr": "^2.3.2",
"@types/node": "^12.7.8",
"@types/react": "^16.9.4",
"@types/react-dom": "^16.9.1",
@ -124,6 +126,7 @@
"stylelint-config-prettier": "^6.0.0",
"stylelint-config-standard": "^19.0.0",
"stylelint-prettier": "^1.1.1",
"ts-jest": "^24.1.0",
"typescript": "^3.6.3",
"why-did-you-update": "^1.0.6"
},

View File

@ -1,23 +1,18 @@
interface CSSModule {
[className: string]: string
}
// type shims for CSS modules
declare module '*.module.scss' {
const cssModule: CSSModule
export = cssModule
}
declare module '*.module.css' {
const cssModule: CSSModule
export = cssModule
const classes: { [key: string]: string }
export default classes
}
/* eslint-disable-next-line @typescript-eslint/no-empty-interface */
interface SvgrComponent
extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
declare module '*.module.scss' {
const classes: { [key: string]: string }
export default classes
}
declare module '*.svg' {
const value: SvgrComponent
export default value
import * as React from 'react'
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement>
>
const src: string
export default src
}

5
src/@types/node_modules.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module 'pigeon-maps'
declare module 'pigeon-marker'
declare module 'react-blockies'
declare module 'react-time'
declare module 'remark-react'

View File

@ -1 +0,0 @@
declare module 'pigeon-maps'

View File

@ -1 +0,0 @@
declare module 'pigeon-marker'

View File

@ -1 +0,0 @@
declare module 'react-blockies'

View File

@ -1 +0,0 @@
declare module 'react-time'

View File

@ -1 +0,0 @@
declare module 'remark-react'

View File

@ -23,12 +23,7 @@
}
.postImage {
@include breakoutviewport();
max-width: none;
display: block;
margin-top: $spacer * 1.5;
margin-bottom: $spacer * 1.5;
a & {
position: relative;

View File

@ -5,6 +5,7 @@ import Container from '../atoms/Container'
import PostTeaser from '../Post/PostTeaser'
import SearchResultsEmpty from './SearchResultsEmpty'
import styles from './SearchResults.module.scss'
import { GatsbyImageProps } from 'gatsby-image'
const query = graphql`
query {
@ -29,6 +30,22 @@ const query = graphql`
}
`
interface Page {
slug: string
}
interface PostNode {
node: {
id: string
fields: { slug: string }
frontmatter: {
title: string
type: string
image: { childImageSharp: GatsbyImageProps }
}
}
}
export default function SearchResults({
searchQuery,
results,
@ -48,13 +65,13 @@ export default function SearchResults({
<Container>
{results.length > 0 ? (
<ul>
{results.map(page =>
{results.map((page: Page) =>
posts
.filter(post => post.node.fields.slug === page.slug)
.map(({ node }: { node: any }) => (
.filter((post: PostNode) => post.node.fields.slug === page.slug)
.map((post: PostNode) => (
<PostTeaser
key={page.slug}
post={node}
post={post.node}
toggleSearch={toggleSearch}
/>
))

View File

@ -1,45 +0,0 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Search from '.'
import { useStaticQuery } from 'gatsby'
describe('Search', () => {
beforeEach(() => {
useStaticQuery.mockImplementation(() => {
return {
allMarkdownRemark: {
edges: [
{
node: {
id: 'ddd',
frontmatter: {
title: 'Hello',
image: {
childImageSharp: 'hello'
}
},
fields: {
slug: '/hello/'
}
}
}
]
}
}
})
const portalRoot = document.createElement('div')
portalRoot.setAttribute('id', 'document')
document.body.appendChild(portalRoot)
})
it('can be opened', () => {
const { getByTitle, getByPlaceholderText } = render(<Search lng="en" />)
fireEvent.click(getByTitle('Search'))
fireEvent.change(getByPlaceholderText('Search everything'), {
target: { value: 'hello' }
})
fireEvent.click(getByTitle('Close search'))
})
})

View File

@ -1,12 +1,26 @@
import React, { useState } from 'react'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import { CSSTransition } from 'react-transition-group'
import lunr from 'lunr'
import SearchInput from './SearchInput'
import SearchButton from './SearchButton'
import SearchResults from './SearchResults'
import styles from './index.module.scss'
declare global {
interface Window {
__LUNR__: {
readonly [language: string]: {
readonly index: lunr.Index
readonly store: {
readonly [key: string]: any
}
}
}
}
}
function getSearchResults(query: string, lng: string) {
if (!query || !window.__LUNR__) return []
const lunrIndex = window.__LUNR__[lng]

View File

@ -21,7 +21,7 @@ export default function Alerts({
message
}: {
transactionHash: string | null
message: { text: MessageChannel; status: string } | null
message: { text?: MessageChannel; status?: string } | null
}) {
const constructMessage = () => {
let messageOutput
@ -53,7 +53,7 @@ export default function Alerts({
return (
<div
className={classes()}
dangerouslySetInnerHTML={{ __html: constructMessage() }}
dangerouslySetInnerHTML={{ __html: `${constructMessage()}` }}
/>
)
}

View File

@ -3,7 +3,7 @@ import { getFiat } from './utils'
import styles from './Conversion.module.scss'
export default class Conversion extends PureComponent<
{ amount: string },
{ amount: number },
{ euro: string; dollar: string }
> {
state = {

View File

@ -10,7 +10,7 @@ export default function InputGroup({
sendTransaction,
selectedAccount
}: {
amount: string
amount: number
onAmountChange(target: any): void
sendTransaction(): void
selectedAccount?: string | null

View File

@ -1,5 +1,5 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Web3 from 'web3'
import InputGroup from './InputGroup'
import Alerts, { alertMessages } from './Alerts'
import styles from './index.module.scss'
@ -9,26 +9,40 @@ const ONE_SECOND = 1000
const ONE_MINUTE = ONE_SECOND * 60
const correctNetwork = 1
export default class Web3Donation extends PureComponent {
interface Web3DonationState {
netId: number
networkName: string
accounts: string[]
selectedAccount: string
amount: number
transactionHash: string
receipt: string
message: {
status?: string
text?: string
}
inTransaction: boolean
}
export default class Web3Donation extends PureComponent<
{ address: string },
Web3DonationState
> {
state = {
netId: null,
networkName: null,
accounts: [],
selectedAccount: null,
amount: '0.01',
transactionHash: null,
receipt: null,
message: null,
netId: 0,
networkName: '',
accounts: [''],
selectedAccount: '',
amount: 0.01,
transactionHash: '',
receipt: '',
message: {},
inTransaction: false
}
static propTypes = {
address: PropTypes.string
}
web3 = null
interval = null
networkInterval = null
web3: Web3 = null
interval: any = null
networkInterval: any = null
componentDidMount() {
this.initWeb3()
@ -47,10 +61,15 @@ export default class Web3Donation extends PureComponent {
this.web3
? this.initAllTheTings()
: this.setState({
message: { status: 'error', text: alertMessages().noWeb3 }
message: {
status: 'error',
text: alertMessages().noWeb3
}
})
} catch (error) {
this.setState({ message: { status: 'error', text: error } })
this.setState({
message: { status: 'error', text: error }
})
}
}
@ -106,7 +125,10 @@ export default class Web3Donation extends PureComponent {
})
} else {
this.setState({
message: { status: 'error', text: alertMessages().noAccount }
message: {
status: 'error',
text: alertMessages().noAccount
}
})
}
}
@ -132,16 +154,21 @@ export default class Web3Donation extends PureComponent {
})
})
.on('error', error =>
this.setState({ message: { status: 'error', text: error } })
this.setState({
message: { status: 'error', text: error.message }
})
)
.then(() => {
this.setState({
message: { status: 'success', text: alertMessages().success }
message: {
status: 'success',
text: alertMessages().success
}
})
})
}
onAmountChange = ({ target }) => {
onAmountChange = ({ target }: { target: any }) => {
this.setState({ amount: target.value })
}

View File

@ -1,8 +1,18 @@
import Web3 from 'web3'
declare global {
interface Window {
ethereum: any
web3: Web3
}
interface Console {
[key: string]: any
}
}
export class Logger {
static dispatch(verb: any, ...args: any) {
// eslint-disable-next-line no-console
console[verb](...args)
}

View File

@ -10,6 +10,7 @@ const exif = {
fstop: '7.2',
shutterspeed: '200',
focalLength: '200',
lensModel: 'Hello',
exposure: '200',
gps: { latitude: '52.4792516', longitude: '13.431609' }
}

View File

@ -1,6 +1,6 @@
import React from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import { useSiteMetadata } from '../../hooks/use-site-metadata'
const query = graphql`
@ -15,69 +15,55 @@ const query = graphql`
}
`
const createSchemaOrg = (
blogURL: string,
title: string,
postSEO: boolean,
postURL: string,
image: string,
description: string
) => {
const schemaOrgJSONLD = [
{
'@context': 'http://schema.org',
'@type': 'WebSite',
url: blogURL,
name: title
}
]
// const createSchemaOrg = (
// blogURL: string,
// title: string,
// postSEO: boolean,
// postURL: string,
// image: string,
// description: string,
// author?: string
// ) => {
// const schemaOrgJSONLD: any = [
// {
// '@context': 'http://schema.org',
// '@type': 'WebSite',
// url: blogURL,
// name: title
// }
// ]
if (postSEO) {
schemaOrgJSONLD.push(
{
'@context': 'http://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
item: {
'@id': postURL,
name: title,
image
}
}
]
},
{
'@context': 'http://schema.org',
'@type': 'BlogPosting',
url: blogURL,
name: title,
headline: title,
image: {
'@type': 'ImageObject',
url: image
},
description
}
)
}
return schemaOrgJSONLD
}
// if (postSEO) {
// schemaOrgJSONLD.push({
// '@context': 'http://schema.org',
// '@type': 'BlogPosting',
// author,
// publisher: author,
// url: postURL,
// name: title,
// headline: title,
// image: {
// '@type': 'ImageObject',
// url: image
// },
// description
// })
// }
// return schemaOrgJSONLD
// }
const MetaTags = ({
description,
image,
url,
schema,
// schema,
postSEO,
title
}: {
description: string
image: string
url: string
schema: string
// schema: string
postSEO: boolean
title: string
}) => {
@ -96,7 +82,7 @@ const MetaTags = ({
<link rel="canonical" href={url} />
{/* Schema.org tags */}
<script type="application/ld+json">{schema}</script>
{/* <script type="application/ld+json">{schema}</script> */}
{/* OpenGraph tags */}
<meta property="og:url" content={url} />
@ -161,23 +147,22 @@ export default function SEO({
const blogURL = siteUrl
const url = postSEO ? postURL : blogURL
let schema = createSchemaOrg(
blogURL,
title,
postSEO,
postURL,
image,
description
)
schema = JSON.stringify(schema)
// const schema = createSchemaOrg(
// blogURL,
// title,
// postSEO,
// postURL,
// image,
// description,
// author
// )
return (
<MetaTags
description={description}
image={image}
url={url}
schema={schema}
// schema={(schema as unknown) as string}
postSEO={postSEO}
title={title}
/>

View File

@ -7,24 +7,25 @@ export const fadeIn = {
}
}
export const moveInTop = {
enter: {
const transition = { type: 'spring' }
const enter = {
y: 0,
transition: { type: 'spring' }
},
...transition
}
export const moveInTop = {
...enter,
exit: {
y: '-2rem',
transition: { type: 'spring' }
...transition
}
}
export const moveInBottom = {
enter: {
y: 0,
transition: { type: 'spring' }
},
...enter,
exit: {
y: '2rem',
transition: { type: 'spring' }
...transition
}
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import { useSiteMetadata } from '../../hooks/use-site-metadata'
const TypekitScript = (typekitID: string) => (

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import { Link } from 'gatsby'
import Hamburger from '../atoms/Hamburger'
import styles from './Menu.module.scss'

View File

@ -30,13 +30,7 @@ export default function Vcard() {
return (
<div className={styles.vcard}>
<Img
className={styles.avatar}
fixed={avatar}
alt="avatar"
width="80"
height="80"
/>
<Img className={styles.avatar} fixed={avatar} alt="avatar" />
<p className={styles.description}>
Blog of designer &amp; developer{' '}
<a className="fn" rel="author" href={uri}>

View File

@ -3,7 +3,6 @@ import { graphql, Link } from 'gatsby'
import PostImage from '../components/Post/PostImage'
import Page from '../templates/Page'
import styles from './goodies.module.scss'
import { FluidObject } from 'gatsby-image'
const page = {
frontmatter: {
@ -13,22 +12,21 @@ const page = {
}
}
interface GoodieNode {
interface Post {
id: string
fields: { slug: string }
frontmatter: {
title: string
image: { childImageSharp: { fluid: FluidObject } }
image: { childImageSharp: any }
}
}
const GoodiesThumbs = ({ edges }: { edges: [{ node: GoodieNode }] }) =>
edges.map(({ node }: { node: GoodieNode }) => {
const { title, image } = node.frontmatter
const { slug } = node.fields
const GoodiesThumb = ({ post }: { post: Post }) => {
const { title, image } = post.frontmatter
const { slug } = post.fields
return (
<article className={styles.goodie} key={node.id}>
<article className={styles.goodie}>
{image && (
<Link to={slug}>
<h1 className={styles.title}>{title}</h1>
@ -37,18 +35,20 @@ const GoodiesThumbs = ({ edges }: { edges: [{ node: GoodieNode }] }) =>
)}
</article>
)
})
}
export default function Goodies({
data,
location
}: {
data: { goodies: { edges: [{ node: GoodieNode }] } }
data: { goodies: { edges: [{ node: Post }] } }
location: Location
}) {
return (
<Page title={page.frontmatter.title} post={page} location={location}>
<GoodiesThumbs edges={data.goodies.edges} />
{data.goodies.edges.map(({ node }) => (
<GoodiesThumb key={node.id} post={node} />
))}
</Page>
)
}

View File

@ -2,25 +2,33 @@
@import 'mixins';
.photos {
@include breakoutviewport;
@include breakoutviewport--full;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
display: grid;
grid-template-columns: 1fr 1fr;
gap: $spacer;
padding-left: $spacer;
padding-right: $spacer;
@media (min-width: $screen-md) {
padding-left: 0;
padding-right: 0;
@media (min-width: $screen-sm) {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
}
.photo {
flex: 0 0 48%;
margin-bottom: 4%;
a {
display: block;
}
figure {
> div {
margin: 0;
}
}
figcaption {
font-size: $font-size-base;
padding-left: $spacer / 2;
padding-right: $spacer / 2;
}
}

View File

@ -1,10 +1,8 @@
import React from 'react'
import { graphql, Link } from 'gatsby'
import Image from '../components/atoms/Image'
import Page from '../templates/Page'
import PostImage from '../components/Post/PostImage'
import styles from './photos.module.scss'
import { FluidObject } from 'gatsby-image'
const page = {
frontmatter: {
@ -13,39 +11,47 @@ const page = {
}
}
interface PhotoNode {
node: {
interface Photo {
id: string
fields: { slug: string }
frontmatter: {
title: string
type: string
image: { childImageSharp: { fluid: FluidObject } }
}
image: { childImageSharp: any }
}
}
const PhotoThumbs = ({ edges }: { edges: PhotoNode[] }) =>
edges.map(({ node }) => {
const { title, image } = node.frontmatter
const { slug } = node.fields
const PhotoThumb = ({ photo }: { photo: Photo }) => {
const { title, image } = photo.frontmatter
const { slug } = photo.fields
const { fluid } = image.childImageSharp
return (
<article className={styles.photo} key={node.id}>
<article className={styles.photo}>
{image && (
<Link to={slug}>
<Image fluid={image.childImageSharp.fluid} alt={title} />
<PostImage title={title} fluid={fluid} alt={title} />
</Link>
)}
</article>
)
})
}
interface PhotosData {
photos: {
edges: [
{
node: Photo
}
]
}
}
export default function Photos({
data,
location
}: {
data: { photos: { edges: PhotoNode[] } }
data: PhotosData
location: Location
}) {
return (
@ -55,7 +61,9 @@ export default function Photos({
location={location}
section={styles.photos}
>
<PhotoThumbs edges={data.photos.edges} />
{data.photos.edges.map(({ node }) => (
<PhotoThumb key={node.id} photo={node} />
))}
</Page>
)
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import SEO from '../components/atoms/SEO'
import Layout from '../components/Layout'
import styles from './Page.module.scss'

View File

@ -6,7 +6,21 @@
padding-top: $spacer;
padding-bottom: $spacer * 3;
> a {
display: block;
}
&:only-child {
padding-bottom: $spacer;
}
}
.postImageWrap {
@include breakoutviewport();
figure {
max-width: none;
margin-top: $spacer * 1.5;
margin-bottom: 0;
}
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import Helmet from 'react-helmet'
import { Helmet } from 'react-helmet'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import PostImage from '../components/Post/PostImage'
@ -39,7 +39,9 @@ export default function Post({
{type === 'post' && <PostLead post={post} />}
{type === 'photo' && <PostContent post={post} />}
{image && (
<div className={styles.postImageWrap}>
<PostImage fluid={image.childImageSharp.fluid} alt={title} />
</div>
)}
{image && image.fields && <Exif exif={image.fields.exif} />}

View File

@ -11,6 +11,7 @@ import SEO from '../components/atoms/SEO'
import Pagination from '../components/molecules/Pagination'
import Featured from '../components/molecules/Featured'
import styles from './Posts.module.scss'
import stylesPost from './Post.module.scss'
export default function Posts({
data,
@ -36,11 +37,13 @@ export default function Posts({
{image && (
<Link to={slug} title={title}>
<div className={stylesPost.postImageWrap}>
<PostImage
title={type === 'photo' ? title : null}
fluid={image.childImageSharp.fluid}
alt={title}
/>
</div>
</Link>
)}

View File

@ -1,18 +1,16 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "esnext",
"jsx": "preserve",
"lib": ["dom", "es2015", "es2017"],
"strict": true,
"noEmit": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["dom", "esnext"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true
"allowJs": true
},
"exclude": ["node_modules", "public", ".cache", "*.js"],
"include": ["./src/**/*"]
"include": ["./src/**/*", "./jest/**/*"]
}