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 => (
+
+ ))}
+
+ ) : (
+ 記事がありません。
+ )
+ }
+
+
+
+