Skip to content

Instantly share code, notes, and snippets.

@sapegin
Last active June 26, 2023 12:38
Show Gist options
  • Save sapegin/675cbbc37ad37f2fcbab7f83ad8e3cb9 to your computer and use it in GitHub Desktop.
Save sapegin/675cbbc37ad37f2fcbab7f83ad8e3cb9 to your computer and use it in GitHub Desktop.
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,
};
---
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>
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>
);
}
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();
});
});
};
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