記事の属性(主に日付)
現状はタイトルだけで、これでは不便なのでいろいろと足す。
- 作成日:
created - 更新日:
modified - タグ:
tags - アイキャッチ:
image(← 使わないだろ…)
/src/content/blog/post-1.md
---
title: ブログ記事1
created: 2023-10-01
modified: 2023-10-01
---
## 最初のブログ記事です
あいうえお
追加した属性をコレクションが取得できるようにする。現状だと /src/content/blog/ 以下に空のファイルを作っただけで Astro 君が激怒するため、すべて省略可能にする。
/src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string().optional(),
created: z.date().optional(),
modified: z.date().optional(),
tags: z.array(z.string()).optional(),
image: z.string().optional(),
}),
});
export const collections = {
blog: blogCollection,
};
日付を表示する
日付(created)をそのまま参照すると Date オブジェクトのクソ長い文字列が表示されるのでフォーマットしたい。また、日付フォーマットは記事本体でも使うだろうから、<time> をコンポーネントにしてしまおう。
なお <time> 要素は書式を守る必要がある。
/src/components/BlogTime.astro
---
interface Props { date: Date; class?: string }
const { date, ...attr } = Astro.props;
const dateTime = (date: Date) => date.toISOString();
const dateStr = (date: Date) => {
return date.toLocaleDateString('ja-JP', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
};
---
<time datetime={dateTime(date)} {...attr}>
<slot />
{dateStr(date)}
</time>
さっそく目次で <BlogTime> をインポートする。
/src/pages/blog.astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../layouts/BlogLayout.astro';
import BlogTime from '../components/BlogTime.astro';
const posts = await getCollection('blog');
---
<BlogLayout title="Blog">
<h2>記事一覧</h2>
<ul>
{
posts.map((post) => (
<li>
{post.data.created && <BlogTime date={post.data.created} />}
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
</li>
))
}
</ul>
</BlogLayout>
記事の日付は <header> に入れたいので、記事 → レイアウト → <header> とリレーしていくことにする。
- 記事ファイルである
[slug].astroはそのままでよい(すでにレイアウトにfrontmatterを渡している) - レイアウトの
BlogLayoutはfrontmatter.createdを受け取ってHeaderへ<BlogTime>を投げる。プロパティとして渡してもよいが、せっかくなので<slot>を使ってしまおう Header.astroもそのまま
/src/layouts/BlogLayout.astro
---
import BlogTime from '../components/BlogTime.astro';
import Head from './Head.astro';
import Header from './Header.astro';
import Footer from './Footer.astro';
import '../styles/style.css';
interface Props {
title?: string;
frontmatter?: {
title?: string;
created?: Date;
};
}
const { title = 'ワイのAstro🚀Blog', frontmatter } = Astro.props;
const pageTitle = frontmatter?.title ?? title;
---
<html lang="ja">
<Head title={pageTitle} />
<body>
<Header title={heading}>
{frontmatter?.created && <BlogTime date={frontmatter?.created}>作成日: </BlogTime>}
</Header>
<main>
<slot />
</main>
<Footer />
</body>
</html>
必須の属性
すべての属性は省略できるけど、title と created は記事として表示するためには必須としたい。
まずは目次から。getCollection() の filter コールバックで title と created の両方が未設定ではない記事をリスト入りしている。
/src/pages/blog.astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../layouts/BlogLayout.astro';
import formatDate from '../components/formatDate';
const posts = await getCollection('blog', ({ data }) => {
return data.title !== undefined && data.created !== undefined;
});
---
記事も同様に。
/src/posts/[slug].astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
import formatDate from '../../components/formatDate';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => {
return data.title !== undefined && data.created !== undefined;
});
return posts.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
なんかガバガバに見えるが、たとえば title には型(string | undefined)が設定されており、違反すると Astro 君がお怒りになるので大丈夫だったりする。
新しい順に並べたい
目次で created の降順に並べ替える。テストも兼ねて新しい記事を作るが割愛する。
/src/pages/blog.astro
---
import { getCollection } from 'astro:content';
import BlogLayout from '../layouts/BlogLayout.astro';
import formatDate from '../components/formatDate';
const posts = await getCollection('blog', ({ data }) => {
return data.title !== undefined && data.created !== undefined;
});
posts.sort((a, b) => {
if (!a.data.created || !b.data.created) return 0;
return b.data.created.valueOf() - a.data.created.valueOf();
});
---