-
-
Save sapegin/675cbbc37ad37f2fcbab7f83ad8e3cb9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z, defineCollection } from 'astro:content'; | |
const blog = defineCollection({ | |
type: 'content', | |
schema: z.object({ | |
title: z.string(), | |
tags: z.array(z.string()), | |
date: z.date(), | |
description: z.string().optional(), | |
source: z.string().optional(), | |
}), | |
}); | |
export const collections = { | |
blog, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
import { groupBy, sortBy } from 'lodash'; | |
import { getCollection } from 'astro:content'; | |
import Layout from '../../layouts/Layout.astro'; | |
import { BlogPage } from '../../templates/BlogPage'; | |
import type { Post } from '../../types/Post'; | |
import { entryToPost } from '../../util/entryToPost'; | |
const groupByYear = (posts: Post[]) => | |
groupBy(posts, (post) => post.date.getFullYear()); | |
const getYears = (postsByYear: Record<string, Post[]>): string[] => { | |
const years = Object.keys(postsByYear); | |
years.sort(); | |
years.reverse(); | |
return years; | |
}; | |
const entries = await getCollection('blog'); | |
const posts = sortBy(entries.map(entryToPost), (x) => -x.date); | |
const postsByYear = groupByYear(posts); | |
const years = getYears(postsByYear); | |
--- | |
<Layout title="Blog"> | |
<BlogPage url="/blog/" postsByYear={postsByYear} years={years} /> | |
</Layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Stack, Heading, VisuallyHidden, PostList } from '../components'; | |
import { Page } from './Page'; | |
import type { Post } from '../types/Post'; | |
type Props = { | |
url: string; | |
years: string[]; | |
postsByYear: Record<string, Post[]>; | |
}; | |
export function BlogPage({ url, years, postsByYear }: Props) { | |
return ( | |
<Page url={url}> | |
<main> | |
<VisuallyHidden as="h1">Artem Sapegin’s blog posts</VisuallyHidden> | |
<Stack gap="l"> | |
{years.map((year) => ( | |
<Stack key={year} as="section" gap="m"> | |
<Heading as="h2" level={2}> | |
{year} | |
</Heading> | |
<PostList posts={postsByYear[year]} /> | |
</Stack> | |
))} | |
</Stack> | |
</main> | |
</Page> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import path from 'path'; | |
import { sortBy } from 'lodash'; | |
import { GatsbyNode } from 'gatsby'; | |
import { createFilePath } from 'gatsby-source-filesystem'; | |
import { MakdownNode, PostsQuery } from './src/types/GraphQL'; | |
const MAX_RELATED = 5; | |
function getRelatedPosts( | |
posts: { slug: string; tags: string[]; timestamp: string }[], | |
{ slug, tags }: { slug: string; tags: string[] } | |
) { | |
const weighted = posts | |
.filter((d) => d.slug !== slug) | |
.map((d) => { | |
const common = (d.tags || []).filter((t) => (tags || []).includes(t)); | |
return { | |
...d, | |
weight: common.length * Number(d.timestamp), | |
}; | |
}) | |
.filter((d) => d.weight > 0); | |
const sorted = sortBy(weighted, 'weight').reverse(); | |
return sorted.slice(0, MAX_RELATED); | |
} | |
export const createPages: GatsbyNode['createPages'] = ({ | |
graphql, | |
actions: { createPage }, | |
}) => { | |
return new Promise((resolve, reject) => { | |
graphql<PostsQuery>(` | |
{ | |
allMarkdownRemark(limit: 1000) { | |
edges { | |
node { | |
frontmatter { | |
layout | |
title | |
tags | |
timestamp: date(formatString: "X") | |
} | |
fields { | |
slug | |
} | |
} | |
} | |
} | |
} | |
`).then((result) => { | |
if (result.errors) { | |
reject(result.errors); | |
return; | |
} | |
if (!result.data) { | |
reject(); | |
return; | |
} | |
const docs = result.data.allMarkdownRemark.edges.map((e) => ({ | |
...e.node.frontmatter, | |
...e.node.fields, | |
})); | |
docs.forEach(({ layout, tags, slug }) => { | |
createPage({ | |
path: slug, | |
component: path.resolve(`${__dirname}/src/layouts/${layout}.tsx`), | |
context: { | |
slug, | |
related: getRelatedPosts(docs, { | |
slug, | |
tags, | |
}), | |
dateFormat: DATE_FORMAT, | |
}, | |
}); | |
}); | |
resolve(); | |
}); | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { graphql } from 'gatsby'; | |
import { Stack, Heading, VisuallyHidden } from 'tamia'; | |
import groupBy from 'just-group-by'; | |
import Page from './Page'; | |
import PostList from '../components/PostList'; | |
import Metatags from '../components/Metatags'; | |
type Fields = { | |
slug: string; | |
}; | |
type Frontmatter = { | |
title: string; | |
tags: string[]; | |
year: string; | |
dateTime: string; | |
}; | |
type Post = Fields & Frontmatter; | |
type GroupedPosts = { | |
[year: string]: Post[]; | |
}; | |
const groupByYear = (posts: Post[]) => groupBy(posts, (post) => post.year); | |
const getYears = (postsByYear: GroupedPosts): string[] => { | |
const years = Object.keys(postsByYear); | |
years.sort(); | |
years.reverse(); | |
return years; | |
}; | |
type Props = { | |
data: { | |
allMarkdownRemark: { | |
edges: { | |
node: { | |
fields: Fields; | |
frontmatter: Frontmatter; | |
}; | |
}[]; | |
}; | |
}; | |
location: { | |
pathname: string; | |
}; | |
}; | |
const Index = ({ | |
data: { | |
allMarkdownRemark: { edges }, | |
}, | |
location: { pathname }, | |
}: Props) => { | |
const posts = edges.map(({ node }) => ({ | |
...node.fields, | |
...node.frontmatter, | |
})); | |
const postsByYear = groupByYear(posts); | |
const years = getYears(postsByYear); | |
return ( | |
<Page url={pathname}> | |
<Metatags slug={pathname} /> | |
<main> | |
<VisuallyHidden as="h1">Artem Sapegin’s blog posts</VisuallyHidden> | |
<Stack gap="l"> | |
{years.map((year) => ( | |
<Stack key={year} as="section" gap="m"> | |
<Heading as="h2" level={2}> | |
{year} | |
</Heading> | |
<PostList posts={postsByYear[year]} /> | |
</Stack> | |
))} | |
</Stack> | |
</main> | |
</Page> | |
); | |
}; | |
export default Index; | |
export const pageQuery = graphql` | |
query IndexPage { | |
allMarkdownRemark( | |
filter: { fileAbsolutePath: { regex: "/all/.*/" } } | |
sort: { fields: [frontmatter___date], order: DESC } | |
) { | |
edges { | |
node { | |
fields { | |
slug | |
} | |
frontmatter { | |
title | |
tags | |
year: date(formatString: "YYYY") | |
dateTime: date(formatString: "YYYY-MM-DD") | |
} | |
} | |
} | |
} | |
} | |
`; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment