hd-garden 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/blog/page.tsx CHANGED
@@ -1,51 +1,13 @@
1
- import Link from "next/link";
2
- import {
3
- ArrowLeft,
4
- ArrowRight,
5
- PushPin,
6
- } from "@phosphor-icons/react/ssr";
7
- import { formatDate, postHref, posts, topics } from "../lib/content";
1
+ import { Suspense } from "react";
8
2
  import { siteConfig } from "../site.config";
9
- import { ContentEmptyState } from "../components/ContentEmptyState";
3
+ import { BlogArchive } from "../components/BlogArchive";
10
4
 
11
5
  export const metadata = {
12
6
  title: siteConfig.pages.blog.title,
13
7
  description: siteConfig.pages.blog.subtitle,
14
8
  };
15
9
 
16
- const pageSize = 8;
17
-
18
- const archiveHref = (topic?: string, page?: number) => {
19
- const params = new URLSearchParams();
20
- if (topic) params.set("topic", topic);
21
- if (page && page > 1) params.set("page", String(page));
22
- const query = params.toString();
23
- return query ? `/blog?${query}` : "/blog";
24
- };
25
-
26
- export default async function BlogIndex({
27
- searchParams,
28
- }: {
29
- searchParams: Promise<{ topic?: string; page?: string }>;
30
- }) {
31
- const requested = await searchParams;
32
- const selectedTopic = topics.includes(requested.topic ?? "")
33
- ? requested.topic
34
- : undefined;
35
- const filteredPosts = selectedTopic
36
- ? posts.filter((post) => post.topic === selectedTopic)
37
- : posts;
38
- const pageCount = Math.max(1, Math.ceil(filteredPosts.length / pageSize));
39
- const parsedPage = Number.parseInt(requested.page ?? "1", 10);
40
- const currentPage = Math.min(
41
- pageCount,
42
- Math.max(1, Number.isFinite(parsedPage) ? parsedPage : 1),
43
- );
44
- const visiblePosts = filteredPosts.slice(
45
- (currentPage - 1) * pageSize,
46
- currentPage * pageSize,
47
- );
48
-
10
+ export default function BlogIndex() {
49
11
  return (
50
12
  <main className="page-shell index-page">
51
13
  <header className="page-intro">
@@ -53,87 +15,9 @@ export default async function BlogIndex({
53
15
  <h1>{siteConfig.pages.blog.title}</h1>
54
16
  <p>{siteConfig.pages.blog.subtitle}</p>
55
17
  </header>
56
-
57
- <div className="topic-strip" aria-label="文章主题">
58
- <Link
59
- href="/blog"
60
- className={!selectedTopic ? "active" : ""}
61
- aria-current={!selectedTopic ? "page" : undefined}
62
- >
63
- 全部 · {posts.length}
64
- </Link>
65
- {topics.map((topic) => (
66
- <Link
67
- href={archiveHref(topic)}
68
- className={selectedTopic === topic ? "active" : ""}
69
- aria-current={selectedTopic === topic ? "page" : undefined}
70
- key={topic}
71
- >
72
- {topic} · {posts.filter((post) => post.topic === topic).length}
73
- </Link>
74
- ))}
75
- </div>
76
-
77
- {visiblePosts.length > 0 ? <section className="archive-list" aria-label="全部博文">
78
- {visiblePosts.map((post) => (
79
- <Link
80
- key={post.path}
81
- href={postHref(post)}
82
- className={`archive-row${post.cover ? " with-cover" : ""}`}
83
- >
84
- {post.cover && (
85
- // eslint-disable-next-line @next/next/no-img-element
86
- <img
87
- className="archive-cover"
88
- src={post.cover}
89
- alt={post.coverAlt ?? `${post.title} 封面`}
90
- loading="lazy"
91
- />
92
- )}
93
- <div className="archive-copy">
94
- <div className="archive-meta">
95
- <time>{formatDate(post.date)}</time>
96
- <span className="archive-topic">
97
- {post.topic}
98
- {post.pinned && (
99
- <span className="pinned-badge">
100
- <PushPin size={12} weight="fill" />
101
- 置顶
102
- </span>
103
- )}
104
- </span>
105
- </div>
106
- <h2>{post.title}</h2>
107
- <p>{post.description}</p>
108
- </div>
109
- <ArrowRight size={19} />
110
- </Link>
111
- ))}
112
- </section> : <ContentEmptyState kind="posts" />}
113
-
114
- {pageCount > 1 && (
115
- <nav className="archive-pagination" aria-label="博文分页">
116
- {currentPage > 1 ? (
117
- <Link href={archiveHref(selectedTopic, currentPage - 1)}>
118
- <ArrowLeft size={16} />
119
- 上一页
120
- </Link>
121
- ) : (
122
- <span />
123
- )}
124
- <span>
125
- {currentPage} / {pageCount}
126
- </span>
127
- {currentPage < pageCount ? (
128
- <Link href={archiveHref(selectedTopic, currentPage + 1)}>
129
- 下一页
130
- <ArrowRight size={16} />
131
- </Link>
132
- ) : (
133
- <span />
134
- )}
135
- </nav>
136
- )}
18
+ <Suspense>
19
+ <BlogArchive />
20
+ </Suspense>
137
21
  </main>
138
22
  );
139
23
  }
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useSearchParams } from "next/navigation";
5
+ import { useEffect, useState } from "react";
6
+ import { ArrowLeft, ArrowRight, PushPin } from "@phosphor-icons/react";
7
+ import { formatDate, postHref, posts, topics } from "../lib/content";
8
+ import { ContentEmptyState } from "./ContentEmptyState";
9
+
10
+ const pageSize = 8;
11
+
12
+ export const archiveHref = (topic?: string, page?: number) => {
13
+ const params = new URLSearchParams();
14
+ if (topic) params.set("topic", topic);
15
+ if (page && page > 1) params.set("page", String(page));
16
+ const query = params.toString();
17
+ return query ? `/blog?${query}` : "/blog";
18
+ };
19
+
20
+ export function BlogArchive() {
21
+ const searchParams = useSearchParams();
22
+ const [ready, setReady] = useState(false);
23
+
24
+ useEffect(() => {
25
+ setReady(true);
26
+ }, []);
27
+
28
+ const requestedTopic = ready ? (searchParams.get("topic") ?? "") : "";
29
+ const selectedTopic = topics.includes(requestedTopic) ? requestedTopic : undefined;
30
+ const filteredPosts = selectedTopic
31
+ ? posts.filter((post) => post.topic === selectedTopic)
32
+ : posts;
33
+ const pageCount = Math.max(1, Math.ceil(filteredPosts.length / pageSize));
34
+ const parsedPage = Number.parseInt((ready ? searchParams.get("page") : null) ?? "1", 10);
35
+ const currentPage = Math.min(
36
+ pageCount,
37
+ Math.max(1, Number.isFinite(parsedPage) ? parsedPage : 1),
38
+ );
39
+ const visiblePosts = filteredPosts.slice(
40
+ (currentPage - 1) * pageSize,
41
+ currentPage * pageSize,
42
+ );
43
+
44
+ return (
45
+ <>
46
+ <div className="topic-strip" aria-label="文章主题">
47
+ <Link
48
+ href="/blog"
49
+ className={!selectedTopic ? "active" : ""}
50
+ aria-current={!selectedTopic ? "page" : undefined}
51
+ >
52
+ 全部 · {posts.length}
53
+ </Link>
54
+ {topics.map((topic) => (
55
+ <Link
56
+ href={archiveHref(topic)}
57
+ className={selectedTopic === topic ? "active" : ""}
58
+ aria-current={selectedTopic === topic ? "page" : undefined}
59
+ key={topic}
60
+ >
61
+ {topic} · {posts.filter((post) => post.topic === topic).length}
62
+ </Link>
63
+ ))}
64
+ </div>
65
+
66
+ {visiblePosts.length > 0 ? (
67
+ <section className="archive-list" aria-label="全部博文">
68
+ {visiblePosts.map((post) => (
69
+ <Link
70
+ key={post.path}
71
+ href={postHref(post)}
72
+ className={`archive-row${post.cover ? " with-cover" : ""}`}
73
+ >
74
+ {post.cover && (
75
+ // eslint-disable-next-line @next/next/no-img-element
76
+ <img
77
+ className="archive-cover"
78
+ src={post.cover}
79
+ alt={post.coverAlt ?? `${post.title} 封面`}
80
+ loading="lazy"
81
+ />
82
+ )}
83
+ <div className="archive-copy">
84
+ <div className="archive-meta">
85
+ <time>{formatDate(post.date)}</time>
86
+ <span className="archive-topic">
87
+ {post.topic}
88
+ {post.pinned && (
89
+ <span className="pinned-badge">
90
+ <PushPin size={12} weight="fill" />
91
+ 置顶
92
+ </span>
93
+ )}
94
+ </span>
95
+ </div>
96
+ <h2>{post.title}</h2>
97
+ <p>{post.description}</p>
98
+ </div>
99
+ <ArrowRight size={19} />
100
+ </Link>
101
+ ))}
102
+ </section>
103
+ ) : (
104
+ <ContentEmptyState kind="posts" />
105
+ )}
106
+
107
+ {pageCount > 1 && (
108
+ <nav className="archive-pagination" aria-label="博文分页">
109
+ {currentPage > 1 ? (
110
+ <Link href={archiveHref(selectedTopic, currentPage - 1)}>
111
+ <ArrowLeft size={16} />
112
+ 上一页
113
+ </Link>
114
+ ) : (
115
+ <span />
116
+ )}
117
+ <span>
118
+ {currentPage} / {pageCount}
119
+ </span>
120
+ {currentPage < pageCount ? (
121
+ <Link href={archiveHref(selectedTopic, currentPage + 1)}>
122
+ 下一页
123
+ <ArrowRight size={16} />
124
+ </Link>
125
+ ) : (
126
+ <span />
127
+ )}
128
+ </nav>
129
+ )}
130
+ </>
131
+ );
132
+ }
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import Link from "next/link";
2
4
  import { ArrowRight, Article, Books, Quotes } from "@phosphor-icons/react/ssr";
3
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-garden",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
6
6
  "license": "MIT",
@@ -14,6 +14,6 @@
14
14
  "pages:prepare": "garden pages:prepare"
15
15
  },
16
16
  "dependencies": {
17
- "hd-garden": "^0.3.0"
17
+ "hd-garden": "^0.3.1"
18
18
  }
19
19
  }