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[] }) {
{posts.map((relatedPost) => (
-
-
+
{relatedPost.title}
diff --git a/src/app/blog/[id]/ShareButtons.tsx b/src/app/blog/[id]/ShareButtons.tsx
index a1933c8..ffb2d7c 100644
--- a/src/app/blog/[id]/ShareButtons.tsx
+++ b/src/app/blog/[id]/ShareButtons.tsx
@@ -1,3 +1,4 @@
+import Link from "next/link";
import { FaTwitter, FaFacebook, FaLinkedin } from "react-icons/fa";
interface ShareButtonsProps {
@@ -8,30 +9,33 @@ interface ShareButtonsProps {
export default function ShareButtons({ url, title }: ShareButtonsProps) {
return (
);
}
diff --git a/src/app/blog/[id]/TableOfContents.tsx b/src/app/blog/[id]/TableOfContents.tsx
index 9f49b9c..511eb07 100644
--- a/src/app/blog/[id]/TableOfContents.tsx
+++ b/src/app/blog/[id]/TableOfContents.tsx
@@ -19,7 +19,7 @@ const TableOfContents = React.memo(function TableOfContents({
-
{heading.text.length > 20
diff --git a/src/app/blog/[id]/page.tsx b/src/app/blog/[id]/page.tsx
index 4180c32..4241b8d 100644
--- a/src/app/blog/[id]/page.tsx
+++ b/src/app/blog/[id]/page.tsx
@@ -2,7 +2,8 @@ import { notFound } from "next/navigation";
import { blogPosts } from "@/data/blogPosts";
import ReadingProgress from "@/components/ReadingProgress";
import { extractHeadings, estimateReadingTime } from "@/utils/blogHelpers";
-import type { BlogPost, Heading } from "./types";
+import type { Heading } from "./types";
+import type { BlogPost } from "@/data/blogPosts";
import dynamic from "next/dynamic";
const AnimatedBlogPost = dynamic(() => import("./AnimatedBlogPost"), {
diff --git a/src/app/blog/[id]/types.ts b/src/app/blog/[id]/types.ts
index 4e78d78..90703bb 100644
--- a/src/app/blog/[id]/types.ts
+++ b/src/app/blog/[id]/types.ts
@@ -1,15 +1,3 @@
-import { MDXRemoteSerializeResult } from 'next-mdx-remote';
-
-export interface BlogPost {
- id: number;
- title: string;
- date: string;
- author: string;
- content: string;
- coverImage?: string;
- tags: string[];
-}
-
export interface Heading {
level: number;
text: string;
diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx
new file mode 100644
index 0000000..e393614
--- /dev/null
+++ b/src/app/contact/page.tsx
@@ -0,0 +1,8 @@
+export default function PrivacyPage() {
+ return (
+
+
隐私政策
+
这里是您的隐私政策内容...
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx
new file mode 100644
index 0000000..ce26ba4
--- /dev/null
+++ b/src/app/privacy/page.tsx
@@ -0,0 +1,8 @@
+export default function PrivacyPage() {
+ return (
+
+
隐私政策
+
这里是您的隐私政策内容...
+
+ );
+}
diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx
new file mode 100644
index 0000000..ce26ba4
--- /dev/null
+++ b/src/app/terms/page.tsx
@@ -0,0 +1,8 @@
+export default function PrivacyPage() {
+ return (
+
+
隐私政策
+
这里是您的隐私政策内容...
+
+ );
+}
diff --git a/src/components/AnimatedLayout.tsx b/src/components/AnimatedLayout.tsx
index f2c6070..1f3160e 100644
--- a/src/components/AnimatedLayout.tsx
+++ b/src/components/AnimatedLayout.tsx
@@ -1,6 +1,11 @@
"use client";
-import { LazyMotion, domAnimation, motion, AnimatePresence } from "framer-motion";
+import {
+ LazyMotion,
+ domAnimation,
+ motion,
+ AnimatePresence,
+} from "framer-motion";
import { usePathname } from "next/navigation";
import { ReactNode } from "react";
@@ -25,7 +30,7 @@ export default function AnimatedLayout({ children }: AnimatedLayoutProps) {
return (
-
+
= ({ text, language }) => {
const [state, setState] = useState<"idle" | "copy" | "copied">("idle");
const [isHovered, setIsHovered] = useState(false);
- const { theme } = useTheme();
+ const { theme } = useThemeType();
const throttledCopy = useMemo(
() =>
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index bd08396..52b6562 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -2,6 +2,12 @@ import Link from "next/link";
import { Badge } from "@/components/ui/badge";
export default function Footer() {
+ const links = [
+ { href: "/privacy", label: "隐私政策" },
+ { href: "/terms", label: "使用条款" },
+ { href: "/contact", label: "联系我们" },
+ ];
+
return (
);
-}
+}
\ No newline at end of file
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
index b2e4948..13661b2 100644
--- a/src/components/ThemeToggle.tsx
+++ b/src/components/ThemeToggle.tsx
@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
-import { useTheme } from "next-themes";
+import { useThemeType } from "@/hooks";
const MoonIcon = () => (