mirror of
https://github.com/kremalicious/blog.git
synced 2024-06-28 16:48:00 +02:00
refactor
This commit is contained in:
parent
fc7cb88049
commit
9d301a4fab
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
|||
node_modules
|
||||
.yarnclean
|
||||
public
|
||||
.cache
|
||||
plugins/gatsby-redirect-from
|
||||
coverage
|
||||
|
|
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
12
public/robots.txt
Normal file
12
public/robots.txt
Normal 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
14
public/sw.js
Normal 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))
|
||||
})
|
||||
})
|
8
src/@types/Post.d.ts
vendored
8
src/@types/Post.d.ts
vendored
|
@ -1,8 +0,0 @@
|
|||
export interface PageContext {
|
||||
tag?: string
|
||||
slug: string
|
||||
currentPageNumber: number
|
||||
numPages: number
|
||||
prevPagePath?: string
|
||||
nextPagePath?: string
|
||||
}
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
28
src/components/PostTeaser.astro
Normal file
28
src/components/PostTeaser.astro
Normal 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>
|
|
@ -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', () => {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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`
|
||||
|
|
13
src/components/core/Time.astro
Normal file
13
src/components/core/Time.astro
Normal 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>
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
|
|
31
src/components/layouts/Post/Lead.astro
Normal file
31
src/components/layouts/Post/Lead.astro
Normal 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>
|
42
src/components/layouts/Post/Title.astro
Normal file
42
src/components/layouts/Post/Title.astro
Normal 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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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));
|
||||
}
|
48
src/components/layouts/Post/index.astro
Normal file
48
src/components/layouts/Post/index.astro
Normal 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>
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -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} />}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user