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 20:12:51 +01:00
parent fc7cb88049
commit 9d301a4fab
Signed by: m
GPG Key ID: 606EEEF3C479A91F
35 changed files with 246 additions and 196 deletions

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
node_modules
.yarnclean
public
.cache
plugins/gatsby-redirect-from
coverage

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

12
public/robots.txt Normal file
View File

@ -0,0 +1,12 @@
# https://platform.openai.com/docs/gptbot
User-agent: GPTBot
Disallow: /
User-agent: *
Disallow: /search/
Disallow: /page/
Disallow: /*/page/
Disallow: /tags/
Disallow: /api/
Sitemap: https://kremalicious.com/sitemap.xml

14
public/sw.js Normal file
View File

@ -0,0 +1,14 @@
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function () {
self.registration
.unregister()
.then(function () {
return self.clients.matchAll()
})
.then(function (clients) {
clients.forEach((client) => client.navigate(client.url))
})
})

View File

@ -1,8 +0,0 @@
export interface PageContext {
tag?: string
slug: string
currentPageNumber: number
numPages: number
prevPagePath?: string
nextPagePath?: string
}

View File

@ -1,6 +1,6 @@
import React, { ReactElement } from 'react'
import ReactDOM from 'react-dom'
import PostTeaser from '../../PostTeaser'
import PostTeaser from '../../PostTeaser.astro'
import styles from './SearchResults.module.css'
import SearchResultsEmpty from './SearchResultsEmpty'

View File

@ -1,6 +0,0 @@
.time {
font-style: italic;
font-size: var(--font-size-small);
color: var(--text-color-light);
margin-bottom: calc(var(--spacer) / var(--line-height));
}

View File

@ -1,19 +0,0 @@
import React, { ReactElement } from 'react'
import Time from './core/Time'
import styles from './PostDate.module.css'
export default function PostDate({
date,
updated
}: {
date: string
updated?: string
}): ReactElement {
return (
<div className={styles.time}>
<Time date={date} />
{updated && ' • updated '}
{updated && <Time date={updated} />}
</div>
)
}

View File

@ -0,0 +1,28 @@
---
// import { Image } from '../core/Image'
import PostTitle from './layouts/Post/Title.astro'
import styles from './PostTeaser.module.css'
const { title, date, updated, hideDate, image, toggleSearch, slug } =
Astro.props
---
<a class={styles.post} href={slug} onclick={toggleSearch && toggleSearch}>
{
/* {image ? (
<Image
image={(image as any).childImageSharp.gatsbyImageData}
alt={title}
/>
) : (
<span className={styles.empty} />
)} */
}
<PostTitle
title={title}
date={hideDate ? null : date}
updated={updated}
className={styles.title}
/>
</a>

View File

