1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-06-28 16:48:00 +02:00
This commit is contained in:
Matthias Kretschmann 2023-08-30 18:44:19 +01:00
parent 84ea68afb0
commit fc7cb88049
Signed by: m
GPG Key ID: 606EEEF3C479A91F
68 changed files with 337 additions and 539 deletions

View File

@ -3,5 +3,6 @@ import react from '@astrojs/react'
// https://astro.build/config
export default defineConfig({
integrations: [react()]
integrations: [react()],
site: 'https://kremalicious.com'
})

View File

@ -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 .",

View File

@ -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()
})
})

View File

@ -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'

View File

@ -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);

View File

@ -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} />
</>
)
}

View 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>
&copy; 2005&ndash;
{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>

View File

@ -1,7 +0,0 @@
import React from 'react'
import testRender from '../../../.config/jest/testRender'
import Footer from '.'
describe('Footer', () => {
testRender(<Footer />)
})

View File

@ -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>
&copy; 2005&ndash;
{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>
)
}

View File

@ -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'

View File

@ -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;

View File

@ -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,

View File

@ -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'

View 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>

View File

@ -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' } })
})
})

View File

@ -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>
)
}

View File

@ -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'

View File

@ -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==');

View File

@ -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 ? (

View File

@ -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);
}

View File

@ -1,4 +1,4 @@
import React, { ReactElement } from 'react'
import type { ReactElement } from 'react'
import styles from './Hamburger.module.css'
export default function Hamburger({

View File

@ -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

View 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>

View File

@ -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 />

View File

@ -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;
}

View File

@ -1,7 +0,0 @@
import React from 'react'
import testRender from '../../.jest/testRender'
import Layout from '.'
describe('Layout', () => {
testRender(<Layout>Hello</Layout>)
})

View File

@ -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;
}

View File

@ -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}
</>
)
}

View File

@ -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
}
}
}
}
}
`

View File

@ -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
View File

@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@ -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);

View File

@ -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 {

View File

@ -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} />} */}
</>
)
}

View File

@ -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'

View File

@ -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}>

View File

@ -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 = ({

View File

@ -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 {

View File

@ -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({

View File

@ -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'; */
}

View File

@ -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
View 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} /> */}
</>
)
}

View File

@ -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(

View File

@ -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
View 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()
}

View File

@ -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>

View File

@ -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', () => {

View File

@ -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', () => {

View File

@ -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 = {

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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": [