mirror of
https://github.com/kremalicious/blog.git
synced 2024-06-28 16:48:00 +02:00
refactor
This commit is contained in:
parent
84ea68afb0
commit
fc7cb88049
|
@ -3,5 +3,6 @@ import react from '@astrojs/react'
|
|||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [react()]
|
||||
integrations: [react()],
|
||||
site: 'https://kremalicious.com'
|
||||
})
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
"homepage": "https://kremalicious.com",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "astro dev --config ./config/astro.config.mjs",
|
||||
"start": "astro dev --config ./config/astro.config.mjs",
|
||||
"build": "astro build --config ./config/astro.config.mjs",
|
||||
"dev": "astro dev --config .config/astro.config.mjs",
|
||||
"start": "astro dev --config .config/astro.config.mjs",
|
||||
"build": "astro build --config .config/astro.config.mjs",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"test": "npm run lint && npm run type-check && npm run jest",
|
||||
"test": "astro check && tsc --noEmit && npm run lint && npm run type-check && npm run jest",
|
||||
"jest": "jest -c .config/jest/jest.config.js --coverage --silent",
|
||||
"lint": "run-p --continue-on-error lint:js lint:css lint:md",
|
||||
"lint:js": "eslint --ignore-path .gitignore --ext .ts,.tsx .",
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import meta from '../../../.config/jest/__fixtures__/meta.json'
|
||||
import Networks from './Networks'
|
||||
|
||||
const { author, rss, jsonfeed } = meta.site.siteMetadata
|
||||
const { twitter, github } = author
|
||||
const links = [twitter, github, rss, jsonfeed, 'hello']
|
||||
|
||||
describe('Networks', () => {
|
||||
it('renders correctly', () => {
|
||||
const { container } = render(<Networks links={links} />)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import Icon from '../core/Icon'
|
||||
import styles from './Networks.module.css'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.avatar {
|
||||
composes: frame from '../atoms/Image.module.css';
|
||||
/* composes: frame from '../atoms/Image.module.css'; */
|
||||
border: 2px solid transparent;
|
||||
border-radius: 50%;
|
||||
margin-bottom: calc(var(--spacer) / 3);
|
||||
|
|
|
@ -1,41 +1,19 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import { getSrc } from 'gatsby-plugin-image'
|
||||
import type { ReactElement } from 'react'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import IconLinks from '../molecules./Footer/Networks'
|
||||
import Networks from './Networks'
|
||||
import styles from './Vcard.module.css'
|
||||
|
||||
const query = graphql`
|
||||
query Avatar {
|
||||
avatar: allFile(filter: { name: { eq: "avatar" } }) {
|
||||
edges {
|
||||
node {
|
||||
childImageSharp {
|
||||
gatsbyImageData(
|
||||
layout: CONSTRAINED
|
||||
width: 80
|
||||
height: 80
|
||||
quality: 85
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Vcard(): ReactElement {
|
||||
const data = useStaticQuery<Queries.AvatarQuery>(query)
|
||||
const { author, rss, jsonfeed } = useSiteMetadata()
|
||||
const { mastodon, twitter, github, name, uri } = author
|
||||
const avatar = getSrc(data.avatar.edges[0].node)
|
||||
// const avatar = getSrc(data.avatar.edges[0].node)
|
||||
const links = [mastodon, github, twitter, rss, jsonfeed]
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
className={styles.avatar}
|
||||
src={avatar}
|
||||
// src={avatar}
|
||||
width="80"
|
||||
height="80"
|
||||
alt="avatar"
|
||||
|
@ -47,7 +25,7 @@ export default function Vcard(): ReactElement {
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<IconLinks links={links} />
|
||||
<Networks links={links} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
30
src/components/Footer/index.astro
Normal file
30
src/components/Footer/index.astro
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
import Icon from '../core/Icon'
|
||||
import Vcard from './Vcard'
|
||||
import styles from './index.module.css'
|
||||
import config from '@config/blog.config.mjs'
|
||||
|
||||
const year = new Date().getFullYear()
|
||||
const { name, uri, github } = config.author
|
||||
---
|
||||
|
||||
<footer role="contentinfo" class={styles.footer}>
|
||||
<Vcard />
|
||||
<section class={styles.copyright}>
|
||||
<p>
|
||||
© 2005–
|
||||
{year + ' '}
|
||||
<a href={uri} rel="me">
|
||||
{name}
|
||||
</a>
|
||||
<a href={`${github}/blog`}>
|
||||
<Icon name="GitHub" />
|
||||
View source
|
||||
</a>
|
||||
<a href="/thanks" class={styles.btc}>
|
||||
<Icon name="Bitcoin" />
|
||||
Say Thanks
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
</footer>
|
|
@ -1,7 +0,0 @@
|
|||
import React from 'react'
|
||||
import testRender from '../../../.config/jest/testRender'
|
||||
import Footer from '.'
|
||||
|
||||
describe('Footer', () => {
|
||||
testRender(<Footer />)
|
||||
})
|
|
@ -1,39 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import Icon from '../core/Icon'
|
||||
import Vcard from './Vcard'
|
||||
import styles from './index.module.css'
|
||||
|
||||
function Copyright() {
|
||||
const { name, uri, github } = useSiteMetadata().author
|
||||
const year = new Date().getFullYear()
|
||||
|
||||
return (
|
||||
<section className={styles.copyright}>
|
||||
<p>
|
||||
© 2005–
|
||||
{year + ' '}
|
||||
<a href={uri} rel="me">
|
||||
{name}
|
||||
</a>
|
||||
<a href={`${github}/blog`}>
|
||||
<Icon name="GitHub" />
|
||||
View source
|
||||
</a>
|
||||
<a href="/thanks" className={styles.btc}>
|
||||
<Icon name="Bitcoin" />
|
||||
Say Thanks
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Footer(): JSX.Element {
|
||||
return (
|
||||
<footer role="contentinfo" className={styles.footer}>
|
||||
<Vcard />
|
||||
<Copyright />
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { ReactElement, useEffect, useState } from 'react'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import Hamburger from '../core/Hamburger'
|
||||
import styles from './Menu.module.css'
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
}
|
||||
|
||||
.results {
|
||||
composes: container from '../../Layout.module.css';
|
||||
/* composes: container from '../../Layout.module.css'; */
|
||||
padding: var(--spacer) calc(var(--spacer) / 2);
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import ReactDOM from 'react-dom'
|
||||
import PostTeaser from '../../molecules/PostTeaser'
|
||||
import PostTeaser from '../../PostTeaser'
|
||||
import styles from './SearchResults.module.css'
|
||||
import SearchResultsEmpty from './SearchResultsEmpty'
|
||||
|
||||
|
@ -9,18 +8,6 @@ export interface Results {
|
|||
slug: string
|
||||
}
|
||||
|
||||
const query = graphql`
|
||||
query SearchResults {
|
||||
allMarkdownRemark {
|
||||
edges {
|
||||
node {
|
||||
...PostTeaser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
function SearchResultsPure({
|
||||
searchQuery,
|
||||
results,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import useDarkMode from '../../hooks/useDarkMode'
|
||||
import Icon from '../core/Icon'
|
||||
import styles from './ThemeSwitch.module.css'
|
||||
|
|
21
src/components/Header/index.astro
Normal file
21
src/components/Header/index.astro
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
import logo from '@images/logo.svg'
|
||||
import Menu from './Menu'
|
||||
import Search from './Search'
|
||||
import ThemeSwitch from './ThemeSwitch'
|
||||
import styles from './index.module.css'
|
||||
---
|
||||
|
||||
<header role="banner" class={styles.header}>
|
||||
<div class={styles.headerContent}>
|
||||
<a href="/" class={styles.title}>
|
||||
<img src={logo} class={styles.logo} /> kremalicious
|
||||
</a>
|
||||
|
||||
<nav aria-label="Menu" class={styles.nav}>
|
||||
<ThemeSwitch />
|
||||
<Search />
|
||||
<Menu />
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import Header from '.'
|
||||
|
||||
describe('Header', () => {
|
||||
it('renders correctly', () => {
|
||||
const { container } = render(
|
||||
<div id="document">
|
||||
<Header />
|
||||
</div>
|
||||
)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByTitle('Menu'))
|
||||
fireEvent.click(screen.getByTitle('Search'))
|
||||
|
||||
const input = screen.getByPlaceholderText('Search everything')
|
||||
fireEvent.change(input, { target: { value: 'wallpaper' } })
|
||||
})
|
||||
})
|
|
@ -1,24 +0,0 @@
|
|||
import React from 'react'
|
||||
import { ReactComponent as Logo } from '../../images/logo.svg'
|
||||
import Menu from './Menu'
|
||||
import Search from './Search'
|
||||
import ThemeSwitch from './ThemeSwitch'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Header(): JSX.Element {
|
||||
return (
|
||||
<header role="banner" className={styles.header}>
|
||||
<div className={styles.headerContent}>
|
||||
<a href="/" className={styles.title}>
|
||||
<Logo className={styles.logo} /> kremalicious
|
||||
</a>
|
||||
|
||||
<nav aria-label="Menu" className={styles.nav}>
|
||||
<ThemeSwitch />
|
||||
<Search />
|
||||
<Menu />
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { PageContext } from '../@types/Post'
|
||||
import Icon from './core/Icon'
|
||||
import styles from './Pagination.module.css'
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
|
||||
.empty {
|
||||
composes: frame from '../atoms/Image.module.css';
|
||||
/* composes: frame from '../atoms/Image.module.css'; */
|
||||
display: block;
|
||||
min-height: 95px;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAACaADAAQAAAABAAAACQAAAAAvQpmhAAAAHElEQVQYGWNgoBL4T8gcggoIGcBA0ASCCmhsBQBhFwX7u70C8QAAAABJRU5ErkJggg==');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
// import { Image } from '../core/Image'
|
||||
import PostTitle from './layouts/Post/Title'
|
||||
import PostTitle from '../gatsby/Post/Title'
|
||||
import styles from './PostTeaser.module.css'
|
||||
|
||||
export default function PostTeaser({
|
||||
|
@ -12,13 +12,12 @@ export default function PostTeaser({
|
|||
toggleSearch?: () => void
|
||||
hideDate?: boolean
|
||||
}): ReactElement {
|
||||
const { image, title, updated } = post.frontmatter
|
||||
const { slug, date } = post.fields
|
||||
const { image, title, updated, date } = post.data
|
||||
|
||||
return (
|
||||
<a
|
||||
className={styles.post}
|
||||
href={slug}
|
||||
href={post.slug}
|
||||
onClick={toggleSearch && toggleSearch}
|
||||
>
|
||||
{/* {image ? (
|
||||
|
|
|
@ -48,8 +48,8 @@
|
|||
}
|
||||
|
||||
.map {
|
||||
composes: breakout from '../Layout.module.css';
|
||||
composes: frame from './Image.module.css';
|
||||
/* composes: breakout from '../Layout.module.css';
|
||||
composes: frame from './Image.module.css'; */
|
||||
height: 220px;
|
||||
margin-top: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import styles from './Hamburger.module.css'
|
||||
|
||||
export default function Hamburger({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FunctionComponent, ReactElement } from 'react'
|
||||
import type { FunctionComponent, ReactElement } from 'react'
|
||||
// https://featherstyles.com
|
||||
// import * as Feather from '@kremalicious/react-feather'
|
||||
import {
|
||||
|
@ -58,7 +58,7 @@ const components: {
|
|||
Mastodon
|
||||
}
|
||||
|
||||
const Icon = ({ name, ...props }: { name: string }): ReactElement => {
|
||||
const Icon = ({ name, ...props }: { name: string }): ReactElement | null => {
|
||||
const IconMapped = components[name]
|
||||
// const IconFeather = (Feather as any)[name]
|
||||
if (!IconMapped) return null
|
||||
|
|
34
src/components/core/Typekit.astro
Normal file
34
src/components/core/Typekit.astro
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
const kitId = import.meta.env.PUBLIC_TYPEKIT_ID
|
||||
const _config = {
|
||||
kitId,
|
||||
scriptTimeout: 3000,
|
||||
async: true
|
||||
}
|
||||
|
||||
;(function (d) {
|
||||
var config = _config,
|
||||
h = d.documentElement,
|
||||
t = setTimeout(function () {
|
||||
h.className =
|
||||
h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive'
|
||||
}, config.scriptTimeout),
|
||||
tk = d.createElement('script'),
|
||||
f = false,
|
||||
s = d.getElementsByTagName('script')[0],
|
||||
a
|
||||
h.className += ' wf-loading'
|
||||
tk.src = 'https://use.typekit.net/' + config.kitId + '.js'
|
||||
tk.async = true
|
||||
tk.onload = tk.onreadystatechange = function () {
|
||||
a = this.readyState
|
||||
if (f || (a && a != 'complete' && a != 'loaded')) return
|
||||
f = true
|
||||
clearTimeout(t)
|
||||
try {
|
||||
Typekit.load(config)
|
||||
} catch (e) {}
|
||||
}
|
||||
s.parentNode.insertBefore(tk, s)
|
||||
})(document)
|
||||
</script>
|
|
@ -1,11 +1,22 @@
|
|||
---
|
||||
import '../../../styles/global.css'
|
||||
import '../../../styles/imports.css'
|
||||
import styles from './index.module.css'
|
||||
import Footer from '../../Footer'
|
||||
import Header from '../../Header'
|
||||
|
||||
const { title, pathname } = Astro.props
|
||||
import Footer from '@components/Footer/index.astro'
|
||||
import Header from '@components/Header/index.astro'
|
||||
import Typekit from '@components/core/Typekit.astro'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const { title, description } = Astro.props
|
||||
import config from '@config/blog.config.mjs'
|
||||
|
||||
const titleFinal = title
|
||||
? `${title} ¦ ${config.siteTitle}`
|
||||
: `${config.siteTitle} ¦ ${config.siteDescription}`
|
||||
const descriptionFinal = description
|
||||
? description.slice(0, 160)
|
||||
: config.siteDescription
|
||||
const canonicalURL = Astro.site + Astro.url.pathname.replace('/', '')
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
@ -13,45 +24,27 @@ const { title, pathname } = Astro.props
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Astro</title>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<script>
|
||||
;(function (d) {
|
||||
var config = {
|
||||
kitId: process.env.PUBLIC_TYPEKIT_ID,
|
||||
scriptTimeout: 3000,
|
||||
async: true
|
||||
},
|
||||
h = d.documentElement,
|
||||
t = setTimeout(function () {
|
||||
h.className =
|
||||
h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive'
|
||||
}, config.scriptTimeout),
|
||||
tk = d.createElement('script'),
|
||||
f = false,
|
||||
s = d.getElementsByTagName('script')[0],
|
||||
a
|
||||
h.className += ' wf-loading'
|
||||
tk.src = 'https://use.typekit.net/' + config.kitId + '.js'
|
||||
tk.async = true
|
||||
tk.onload = tk.onreadystatechange = function () {
|
||||
a = this.readyState
|
||||
if (f || (a && a != 'complete' && a != 'loaded')) return
|
||||
f = true
|
||||
clearTimeout(t)
|
||||
try {
|
||||
Typekit.load(config)
|
||||
} catch (e) {}
|
||||
}
|
||||
s.parentNode.insertBefore(tk, s)
|
||||
})(document)
|
||||
</script>
|
||||
<title>{titleFinal}</title>
|
||||
<meta name="description" content={descriptionFinal} />
|
||||
<meta property="og:title" content={title} />
|
||||
<!-- <meta property="og:image" content={image} /> -->
|
||||
<meta property="og:url" content={canonicalURL} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:creator" content={config.author.twitter} />
|
||||
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
</head>
|
||||
<body>
|
||||
<Typekit />
|
||||
<Header />
|
||||
|
||||
<main class={styles.document} id="document">
|
||||
<div class={styles.content}><slot /></div>
|
||||
<div class={styles.content}>
|
||||
{title && <h1 class={styles.pagetitle}>{title}</h1>}
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
}
|
||||
|
||||
.wide {
|
||||
composes: container;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
|
@ -76,3 +75,13 @@
|
|||
margin-right: -8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.pagetitle {
|
||||
font-size: var(--font-size-h3);
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacer);
|
||||
color: var(--text-color-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import React from 'react'
|
||||
import testRender from '../../.jest/testRender'
|
||||
import Layout from '.'
|
||||
|
||||
describe('Layout', () => {
|
||||
testRender(<Layout>Hello</Layout>)
|
||||
})
|
|
@ -1,9 +0,0 @@
|
|||
.pagetitle {
|
||||
font-size: var(--font-size-h3);
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacer);
|
||||
color: var(--text-color-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Page({
|
||||
title,
|
||||
section,
|
||||
children
|
||||
}: {
|
||||
title: string
|
||||
children: ReactNode
|
||||
section?: string
|
||||
}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<h1 className={styles.pagetitle}>{title}</h1>
|
||||
{section ? <section className={section}>{children}</section> : children}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { PageContext } from '../../../@types/Post'
|
||||
import HeadMeta, { HeadMetaProps } from '../../core/HeadMeta'
|
||||
import { Image } from '../../core/Image'
|
||||
// import { Image } from '../../core/Image'
|
||||
import Pagination from '../../Pagination'
|
||||
import Page from '../Page'
|
||||
import styles from './index.module.css'
|
||||
|
@ -11,15 +9,13 @@ export const PhotoThumb = ({
|
|||
}: {
|
||||
photo: Queries.PhotosTemplateQuery['allMarkdownRemark']['edges'][0]['node']
|
||||
}): ReactElement => {
|
||||
const { title, image } = photo.frontmatter
|
||||
const { slug } = photo.fields
|
||||
const { gatsbyImageData } = (image as any).childImageSharp
|
||||
const { title, image, slug } = photo.data
|
||||
|
||||
return (
|
||||
<article className={styles.photo}>
|
||||
{image && (
|
||||
<a href={slug}>
|
||||
<Image title={title} image={gatsbyImageData} alt={title} />
|
||||
{/* <Image title={title} image={gatsbyImageData} alt={title} /> */}
|
||||
</a>
|
||||
)}
|
||||
</article>
|
||||
|
@ -65,42 +61,3 @@ export default function Photos({
|
|||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export function Head({
|
||||
pageContext
|
||||
}: {
|
||||
pageContext: PhotosPageProps['pageContext']
|
||||
}) {
|
||||
const { currentPageNumber, numPages } = pageContext
|
||||
const meta = getMetadata(currentPageNumber, numPages)
|
||||
return <HeadMeta {...meta} slug={pageContext.slug} />
|
||||
}
|
||||
|
||||
export const photosQuery = graphql`
|
||||
query PhotosTemplate($skip: Int, $limit: Int) {
|
||||
allMarkdownRemark(
|
||||
filter: { fields: { type: { eq: "photo" } } }
|
||||
sort: { fields: { date: DESC } }
|
||||
skip: $skip
|
||||
limit: $limit
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
frontmatter {
|
||||
title
|
||||
image {
|
||||
childImageSharp {
|
||||
...PhotoFluidThumb
|
||||
}
|
||||
}
|
||||
}
|
||||
fields {
|
||||
slug
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { PageContext } from '../../../@types/Post'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import Exif from '../../core/Exif'
|
||||
import HeadMeta from '../../core/HeadMeta'
|
||||
import SchemaOrg from '../../core/HeadMeta/SchemaOrg'
|
||||
import { Image } from '../../core/Image'
|
||||
import RelatedPosts from '../../RelatedPosts'
|
||||
import PostActions from './Actions'
|
||||
import PostContent from './Content'
|
||||
import PostLead from './Lead'
|
||||
import PostLinkActions from './LinkActions'
|
||||
import PostMeta from './Meta'
|
||||
import PrevNext from './PrevNext'
|
||||
import PostTitle from './Title'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Post({
|
||||
data,
|
||||
pageContext: { next, prev }
|
||||
}: {
|
||||
data: Queries.BlogPostBySlugQuery
|
||||
pageContext: {
|
||||
next: { title: string; slug: string }
|
||||
prev: { title: string; slug: string }
|
||||
}
|
||||
}): ReactElement {
|
||||
const { post } = data
|
||||
const { title, image, linkurl, tags, updated } = post.frontmatter
|
||||
const { slug, githubLink, date, type } = post.fields
|
||||
|
||||
return (
|
||||
<>
|
||||
<article className={styles.hentry}>
|
||||
<header>
|
||||
<PostTitle
|
||||
linkurl={linkurl}
|
||||
title={title}
|
||||
date={date}
|
||||
updated={updated}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{type === 'article' && <PostLead post={post} />}
|
||||
{type === 'photo' && <PostContent post={post} />}
|
||||
|
||||
{image && (
|
||||
<Image
|
||||
className={styles.image}
|
||||
image={(image as any).childImageSharp.gatsbyImageData}
|
||||
alt={title}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === 'photo' ? (
|
||||
image?.fields && (
|
||||
<Exif exif={image.fields.exif as Queries.ImageExif} />
|
||||
)
|
||||
) : (
|
||||
<PostContent post={post} />
|
||||
)}
|
||||
|
||||
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
|
||||
<PostMeta post={post} />
|
||||
{type !== 'photo' && <PostActions githubLink={githubLink} />}
|
||||
</article>
|
||||
|
||||
<RelatedPosts isPhotos={type === 'photo'} tags={tags as string[]} />
|
||||
<PrevNext prev={prev} next={next} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Head({
|
||||
pageContext,
|
||||
data
|
||||
}: {
|
||||
pageContext: PageContext
|
||||
data: Queries.BlogPostBySlugQuery
|
||||
}): ReactElement {
|
||||
const { siteUrl } = useSiteMetadata()
|
||||
const { excerpt, rawMarkdownBody } = data.post
|
||||
const { title, image, style, updated } = data.post.frontmatter
|
||||
const { date } = data.post.fields
|
||||
const description = excerpt || rawMarkdownBody
|
||||
|
||||
return (
|
||||
<HeadMeta
|
||||
title={title}
|
||||
description={description}
|
||||
slug={pageContext.slug}
|
||||
image={image}
|
||||
>
|
||||
<>
|
||||
<SchemaOrg
|
||||
post={{
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
url: `${siteUrl}${pageContext.slug}`,
|
||||
datePublished: date,
|
||||
dateModified: updated
|
||||
}}
|
||||
/>
|
||||
{style && <link rel="stylesheet" href={style.publicURL} />}
|
||||
</>
|
||||
</HeadMeta>
|
||||
)
|
||||
}
|
1
src/env.d.ts
vendored
1
src/env.d.ts
vendored
|
@ -1 +1,2 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.actions {
|
||||
composes: breakout from '../../Layout.module.css';
|
||||
/* composes: breakout from '../../Layout.module.css'; */
|
||||
margin-top: calc(var(--spacer) * 2);
|
||||
margin-bottom: calc(var(--spacer) * 2);
|
||||
background: var(--box-background-color);
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import Icon from '../../core/Icon'
|
||||
import type { ReactElement } from 'react'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import Icon from '../../components/core/Icon'
|
||||
import styles from './Actions.module.css'
|
||||
|
||||
interface ActionProps {
|
|
@ -1,5 +1,5 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Changelog from '../../core/Changelog'
|
||||
import type { ReactElement } from 'react'
|
||||
// import Changelog from '../../core/Changelog'
|
||||
import styles from './Content.module.css'
|
||||
import PostToc from './Toc'
|
||||
|
||||
|
@ -9,7 +9,7 @@ export default function PostContent({
|
|||
post: Queries.BlogPostBySlugQuery['post']
|
||||
}): ReactElement {
|
||||
const separator = '<!-- more -->'
|
||||
const changelog = post.frontmatter.changelog
|
||||
// const changelog = post.data.changelog
|
||||
|
||||
let content = post.html
|
||||
|
||||
|
@ -25,14 +25,12 @@ export default function PostContent({
|
|||
|
||||
return (
|
||||
<>
|
||||
{post.frontmatter.toc && (
|
||||
<PostToc tableOfContents={post.tableOfContents} />
|
||||
)}
|
||||
{post.data.toc && <PostToc tableOfContents={post.tableOfContents} />}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
className={styles.content}
|
||||
/>
|
||||
{changelog && <Changelog repo={changelog} />}
|
||||
{/* {changelog && <Changelog repo={changelog} />} */}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Icon from '../../core/Icon'
|
||||
import Icon from '../../components/core/Icon'
|
||||
import styles from './LinkActions.module.css'
|
||||
import stylesMore from './More.module.css'
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import slugify from 'slugify'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import Tag from '../../core/Tag'
|
||||
import PostDate from '../../molecules/PostDate'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import Tag from '../../components/core/Tag'
|
||||
import PostDate from '../../components/PostDate'
|
||||
import styles from './Meta.module.css'
|
||||
|
||||
export default function PostMeta({
|
||||
|
@ -11,8 +11,8 @@ export default function PostMeta({
|
|||
post: Queries.BlogPostBySlugQuery['post']
|
||||
}): ReactElement {
|
||||
const siteMeta = useSiteMetadata()
|
||||
const { author, updated, tags } = post.frontmatter
|
||||
const { date, type } = post.fields
|
||||
const { author, updated, tags } = post.data
|
||||
const { date, type } = post
|
||||
|
||||
return (
|
||||
<footer className={styles.entryMeta}>
|
|
@ -1,5 +1,5 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Icon from '../../core/Icon'
|
||||
import Icon from '../../components/core/Icon'
|
||||
import styles from './More.module.css'
|
||||
|
||||
const PostMore = ({
|
|
@ -1,5 +1,5 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Icon from '../../core/Icon'
|
||||
import Icon from '../../components/core/Icon'
|
||||
import styles from './PrevNext.module.css'
|
||||
|
||||
interface Node {
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Icon from '../../core/Icon'
|
||||
import PostDate from '../../PostDate'
|
||||
import type { ReactElement } from 'react'
|
||||
import Icon from '../../components/core/Icon'
|
||||
import PostDate from '../../components/PostDate'
|
||||
import styles from './Title.module.css'
|
||||
|
||||
export default function PostTitle({
|
|
@ -1,5 +1,5 @@
|
|||
.hentry {
|
||||
composes: container from '../../Layout.module.css';
|
||||
/* composes: container from '../../Layout.module.css'; */
|
||||
width: 100%;
|
||||
padding-bottom: var(--spacer);
|
||||
}
|
||||
|
@ -13,5 +13,5 @@
|
|||
}
|
||||
|
||||
.image {
|
||||
composes: breakout from '../../Layout.module.css';
|
||||
/* composes: breakout from '../../Layout.module.css'; */
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import Post from '.'
|
||||
import link from '../../../../.config/jest/__fixtures__/link.json'
|
||||
import post from '../../../../.config/jest/__fixtures__/post.json'
|
||||
import postWithMore from '../../../../.config/jest/__fixtures__/postWithMore.json'
|
||||
import link from '../../../.config/jest/__fixtures__/link.json'
|
||||
import post from '../../../.config/jest/__fixtures__/post.json'
|
||||
import postWithMore from '../../../.config/jest/__fixtures__/postWithMore.json'
|
||||
|
||||
describe('Post', () => {
|
||||
const pageContext = {
|
60
src/gatsby/Post/index.tsx
Normal file
60
src/gatsby/Post/index.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import type { ReactElement } from 'react'
|
||||
// import Exif from '../../core/Exif'
|
||||
// import { Image } from '../../core/Image'
|
||||
// import RelatedPosts from '../../RelatedPosts'
|
||||
import PostActions from './Actions'
|
||||
import PostContent from './Content'
|
||||
import PostLead from './Lead'
|
||||
import PostLinkActions from './LinkActions'
|
||||
import PostMeta from './Meta'
|
||||
// import PrevNext from './PrevNext'
|
||||
import PostTitle from './Title'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Post({ entry }): ReactElement {
|
||||
const { title, image, linkurl, tags, updated } = entry.data
|
||||
const { slug, githubLink, date, type } = entry
|
||||
|
||||
return (
|
||||
<>
|
||||
<article className={styles.hentry}>
|
||||
<header>
|
||||
<PostTitle
|
||||
linkurl={linkurl}
|
||||
title={title}
|
||||
date={date}
|
||||
updated={updated}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{type === 'article' && <PostLead post={entry} />}
|
||||
{type === 'photo' && <PostContent post={entry} />}
|
||||
|
||||
{/* {image && (
|
||||
<Image
|
||||
className={styles.image}
|
||||
image={(image as any).childImageSharp.gatsbyImageData}
|
||||
alt={title}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
<PostContent post={entry} />
|
||||
|
||||
{/* {type === 'photo' ? (
|
||||
image?.fields && (
|
||||
<Exif exif={image.fields.exif as Queries.ImageExif} />
|
||||
)
|
||||
) : (
|
||||
<PostContent post={post} />
|
||||
)} */}
|
||||
|
||||
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
|
||||
<PostMeta post={entry} />
|
||||
{type !== 'photo' && <PostActions githubLink={githubLink} />}
|
||||
</article>
|
||||
|
||||
{/* <RelatedPosts isPhotos={type === 'photo'} tags={tags as string[]} /> */}
|
||||
{/* <PrevNext prev={prev} next={next} /> */}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@ import { PageProps, graphql } from 'gatsby'
|
|||
import HeadMeta from '../components/core/HeadMeta'
|
||||
import PostTeaser from '../components/molecules/PostTeaser'
|
||||
import { PhotoThumb } from '../components/templates/Photos'
|
||||
import PostMore from '../components/layouts/Post/More'
|
||||
import PostMore from './Post/More'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Home(
|
|
@ -1,37 +1,5 @@
|
|||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
const query = graphql`
|
||||
query SiteMetadata {
|
||||
site {
|
||||
siteMetadata {
|
||||
siteTitle
|
||||
siteTitleShort
|
||||
siteDescription
|
||||
siteUrl
|
||||
author {
|
||||
name
|
||||
email
|
||||
uri
|
||||
twitter
|
||||
mastodon
|
||||
github
|
||||
bitcoin
|
||||
ether
|
||||
}
|
||||
menu {
|
||||
title
|
||||
link
|
||||
}
|
||||
rss
|
||||
jsonfeed
|
||||
itemsPerPage
|
||||
repoContentPath
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
import siteMetadata from '../../.config/blog.config.mjs'
|
||||
|
||||
export function useSiteMetadata() {
|
||||
const { site } = useStaticQuery<Queries.SiteMetadataQuery>(query)
|
||||
return site.siteMetadata
|
||||
return siteMetadata
|
||||
}
|
||||
|
|
19
src/lib/astro.ts
Normal file
19
src/lib/astro.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { getCollection, CollectionEntry } from 'astro:content'
|
||||
|
||||
export async function loadAndFormatCollection(name: string) {
|
||||
const posts = await getCollection(name)
|
||||
|
||||
posts.forEach((post: CollectionEntry<'articles'>) => {
|
||||
// remove date from slug
|
||||
const slug = post.slug.substring(11)
|
||||
// use date from frontmatter, or grab from file path
|
||||
const date = post.data.date
|
||||
? post.data.date.toString()
|
||||
: slug.substring(1, 11)
|
||||
|
||||
post.slug = slug
|
||||
post.data.date = date
|
||||
})
|
||||
|
||||
return posts.reverse()
|
||||
}
|
|
@ -1,18 +1,26 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import LayoutBase from '@layouts/Base/index.astro'
|
||||
import { loadAndFormatCollection } from '@lib/astro'
|
||||
|
||||
// 1. Generate a new path for every collection entry
|
||||
export async function getStaticPaths() {
|
||||
const articles = await getCollection('articles');
|
||||
return articles.map(entry => ({
|
||||
params: { slug: entry.slug }, props: { entry },
|
||||
}));
|
||||
const articles = await loadAndFormatCollection('articles')
|
||||
const links = await loadAndFormatCollection('links')
|
||||
const photos = await loadAndFormatCollection('photos')
|
||||
|
||||
return [...articles, ...links, ...photos].map((entry) => {
|
||||
return {
|
||||
params: { slug: entry.slug, date: entry.date },
|
||||
props: { entry }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 2. For your template, you can get the entry directly from the prop
|
||||
const { entry } = Astro.props;
|
||||
const { Content } = await entry.render();
|
||||
const { entry } = Astro.props
|
||||
const { Content } = await entry.render()
|
||||
---
|
||||
|
||||
<h1>{entry.data.title}</h1>
|
||||
<Content />
|
||||
<LayoutBase>
|
||||
<Content />
|
||||
</LayoutBase>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import NotFound from '../../pages_gatsby/404'
|
||||
import NotFound from '../../gatsby/404'
|
||||
|
||||
describe('/404', () => {
|
||||
it('renders without crashing', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import data from '../../../.config/jest/__fixtures__/home.json'
|
||||
import Home from '../../pages_gatsby/index'
|
||||
import Home from '../../gatsby/index'
|
||||
|
||||
describe('/', () => {
|
||||
it('renders without crashing', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import Tags from '../../pages_gatsby/tags'
|
||||
import Tags from '../../gatsby/tags'
|
||||
|
||||
describe('/tags', () => {
|
||||
const data = {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<ul>
|
||||
{
|
||||
posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/${post.slug}/`}>
|
||||
<img width={720} height={360} src={post.data.image} alt="" />
|
||||
<h4 class="title">{post.data.title}</h4>
|
||||
<p class="date">{post.data.date}</p>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
|
@ -1,18 +1,12 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content'
|
||||
import PostTeaser from '../components/PostTeaser'
|
||||
import PostMore from '../components/layouts/Post/More'
|
||||
import LayoutBase from '@components/layouts/Base/index.astro'
|
||||
import PostTeaser from '@components/PostTeaser'
|
||||
import { PhotoThumb } from '../components/layouts/Photos'
|
||||
import { loadAndFormatCollection } from '@lib/astro'
|
||||
|
||||
function sortByDateNewest(collection) {
|
||||
return collection.sort(
|
||||
(a, b) => b.data.date.valueOf() - a.data.date.valueOf()
|
||||
)
|
||||
}
|
||||
|
||||
const articles = sortByDateNewest(await getCollection('articles'))
|
||||
const links = sortByDateNewest(await getCollection('links'))
|
||||
const photos = sortByDateNewest(await getCollection('photos'))
|
||||
const articles = await loadAndFormatCollection('articles')
|
||||
const links = await loadAndFormatCollection('links')
|
||||
const photos = await loadAndFormatCollection('photos')
|
||||
---
|
||||
|
||||
<style>
|
||||
|
@ -64,45 +58,31 @@ const photos = sortByDateNewest(await getCollection('photos'))
|
|||
}
|
||||
</style>
|
||||
|
||||
<section class="section">
|
||||
<div class="articles">
|
||||
{
|
||||
articles
|
||||
.slice(0, 2)
|
||||
.map((article) => <PostTeaser post={article} hideDate />)
|
||||
}
|
||||
</div>
|
||||
<div class="articles articlesLast">
|
||||
{
|
||||
articles
|
||||
.slice(2, 8)
|
||||
.map((article) => <PostTeaser post={article} hideDate />)
|
||||
}
|
||||
</div>
|
||||
<LayoutBase>
|
||||
<section class="section">
|
||||
<div class="articles">
|
||||
{
|
||||
articles
|
||||
.slice(0, 2)
|
||||
.map((article) => <PostTeaser post={article} hideDate />)
|
||||
}
|
||||
</div>
|
||||
<div class="articles articlesLast">
|
||||
{
|
||||
articles
|
||||
.slice(2, 8)
|
||||
.map((article) => <PostTeaser post={article} hideDate />)
|
||||
}
|
||||
</div>
|
||||
|
||||
<PostMore to="/archive">All Articles</PostMore>
|
||||
</section>
|
||||
<a href="/archive">All Articles</a>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<!-- <section class="section">
|
||||
<div class="photos">
|
||||
{photos.map(({ node }) => <PhotoThumb photo={node} />)}
|
||||
</div>
|
||||
|
||||
<PostMore to="/photos">All Photos</PostMore>
|
||||
</section>
|
||||
|
||||
<!-- <ul>
|
||||
{
|
||||
posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/${post.slug}/`}>
|
||||
<img width={720} height={360} src={post.data.image} alt="" />
|
||||
<h4 class="title">{post.data.title}</h4>
|
||||
<p class="date">{post.data.date}</p>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul> -->
|
||||
|
||||
../components/layouts/Photos
|
||||
</section> -->
|
||||
</LayoutBase>
|
||||
|
|
|
@ -331,24 +331,3 @@ td {
|
|||
color: #fff;
|
||||
}
|
||||
/* stylelint-enable selector-no-vendor-prefix */
|
||||
|
||||
/* More basic elements
|
||||
///////////////////////////////////// */
|
||||
|
||||
.medium-zoom-overlay {
|
||||
background-color: var(--body-background-color) !important;
|
||||
}
|
||||
|
||||
#___gatsby {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gatsby-resp-image-figure {
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
.gatsby-image-wrapper,
|
||||
.gatsby-image-wrapper-constrained {
|
||||
max-height: 100vh;
|
||||
display: block !important;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,15 @@
|
|||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }],
|
||||
"jsxImportSource": "react"
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@config/*": [".config/*"],
|
||||
"@components/*": ["src/components/*"],
|
||||
"@layouts/*": ["src/components/layouts/*"],
|
||||
"@images/*": ["src/images/*"],
|
||||
"@lib/*": ["src/lib/*"]
|
||||
}
|
||||
}
|
||||
// "exclude": ["node_modules", "public", "dist", "./**/*.js"],
|
||||
// "include": [
|
||||
|
|
Loading…
Reference in New Issue
Block a user