diff --git a/src/components/blog/Masthead.astro b/src/components/blog/Masthead.astro index 6881878..ee340be 100644 --- a/src/components/blog/Masthead.astro +++ b/src/components/blog/Masthead.astro @@ -2,6 +2,9 @@ import { Image } from "astro:assets"; import type { CollectionEntry } from "astro:content"; import FormattedDate from "@/components/FormattedDate.astro"; +import Card from "../componentsBefore/Card.astro"; +import { Icon } from "astro-icon/components"; +import Label from "../componentsBefore/Label.astro"; interface Props { content: CollectionEntry<"post">; @@ -19,19 +22,30 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = { --- { - data.coverImage && ( + data.banner && (
{data.coverImage.alt}
) } {data.draft ? (Draft) : null} + +{ + data.category && ( +
+ +
+ ) +} +

{data.title}

@@ -81,3 +95,11 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = { ) } + +{ + data.description && data.description.trim().length > 0 && ( + +
{data.description}
+
+ ) +} diff --git a/src/components/componentsBefore/Card.astro b/src/components/componentsBefore/Card.astro index d3cf64f..3be1bc6 100644 --- a/src/components/componentsBefore/Card.astro +++ b/src/components/componentsBefore/Card.astro @@ -1,10 +1,10 @@ --- -import { cn } from '@/utils' -import type { ImageMetadata } from 'astro' -import { Image } from 'astro:assets' +import { cn } from "@/utils/tailwind"; +import type { ImageMetadata } from "astro"; +import { Image } from "astro:assets"; const { - as: Tag = 'div', + as: Tag = "div", class: className, href, target, @@ -13,25 +13,25 @@ const { date, imagePath, altText, - imageClass -} = Astro.props + imageClass, +} = Astro.props; // If href is provided, use 'a' tag instead of the default or provided tag -const Component = href ? 'a' : Tag +const Component = href ? "a" : Tag; -const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/*.{jpeg,jpg,png,gif}') +const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/*.{jpeg,jpg,png,gif}"); if (imagePath) { if (!images[imagePath]) - throw new Error(`"${imagePath}" does not exist in glob: "src/assets/*.{jpeg,jpg,png,gif}"`) + throw new Error(`"${imagePath}" does not exist in glob: "src/assets/*.{jpeg,jpg,png,gif}"`); } --- ) } -
-
-

{heading}

- {subheading &&

{subheading}

} - {date &&

{date}

} +
+
+

{heading}

+ {subheading &&

{subheading}

} + {date &&

{date}

}
diff --git a/src/components/componentsBefore/Label.astro b/src/components/componentsBefore/Label.astro index 2c82745..20d23ae 100644 --- a/src/components/componentsBefore/Label.astro +++ b/src/components/componentsBefore/Label.astro @@ -1,18 +1,18 @@ --- -import { cn } from '@/utils' +import { cn } from "@/utils/tailwind"; -const { class: className, as: Tag = 'div', title, href, ...props } = Astro.props +const { class: className, as: Tag = "div", title, href, ...props } = Astro.props; --- - +

{title}

diff --git a/src/components/componentsBefore/ProjectCard.astro b/src/components/componentsBefore/ProjectCard.astro index 0608fd3..090fb78 100644 --- a/src/components/componentsBefore/ProjectCard.astro +++ b/src/components/componentsBefore/ProjectCard.astro @@ -1,24 +1,24 @@ --- -import { Image } from 'astro:assets' -import type { ImageMetadata } from 'astro' -import { cn } from '@/utils' +import { Image } from "astro:assets"; +import type { ImageMetadata } from "astro"; +import { cn } from "@/utils/tailwind"; const { - as: Tag = 'a', + as: Tag = "a", class: className, href, heading, subheading, imagePath, altText, - target -} = Astro.props + target, +} = Astro.props; -let imageComponent = null +let imageComponent = null; if (imagePath) { - const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/*.{jpeg,jpg,png,gif}') + const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/*.{jpeg,jpg,png,gif}"); if (images[imagePath]) { - imageComponent = images[imagePath] + imageComponent = images[imagePath]; } } --- @@ -26,9 +26,9 @@ if (imagePath) { ) } -
-

{heading}

-

{subheading}

+
+

{heading}

+

{subheading}

diff --git a/src/components/componentsBefore/Section.astro b/src/components/componentsBefore/Section.astro index c0a24bc..3858e5d 100644 --- a/src/components/componentsBefore/Section.astro +++ b/src/components/componentsBefore/Section.astro @@ -1,15 +1,15 @@ --- -import { cn } from '@/utils' +import { cn } from "@/utils/tailwind"; -const { class: className, title, subtitle } = Astro.props +const { class: className, title, subtitle } = Astro.props; --- -
-
-

{title}

-

{subtitle}

+
+
+

{title}

+

{subtitle}

-
+
diff --git a/src/components/note/Note.astro b/src/components/note/Note.astro index ca54da7..d52fb68 100644 --- a/src/components/note/Note.astro +++ b/src/components/note/Note.astro @@ -10,6 +10,21 @@ type Props = Polymorphic<{ as: Tag }> & { const { as: Tag = "div", note, isPreview = false } = Astro.props; const { Content } = await render(note); +const dateTimeOptions: Intl.DateTimeFormatOptions = note.data.date_created + ? { + hour: "2-digit", + minute: "2-digit", + year: "2-digit", + month: "2-digit", + day: "2-digit", + } + : { + year: "2-digit", + month: "2-digit", + day: "2-digit", + }; + +const date = note.data.date_created ?? note.data.date; ---
- +
new Date(val)), date_modified: z.date().optional(), + data_created: z.date().optional(), + category: z.string().optional().nullable(), }), }); @@ -28,9 +30,10 @@ const note = defineCollection({ schema: baseSchema.extend({ description: z.string().optional().nullable(), date: z.union([z.string(), z.date()]).transform((val) => new Date(val)), + date_modified: z.date().optional(), + data_created: z.date().optional(), tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase), }), }); - export const collections = { post, note }; diff --git a/src/data/post.ts b/src/data/post.ts index b76d262..c878a80 100644 --- a/src/data/post.ts +++ b/src/data/post.ts @@ -55,3 +55,25 @@ export function getUniqueTagsWithCount(posts: CollectionEntry<"post">[]): [strin ), ].sort((a, b) => b[1] - a[1]); } + +/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */ +export function getAllCategories(posts: Array>): string[] { + return posts.map((post) => post.data.category ?? "未分类"); +} + +/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */ +export function getUniqueCategories(posts: Array>): string[] { + return [...new Set(getAllCategories(posts))]; +} + +/** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */ +export function getUniqueCategoriesWithCount( + posts: Array>, +): Array<[string, number]> { + return [ + ...getAllCategories(posts).reduce( + (acc, t) => acc.set(t, (acc.get(t) || 0) + 1), + new Map(), + ), + ].sort((a, b) => b[1] - a[1]); +} diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro index cf23bd2..ae170dd 100644 --- a/src/layouts/BlogPost.astro +++ b/src/layouts/BlogPost.astro @@ -19,6 +19,7 @@ const { date_modified: updatedDate, date: publishDate, tags, + category, } = post.data; const socialImage = ogImage ?? `/og-image/${post.id}.png`; const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString(); @@ -32,7 +33,7 @@ const readingTime: string = remarkPluginFrontmatter.readingTime; description: description ?? "", ogImage: socialImage, title, - tags: tags.join(", "), + tags: [category, ...tags].join(", "), }} >
diff --git a/src/pages/categories/[category]/[...page].astro b/src/pages/categories/[category]/[...page].astro new file mode 100644 index 0000000..46b4458 --- /dev/null +++ b/src/pages/categories/[category]/[...page].astro @@ -0,0 +1,74 @@ +--- +import type { CollectionEntry } from "astro:content"; +import Pagination from "@/components/Paginator.astro"; +import PostPreview from "@/components/blog/PostPreview.astro"; +import { getAllPosts, getUniqueCategories } from "@/data/post"; +import PageLayout from "@/layouts/Base.astro"; +import { collectionDateSort } from "@/utils/date"; +import type { GetStaticPaths, Page } from "astro"; + +export const getStaticPaths: GetStaticPaths = async ({ paginate }) => { + const allPosts = await getAllPosts(); + const sortedPosts = allPosts.sort(collectionDateSort); + const uniqueCategories = getUniqueCategories(sortedPosts); + + return uniqueCategories.flatMap((category) => { + const filterPosts = sortedPosts.filter((post) => post.data.category === category); + return paginate(filterPosts, { + pageSize: 10, + params: { category }, + }); + }); +}; + +interface Props { + page: Page>; +} + +const { page } = Astro.props; +const { category } = Astro.params; + +const meta = { + description: `View all posts with the category - ${category}`, + title: `Category: ${category}`, +}; + +const paginationProps = { + ...(page.url.prev && { + prevUrl: { + text: "← Previous Categories", + url: page.url.prev, + }, + }), + ...(page.url.next && { + nextUrl: { + text: "Next Categories →", + url: page.url.next, + }, + }), +}; +--- + + +
+

