diff --git a/next.config.mjs b/next.config.mjs index d730e0c..b253027 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -12,12 +12,16 @@ const nextConfig = { images: { unoptimized: true, }, + productionBrowserSourceMaps: true, pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"], - webpack: (config, { isServer }) => { + webpack: (config, { dev, isServer }) => { if (!isServer) { config.externals = { // 添加其他需要从 CDN 加载的依赖 }; + if (!dev && !isServer) { + config.optimization.usedExports = true; + } } return config; }, diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 3077f41..408460d 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,10 +1,10 @@ "use client"; import Image from "next/image"; -import { useTheme } from "next-themes"; +import { useThemeType } from "@/hooks"; export default function About() { - const { theme } = useTheme(); + const { theme } = useThemeType(); return (

博客文章

-
-
- - -
-
- - -
-
-
- handleTagClick(null)} - > - 全部 - - {visibleTags.map((tag) => ( - handleTagClick(tag)} - > - {tag} - - ))} - {allTags.length > INITIAL_TAG_COUNT && ( - - )} -
+ + setSortOption(value)} + /> + + setIsTagsExpanded(!isTagsExpanded)} + initialTagCount={INITIAL_TAG_COUNT} + /> +
{isLoading ? ( Array.from({ length: POSTS_PER_PAGE }).map((_, index) => ( @@ -226,48 +161,12 @@ export default function BlogList() { ) : currentPosts.length > 0 ? ( {currentPosts.map((post, index) => ( - - - - - - {post.title} - - - - {post.date} | {post.author} - - - -

- {post.excerpt} -

-
- - {post.tags.map((tag) => ( - handleTagClick(tag)} - > - {tag} - - ))} - -
-
+ post={post} + onTagClick={handleTagClick} + index={index} + /> ))}
) : ( @@ -288,30 +187,13 @@ export default function BlogList() {
)}
+ {filteredPostsMemo.length > 0 && ( - -
- {Array.from( - { length: Math.ceil(filteredPostsMemo.length / POSTS_PER_PAGE) }, - (_, i) => ( - - ) - )} -
-
+ )} ); diff --git a/src/app/blog/BlogPostCard.tsx b/src/app/blog/BlogPostCard.tsx new file mode 100644 index 0000000..77e33cd --- /dev/null +++ b/src/app/blog/BlogPostCard.tsx @@ -0,0 +1,65 @@ +import Link from "next/link"; +import { motion } from "framer-motion"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, + Badge, +} from "@/components/ui"; +import { BlogPost } from "@/data/blogPosts"; + +interface BlogPostCardProps { + post: BlogPost; + onTagClick: (tag: string) => void; + index: number; +} + +const cardVariants = { + hidden: { opacity: 0, y: -20 }, + visible: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 20 }, +}; + +export function BlogPostCard({ post, onTagClick, index }: BlogPostCardProps) { + return ( + + + + + +

{post.title}

+ +
+ + {post.date} | {post.author} + +
+ +

{post.excerpt}

+
+ + {post.tags.map((tag) => ( + onTagClick(tag)} + > + {tag} + + ))} + +
+
+ ); +} diff --git a/src/app/blog/BlogSearch.tsx b/src/app/blog/BlogSearch.tsx new file mode 100644 index 0000000..909c679 --- /dev/null +++ b/src/app/blog/BlogSearch.tsx @@ -0,0 +1,43 @@ +import { Label, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui"; + +type SortOption = "date" | "title"; + +interface BlogSearchProps { + searchTerm: string; + onSearchChange: (e: React.ChangeEvent) => void; + sortOption: SortOption; + onSortChange: (value: SortOption) => void; +} + +export function BlogSearch({ searchTerm, onSearchChange, sortOption, onSortChange }: BlogSearchProps) { + return ( +
+
+ + +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/blog/Pagination.tsx b/src/app/blog/Pagination.tsx new file mode 100644 index 0000000..6036561 --- /dev/null +++ b/src/app/blog/Pagination.tsx @@ -0,0 +1,39 @@ +import { Button } from "@/components/ui"; +import { motion } from "framer-motion"; + +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (pageNumber: number) => void; +} + +const cardVariants = { + hidden: { opacity: 0, y: -20 }, + visible: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 20 }, +}; + +export function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) { + return ( + +
+ {Array.from({ length: totalPages }, (_, i) => ( + + ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/blog/TagList.tsx b/src/app/blog/TagList.tsx new file mode 100644 index 0000000..55aab1a --- /dev/null +++ b/src/app/blog/TagList.tsx @@ -0,0 +1,55 @@ +import { Badge, Button } from "@/components/ui"; +import { ChevronDown, ChevronUp } from "lucide-react"; + +interface TagListProps { + allTags: string[]; + selectedTag: string | null; + onTagClick: (tag: string | null) => void; + isTagsExpanded: boolean; + onToggleExpand: () => void; + initialTagCount: number; +} + +export function TagList({ allTags, selectedTag, onTagClick, isTagsExpanded, onToggleExpand, initialTagCount }: TagListProps) { + const visibleTags = isTagsExpanded ? allTags : allTags.slice(0, initialTagCount); + + return ( +
+ onTagClick(null)} + > + 全部 + + {visibleTags.map((tag) => ( + onTagClick(tag)} + > + {tag} + + ))} + {allTags.length > initialTagCount && ( + + )} +
+ ); +} \ No newline at end of file diff --git a/src/app/blog/[id]/AnimatedBlogPost.tsx b/src/app/blog/[id]/AnimatedBlogPost.tsx index be4b406..9b1f7b1 100644 --- a/src/app/blog/[id]/AnimatedBlogPost.tsx +++ b/src/app/blog/[id]/AnimatedBlogPost.tsx @@ -9,7 +9,8 @@ import ShareButtons from "./ShareButtons"; import TableOfContents from "./TableOfContents"; import Comments from "@/components/Comments"; import { mdxComponents } from "./MdxComponents"; -import type { BlogPost, Heading } from "./types"; +import type { Heading } from "./types"; +import type { BlogPost } from "@/data/blogPosts"; interface AnimatedBlogPostProps { post: BlogPost; diff --git a/src/app/blog/[id]/BlogFooter.tsx b/src/app/blog/[id]/BlogFooter.tsx index c8907fb..7e71a1e 100644 --- a/src/app/blog/[id]/BlogFooter.tsx +++ b/src/app/blog/[id]/BlogFooter.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { Badge, Button } from "@/components/ui"; -import { BlogPost } from "./types"; +import { BlogPost } from "@/data/blogPosts"; interface BlogFooterProps { post: BlogPost; diff --git a/src/app/blog/[id]/BlogHeader.tsx b/src/app/blog/[id]/BlogHeader.tsx index 6714a53..6e95007 100644 --- a/src/app/blog/[id]/BlogHeader.tsx +++ b/src/app/blog/[id]/BlogHeader.tsx @@ -1,5 +1,5 @@ import Image from "next/image"; -import { BlogPost } from "./types"; +import { BlogPost } from "@/data/blogPosts"; interface BlogHeaderProps { post: BlogPost; diff --git a/src/app/blog/[id]/MdxComponents.tsx b/src/app/blog/[id]/MdxComponents.tsx index e480024..2d2f824 100644 --- a/src/app/blog/[id]/MdxComponents.tsx +++ b/src/app/blog/[id]/MdxComponents.tsx @@ -31,7 +31,7 @@ export const mdxComponents: MDXComponents = { ), a: (props: any) => ( ), diff --git a/src/app/blog/[id]/RelatedPosts.tsx b/src/app/blog/[id]/RelatedPosts.tsx index 8bf2036..62e53d8 100644 --- a/src/app/blog/[id]/RelatedPosts.tsx +++ b/src/app/blog/[id]/RelatedPosts.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui"; -import { BlogPost } from "./types"; +import { BlogPost } from "@/data/blogPosts"; function RelatedPosts({ posts }: { posts: BlogPost[] }) { if (posts.length === 0) return null; @@ -14,10 +14,7 @@ function RelatedPosts({ posts }: { posts: BlogPost[] }) {