hd-garden 0.3.0 → 0.3.2
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import { ArrowLeft, ArrowRight, PushPin } from "@phosphor-icons/react";
|
|
6
|
+
import { formatDate, postHref, posts, topics } from "../lib/content";
|
|
7
|
+
import { ContentEmptyState } from "./ContentEmptyState";
|
|
8
|
+
|
|
9
|
+
const pageSize = 8;
|
|
10
|
+
|
|
11
|
+
export const archiveHref = (topic?: string, page?: number) => {
|
|
12
|
+
const params = new URLSearchParams();
|
|
13
|
+
if (topic) params.set("topic", topic);
|
|
14
|
+
if (page && page > 1) params.set("page", String(page));
|
|
15
|
+
const query = params.toString();
|
|
16
|
+
return query ? `/blog?${query}` : "/blog";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function BlogArchive() {
|
|
20
|
+
const searchParams = useSearchParams();
|
|
21
|
+
const requestedTopic = searchParams.get("topic") ?? "";
|
|
22
|
+
const selectedTopic = topics.includes(requestedTopic) ? requestedTopic : undefined;
|
|
23
|
+
const filteredPosts = selectedTopic
|
|
24
|
+
? posts.filter((post) => post.topic === selectedTopic)
|
|
25
|
+
: posts;
|
|
26
|
+
const pageCount = Math.max(1, Math.ceil(filteredPosts.length / pageSize));
|
|
27
|
+
const parsedPage = Number.parseInt(searchParams.get("page") ?? "1", 10);
|
|
28
|
+
const currentPage = Math.min(
|
|
29
|
+
pageCount,
|
|
30
|
+
Math.max(1, Number.isFinite(parsedPage) ? parsedPage : 1),
|
|
31
|
+
);
|
|
32
|
+
const visiblePosts = filteredPosts.slice(
|
|
33
|
+
(currentPage - 1) * pageSize,
|
|
34
|
+
currentPage * pageSize,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<div className="topic-strip" aria-label="文章主题">
|
|
40
|
+
<Link
|
|
41
|
+
href="/blog"
|
|
42
|
+
className={!selectedTopic ? "active" : ""}
|
|
43
|
+
aria-current={!selectedTopic ? "page" : undefined}
|
|
44
|
+
>
|
|
45
|
+
全部 · {posts.length}
|
|
46
|
+
</Link>
|
|
47
|
+
{topics.map((topic) => (
|
|
48
|
+
<Link
|
|
49
|
+
href={archiveHref(topic)}
|
|
50
|
+
className={selectedTopic === topic ? "active" : ""}
|
|
51
|
+
aria-current={selectedTopic === topic ? "page" : undefined}
|
|
52
|
+
key={topic}
|
|
53
|
+
>
|
|
54
|
+
{topic} · {posts.filter((post) => post.topic === topic).length}
|
|
55
|
+
</Link>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{visiblePosts.length > 0 ? (
|
|
60
|
+
<section className="archive-list" aria-label="全部博文">
|
|
61
|
+
{visiblePosts.map((post) => (
|
|
62
|
+
<Link
|
|
63
|
+
key={post.path}
|
|
64
|
+
href={postHref(post)}
|
|
65
|
+
className={`archive-row${post.cover ? " with-cover" : ""}`}
|
|
66
|
+
>
|
|
67
|
+
{post.cover && (
|
|
68
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
69
|
+
<img
|
|
70
|
+
className="archive-cover"
|
|
71
|
+
src={post.cover}
|
|
72
|
+
alt={post.coverAlt ?? `${post.title} 封面`}
|
|
73
|
+
loading="lazy"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
<div className="archive-copy">
|
|
77
|
+
<div className="archive-meta">
|
|
78
|
+
<time>{formatDate(post.date)}</time>
|
|
79
|
+
<span className="archive-topic">
|
|
80
|
+
{post.topic}
|
|
81
|
+
{post.pinned && (
|
|
82
|
+
<span className="pinned-badge">
|
|
83
|
+
<PushPin size={12} weight="fill" />
|
|
84
|
+
置顶
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
<h2>{post.title}</h2>
|
|
90
|
+
<p>{post.description}</p>
|
|
91
|
+
</div>
|
|
92
|
+
<ArrowRight size={19} />
|
|
93
|
+
</Link>
|
|
94
|
+
))}
|
|
95
|
+
</section>
|
|
96
|
+
) : (
|
|
97
|
+
<ContentEmptyState kind="posts" />
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{pageCount > 1 && (
|
|
101
|
+
<nav className="archive-pagination" aria-label="博文分页">
|
|
102
|
+
{currentPage > 1 ? (
|
|
103
|
+
<Link href={archiveHref(selectedTopic, currentPage - 1)}>
|
|
104
|
+
<ArrowLeft size={16} />
|
|
105
|
+
上一页
|
|
106
|
+
</Link>
|
|
107
|
+
) : (
|
|
108
|
+
<span />
|
|
109
|
+
)}
|
|
110
|
+
<span>
|
|
111
|
+
{currentPage} / {pageCount}
|
|
112
|
+
</span>
|
|
113
|
+
{currentPage < pageCount ? (
|
|
114
|
+
<Link href={archiveHref(selectedTopic, currentPage + 1)}>
|
|
115
|
+
下一页
|
|
116
|
+
<ArrowRight size={16} />
|
|
117
|
+
</Link>
|
|
118
|
+
) : (
|
|
119
|
+
<span />
|
|
120
|
+
)}
|
|
121
|
+
</nav>
|
|
122
|
+
)}
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
}
|
package/package.json
CHANGED