diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index 3f97448..ee7fbb1 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -3,6 +3,7 @@ import { Badge } from '@/components/ui/badge'; import member from '@/data/member'; import type { CollectionEntry } from 'astro:content'; import MemberIcon from '@/components/MemberIcon'; +import { cn } from '@/lib/utils'; type Props = { blog: CollectionEntry<'blog'>; @@ -13,10 +14,20 @@ const BlogCard = ({ blog, color }: Props) => { const author = member.find(m => m.name === blog.data.author); return ( - - + + +   + +
{blog.data.title} @@ -29,27 +40,34 @@ const BlogCard = ({ blog, color }: Props) => {
{blog.data.tags.map(tag => { return ( - - #{tag} - + + + #{tag} + + ); })}
{author && ( Author:  - + {author.name} - + )}
- +
); }; diff --git a/src/components/BlogFilter.astro b/src/components/BlogFilter.astro new file mode 100644 index 0000000..f969332 --- /dev/null +++ b/src/components/BlogFilter.astro @@ -0,0 +1,30 @@ +--- +import getBlog from '@/lib/getBlog'; +import { Badge } from '@/components/ui/badge'; +const blogs = await getBlog(); +--- + +
+ Author: +
+ { + Array.from(new Set(blogs.map(blog => blog.data.author))).map(author => ( + + {author} + + )) + } +
+
+
+ Tag: +
+ { + Array.from(new Set(blogs.map(blog => blog.data.tags).flat())).map(tag => ( + + #{tag} + + )) + } +
+
diff --git a/src/lib/getTags.ts b/src/lib/getTags.ts new file mode 100644 index 0000000..882fa2d --- /dev/null +++ b/src/lib/getTags.ts @@ -0,0 +1,14 @@ +import { getCollection } from 'astro:content'; + +const getTags = async () => { + const blogs = await getCollection('blog'); + // NOTE: 特殊文字を含むタグがある場合に備えてエンコード + const allTags = blogs.flatMap(blog => + blog.data.tags.map(tag => encodeURIComponent(tag)) + ); + const uniqueTags = [...new Set(allTags)].sort(); + + return uniqueTags; +}; + +export default getTags; diff --git a/src/pages/blog.astro b/src/pages/blog.astro index f2bd7de..1838f11 100644 --- a/src/pages/blog.astro +++ b/src/pages/blog.astro @@ -7,6 +7,7 @@ import BlogCard from '@/components/BlogCard'; import Breadcrumb from '@/components/Breadcrumb.astro'; import getBlog from '@/lib/getBlog'; import siteInfo from '@/data/siteInfo'; +import BlogFilter from '@/components/BlogFilter.astro'; const title = 'Blog'; const blogs = await getBlog(); const description = `${siteInfo.appName} Memberが執筆したBlogの一覧ページです。直近では「${blogs[0].data.title}」、「${blogs[1].data.title}」、「${blogs[2].data.title}」を投稿しています。`; @@ -15,6 +16,7 @@ const description = `${siteInfo.appName} Memberが執筆したBlogの一覧ペ
+
{blogs.map(blog => )}
diff --git a/src/pages/blog/author/[name].astro b/src/pages/blog/author/[name].astro new file mode 100644 index 0000000..28c12b9 --- /dev/null +++ b/src/pages/blog/author/[name].astro @@ -0,0 +1,52 @@ +--- +import '@/styles/globals.css'; +import Layout from '@/layouts/Layout.astro'; +import PageTitle from '@/components/PageTitle.astro'; +import Section from '@/components/Section.astro'; +import BlogCard from '@/components/BlogCard'; +import Breadcrumb from '@/components/Breadcrumb.astro'; +import BlogFilter from '@/components/BlogFilter.astro'; +import { getCollection } from 'astro:content'; +import member from '@/data/member'; + +export async function getStaticPaths() { + const authors = member.map(m => m.name); + + return authors.map(author => ({ + params: { name: author }, + })); +} + +const { name } = Astro.params; +const blogs = await getCollection('blog'); +const filteredBlogs = blogs + .filter(blog => blog.data.author === name) + .sort((a, b) => { + const dateA = new Date(a.data.pubDate).getTime(); + const dateB = new Date(b.data.pubDate).getTime(); + return dateB - dateA; + }); + +const title = `Blog - ${name}`; +const description = `${name}が執筆したBlogの一覧ページです。`; +--- + + + +
+ + { + filteredBlogs.length > 0 ? ( +
+ {filteredBlogs.map(blog => ( + + ))} +
+ ) : ( +

記事がありません。

+ ) + } +
+ + +
diff --git a/src/pages/blog/tag/[tag].astro b/src/pages/blog/tag/[tag].astro new file mode 100644 index 0000000..20a8168 --- /dev/null +++ b/src/pages/blog/tag/[tag].astro @@ -0,0 +1,57 @@ +--- +import '@/styles/globals.css'; +import Layout from '@/layouts/Layout.astro'; +import PageTitle from '@/components/PageTitle.astro'; +import Section from '@/components/Section.astro'; +import BlogCard from '@/components/BlogCard'; +import Breadcrumb from '@/components/Breadcrumb.astro'; +import BlogFilter from '@/components/BlogFilter.astro'; +import { getCollection } from 'astro:content'; +import getTags from '@/lib/getTags'; + +export async function getStaticPaths() { + const tags = await getTags(); + + return tags.map(tag => ({ + // NOTE: tagに特殊文字が含まれる場合に備えてエンコード + params: { tag }, + })); +} + +const { tag } = Astro.params; +const blogs = await getCollection('blog'); +const filteredBlogs = blogs + .filter(blog => + blog.data.tags.map(tag => encodeURIComponent(tag)).includes(tag) + ) + .sort((a, b) => { + const dateA = new Date(a.data.pubDate).getTime(); + const dateB = new Date(b.data.pubDate).getTime(); + return dateB - dateA; + }); + +// NOTE: tagをデコードして表示用に戻す +const decodedTag = decodeURIComponent(tag); +const title = `Blog - #${decodedTag}`; +const description = `#${decodedTag}タグが付けられたBlogの一覧ページです。`; +--- + + + +
+ + { + filteredBlogs.length > 0 ? ( +
+ {filteredBlogs.map(blog => ( + + ))} +
+ ) : ( +

記事がありません。

+ ) + } +
+ + +