@ -1,7 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
import post from '../../../.jest/__fixtures__/post.json'
import PostTeaser from './PostTeaser'
import PostTeaser from './PostTeaser.astro'
describe('PostTeaser', () => {
it('renders correctly', () => {

View File

@ -1,40 +0,0 @@
import type { ReactElement } from 'react'
// import { Image } from '../core/Image'
import PostTitle from '../gatsby/Post/Title'
import styles from './PostTeaser.module.css'
export default function PostTeaser({
post,
toggleSearch,
hideDate
}: {
post: Queries.PostTeaserFragment
toggleSearch?: () => void
hideDate?: boolean
}): ReactElement {
const { image, title, updated, date } = post.data
return (
<a
className={styles.post}
href={post.slug}
onClick={toggleSearch && toggleSearch}
>
{/* {image ? (
<Image
image={(image as any).childImageSharp.gatsbyImageData}
alt={title}
/>
) : (
<span className={styles.empty} />
)} */}
<PostTitle
title={title}
date={hideDate ? null : date}
updated={updated}
className={styles.title}
/>
</a>
)
}

View File

@ -1,6 +1,6 @@
import React, { ReactElement, useState } from 'react'
import { PhotoThumb } from '../templates/Photos'
import PostTeaser from './PostTeaser'
import PostTeaser from './PostTeaser.astro'
import styles from './RelatedPosts.module.css'
const query = graphql`

View File

@ -0,0 +1,13 @@
---
import { format, formatDistance } from 'date-fns'
type Props = {
date: Date
}
const { date } = Astro.props
---
<time title={format(date, 'yyyy/MM/dd HH:mm')} datetime={date.toISOString()}>
{formatDistance(date, Date.now(), { addSuffix: true })}
</time>

View File

@ -1,13 +0,0 @@
import React, { ReactElement } from 'react'
import { format, formatDistance } from 'date-fns'
export default function Time({ date }: { date: string }): ReactElement {
const dateNew = new Date(date)
const dateIso = dateNew.toISOString()
return (
<time title={format(dateNew, 'yyyy/MM/dd HH:mm')} dateTime={dateIso}>
{formatDistance(dateNew, Date.now(), { addSuffix: true })}
</time>
)
}

View File

@ -7,7 +7,13 @@ 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
type Props = {
title?: string
pageTitle?: string
description?: string
}
const { title, pageTitle, description } = Astro.props
import config from '@config/blog.config.mjs'
const titleFinal = title
@ -42,7 +48,7 @@ const canonicalURL = Astro.site + Astro.url.pathname.replace('/', '')
<main class={styles.document} id="document">
<div class={styles.content}>
{title && <h1 class={styles.pagetitle}>{title}</h1>}
{pageTitle && <h1 class={styles.pagetitle}>{pageTitle}</h1>}
<slot />
</div>
</main>

View File

@ -0,0 +1,31 @@
---
type Props = {
body: string
}
// Extract lead paragraph from content
// Grab everything before more tag, or just first paragraph
let lead
const content = Astro.props.body
const separator = '<!-- more -->'
if (content.includes(separator)) {
lead = content.split(separator)[0]
} else {
lead = content.split('\n')[0]
}
---
<style>
.lead {
font-size: var(--font-size-large);
margin-bottom: calc(var(--spacer) * var(--line-height));
}
.index {
font-size: var(--font-size-base);
margin-top: calc(var(--spacer) * var(--line-height));
}
</style>
<div class="lead">{lead}</div>

View File

@ -0,0 +1,42 @@
---
import Time from '@components/core/Time.astro'
import Icon from '../../core/Icon'
import styles from './Title.module.css'
type Props = {
linkurl?: string
title: string
date?: Date
updated?: Date
className?: string
}
const { linkurl, title, date, updated, className } = Astro.props
const linkHostname = linkurl ? new URL(linkurl).hostname : null
---
{
linkurl ? (
<>
<h1
class={`${styles.title} ${styles.titleLink} ${className && className}`}
>
<a href={linkurl} title={`Go to source: ${linkurl}`}>
{title} <Icon name="ExternalLink" />
</a>
</h1>
<div class={styles.linkurl}>{linkHostname}</div>
</>
) : (
<>
<h1 class={`${styles.title} ${className && className}`}>{title}</h1>
{date && (
<div class={styles.date}>
<Time date={date} />
{updated && ' • updated '}
{updated && <Time date={updated} />}
</div>
)}
</>
)
}

View File

@ -27,3 +27,10 @@
margin-top: -2rem;
margin-bottom: var(--spacer);
}
.date {
font-style: italic;
font-size: var(--font-size-small);
color: var(--text-color-light);
margin-bottom: calc(var(--spacer) / var(--line-height));
}

View File

@ -0,0 +1,48 @@
---
import type { CollectionEntry } from 'astro:content'
import LayoutBase from '@layouts/Base/index.astro'
import Title from './Title.astro'
import Lead from './Lead.astro'
type Props = CollectionEntry<'articles' | 'links' | 'photos'>
const { collection, body } = Astro.props
console.log(Astro.props)
const { title, date, updated, image, linkurl } = Astro.props.data
---
<style>
.entry {
/* composes: container from '../../Layout.module.css'; */
width: 100%;
padding-bottom: var(--spacer);
}
.entry > a {
display: block;
}
.entry:only-child {
padding-bottom: var(--spacer);
}
.image {
/* composes: breakout from '../../Layout.module.css'; */
}
</style>
<LayoutBase title={title}>
<article class="entry">
<header>
<Title linkurl={linkurl} title={title} date={date} updated={updated} />
</header>
{collection === 'articles' && <Lead body={body} />}
<div class="hero-image">
{image && <img width={1020} height={510} src={image} alt="" />}
</div>
<slot />
</article>
</LayoutBase>

View File

@ -10,12 +10,16 @@ const articles = defineCollection({
.string()
.or(z.date())
.optional()
.transform((val: string) => new Date(val)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((val: string) => (val ? new Date(val) : undefined)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
image: z.string().optional(),
tags: z.array(z.string()).optional(),
download: z.string().optional(),
@ -34,12 +38,16 @@ const links = defineCollection({
.string()
.or(z.date())
.optional()
.transform((val: string) => new Date(val)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((str: string) => (str ? new Date(str) : undefined)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
tags: z.array(z.string()).optional()
})
})
@ -52,12 +60,16 @@ const photos = defineCollection({
.string()
.or(z.date())
.optional()
.transform((val: string) => new Date(val)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
updated: z
.string()
.or(z.date())
.optional()
.transform((str: string) => (str ? new Date(str) : undefined)),
.transform((val: string | Date | undefined) =>
val ? new Date(val) : undefined
),
image: z.string(),
tags: z.array(z.string()).optional()
})

View File

@ -1,9 +0,0 @@
.lead {
font-size: var(--font-size-large);
margin-bottom: calc(var(--spacer) * var(--line-height));
}
.index {
font-size: var(--font-size-base);
margin-top: calc(var(--spacer) * var(--line-height));
}

View File

@ -1,31 +0,0 @@
import React, { ReactElement } from 'react'
import styles from './Lead.module.css'
// Extract lead paragraph from content
// Grab everything before more tag, or just first paragraph
const PostLead = ({
post,
className
}: {
post: Queries.BlogPostBySlugQuery['post']
className?: string
}): ReactElement => {
let lead
const content = post.html
const separator = '<!-- more -->'
if (content.includes(separator)) {
lead = content.split(separator)[0]
} else {
lead = content.split('\n')[0]
}
return (
<div
className={`${styles.lead} ${className && className}`}
dangerouslySetInnerHTML={{ __html: lead }}
/>
)
}
export default PostLead

View File

@ -1,40 +0,0 @@
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({
linkurl,
title,
date,
updated,
className
}: {
linkurl?: string
title: string
date?: string
updated?: string
className?: string
}): ReactElement {
const linkHostname = linkurl ? new URL(linkurl).hostname : null
return linkurl ? (
<>
<h1
className={`${styles.title} ${styles.titleLink} ${
className && className
}`}
>
<a href={linkurl} title={`Go to source: ${linkurl}`}>
{title} <Icon name="ExternalLink" />
</a>
</h1>
<div className={styles.linkurl}>{linkHostname}</div>
</>
) : (
<>
<h1 className={`${styles.title} ${className && className}`}>{title}</h1>
{date && <PostDate date={date} updated={updated} />}
</>
)
}

View File

@ -4,11 +4,11 @@ import type { ReactElement } from 'react'
// import RelatedPosts from '../../RelatedPosts'
import PostActions from './Actions'
import PostContent from './Content'
import PostLead from './Lead'
import PostLead from '../../components/layouts/Post/Lead'
import PostLinkActions from './LinkActions'
import PostMeta from './Meta'
// import PrevNext from './PrevNext'
import PostTitle from './Title'
import PostTitle from '../../components/layouts/Post/Title'
import styles from './index.module.css'
export default function Post({ entry }): ReactElement {

View File

@ -1,18 +1,20 @@
import { getCollection, CollectionEntry } from 'astro:content'
export async function loadAndFormatCollection(name: string) {
export async function loadAndFormatCollection(
name: 'articles' | 'links' | 'photos'
) {
const posts = await getCollection(name)
posts.forEach((post: CollectionEntry<'articles'>) => {
posts.forEach((post: CollectionEntry<'articles' | 'links' | 'photos'>) => {
// remove date from slug
const slug = post.slug.substring(11)
const slug = post.slug.substring(11) as CollectionEntry<
'articles' | 'links' | 'photos'
>['slug']
// use date from frontmatter, or grab from file path
const date = post.data.date
? post.data.date.toString()
: slug.substring(1, 11)
const date = post.data.date ? post.data.date : slug.substring(1, 11)
post.slug = slug
post.data.date = date
post.data.date = new Date(date)
})
return posts.reverse()

View File

@ -1,8 +1,7 @@
---
import LayoutBase from '@layouts/Base/index.astro'
import LayoutPost from '@layouts/Post/index.astro'
import { loadAndFormatCollection } from '@lib/astro'
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const articles = await loadAndFormatCollection('articles')
const links = await loadAndFormatCollection('links')
@ -10,17 +9,16 @@ export async function getStaticPaths() {
return [...articles, ...links, ...photos].map((entry) => {
return {
params: { slug: entry.slug, date: entry.date },
params: { slug: entry.slug },
props: { entry }
}
})
}
// 2. For your template, you can get the entry directly from the prop
const { entry } = Astro.props
const { Content } = await entry.render()
---
<LayoutBase>
<LayoutPost {...entry}>
<Content />
</LayoutBase>
</LayoutPost>

View File

@ -1,6 +1,6 @@
---
import LayoutBase from '@components/layouts/Base/index.astro'
import PostTeaser from '@components/PostTeaser'
import PostTeaser from '@components/PostTeaser.astro'
import { PhotoThumb } from '../components/layouts/Photos'
import { loadAndFormatCollection } from '@lib/astro'
@ -64,14 +64,18 @@ const photos = await loadAndFormatCollection('photos')
{
articles
.slice(0, 2)
.map((article) => <PostTeaser post={article} hideDate />)
.map((article) => (
<PostTeaser slug={article.slug} hideDate {...article.data} />
))
}
</div>
<div class="articles articlesLast">
{
articles
.slice(2, 8)
.map((article) => <PostTeaser post={article} hideDate />)
.map((article) => (
<PostTeaser slug={article.slug} hideDate {...article.data} />
))
}
</div>