Posts with the category {category}

+ All {" "}Categories + + +
+
+

Post List

+
    + { + page.data.map((p) => ( +
  • + +
  • + )) + } +
+ +
+
diff --git a/src/pages/categories/index.astro b/src/pages/categories/index.astro new file mode 100644 index 0000000..885a65f --- /dev/null +++ b/src/pages/categories/index.astro @@ -0,0 +1,35 @@ +--- +import { getAllPosts, getUniqueCategoriesWithCount } from "@/data/post"; +import PageLayout from "@/layouts/Base.astro"; + +const allPosts = await getAllPosts(); +const allCategories = getUniqueCategoriesWithCount(allPosts); + +const meta = { + description: "A list of all the categories I've written about in my posts", + title: "All Categories", +}; +--- + + +

Categories

+
    + { + allCategories.map(([item, val]) => ( +
  • + + #{item} + + + - {val} Post{val > 1 && "s"} + +
  • + )) + } +
+
diff --git a/src/pages/posts/[...page].astro b/src/pages/posts/[...page].astro index 9e7ea6f..9e030e1 100644 --- a/src/pages/posts/[...page].astro +++ b/src/pages/posts/[...page].astro @@ -2,7 +2,7 @@ import type { CollectionEntry } from "astro:content"; import Pagination from "@/components/Paginator.astro"; import PostPreview from "@/components/blog/PostPreview.astro"; -import { getAllPosts, getUniqueTags, groupPostsByYear } from "@/data/post"; +import { getAllPosts, getUniqueCategories, getUniqueTags, groupPostsByYear } from "@/data/post"; import PageLayout from "@/layouts/Base.astro"; import { collectionDateSort } from "@/utils/date"; import type { GetStaticPaths, Page } from "astro"; @@ -11,20 +11,23 @@ import { Icon } from "astro-icon/components"; export const getStaticPaths = (async ({ paginate }) => { const MAX_POSTS_PER_PAGE = 10; const MAX_TAGS = 7; + const MAX_CATEGORIES = 7; const allPosts = await getAllPosts(); const uniqueTags = getUniqueTags(allPosts).slice(0, MAX_TAGS); + const uniqueCategories = getUniqueCategories(allPosts).slice(0, MAX_CATEGORIES); return paginate(allPosts.sort(collectionDateSort), { pageSize: MAX_POSTS_PER_PAGE, - props: { uniqueTags }, + props: { uniqueTags, uniqueCategories }, }); }) satisfies GetStaticPaths; interface Props { page: Page>; uniqueTags: string[]; + uniqueCategories: string[]; } -const { page, uniqueTags } = Astro.props; +const { page, uniqueTags, uniqueCategories } = Astro.props; const meta = { description: "Read my collection of posts and the things that interest me", @@ -79,47 +82,96 @@ const descYearKeys = Object.keys(groupedByYear).sort((a, b) => +b - +a); }
- { - !!uniqueTags.length && ( - - ) - } + +