2026-03-24 17:33:01 +00:00
|
|
|
---
|
2026-03-24 18:01:45 +00:00
|
|
|
import { getCollection } from "astro:content";
|
2026-03-24 17:33:01 +00:00
|
|
|
import PostItem from "./PostItem.astro";
|
2026-03-25 08:43:02 +00:00
|
|
|
import { getLangFromUrl } from "@/i18n";
|
2026-03-24 17:33:01 +00:00
|
|
|
|
2026-03-25 09:01:09 +00:00
|
|
|
const token = import.meta.env.GITHUB_TOKEN;
|
|
|
|
|
const owner = import.meta.env.GISCUS_REPO_OWNER;
|
|
|
|
|
const name = import.meta.env.GISCUS_REPO_NAME;
|
|
|
|
|
const categoryId = import.meta.env.GISCUS_CATEGORY_ID;
|
2026-03-24 22:50:33 +00:00
|
|
|
const lang = getLangFromUrl(Astro.url);
|
2026-03-24 18:01:45 +00:00
|
|
|
const allPosts = await getCollection("blog");
|
2026-03-25 08:43:02 +00:00
|
|
|
|
|
|
|
|
type ReactionGroup = {
|
|
|
|
|
users?: {
|
|
|
|
|
totalCount?: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type DiscussionNode = {
|
|
|
|
|
title: string;
|
|
|
|
|
comments?: {
|
|
|
|
|
totalCount?: number;
|
|
|
|
|
};
|
|
|
|
|
reactionGroups?: ReactionGroup[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function normalizePath(path: string) {
|
|
|
|
|
return path.replace(/^\/+|\/+$/g, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchDiscussionStats(): Promise<DiscussionNode[]> {
|
2026-03-25 09:01:09 +00:00
|
|
|
// const token = import.meta.env.GITHUB_TOKEN;
|
2026-03-25 08:43:02 +00:00
|
|
|
if (!token) return [];
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
query($owner: String!, $name: String!, $categoryId: ID!) {
|
|
|
|
|
repository(owner: $owner, name: $name) {
|
|
|
|
|
discussions(first: 100, categoryId: $categoryId) {
|
|
|
|
|
nodes {
|
|
|
|
|
title
|
|
|
|
|
comments(first: 0) {
|
|
|
|
|
totalCount
|
|
|
|
|
}
|
|
|
|
|
reactionGroups {
|
|
|
|
|
users {
|
|
|
|
|
totalCount
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const res = await fetch("https://api.github.com/graphql", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
query,
|
|
|
|
|
variables: {
|
2026-03-25 09:01:09 +00:00
|
|
|
owner: owner,
|
|
|
|
|
name: name,
|
|
|
|
|
categoryId: categoryId,
|
2026-03-25 08:43:02 +00:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
return (json?.data?.repository?.discussions?.nodes ?? []) as DiscussionNode[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const discussions = await fetchDiscussionStats();
|
2026-03-25 10:38:01 +00:00
|
|
|
|
|
|
|
|
const sortedPosts = [...allPosts].sort(
|
|
|
|
|
(a, b) =>
|
|
|
|
|
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const latestPosts = sortedPosts.slice(0, 5);
|
2026-03-24 17:33:01 +00:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<ul>
|
2026-03-24 18:01:45 +00:00
|
|
|
{
|
2026-03-25 10:38:01 +00:00
|
|
|
latestPosts.map((post: any) => {
|
2026-03-24 18:01:45 +00:00
|
|
|
const formattedDate = new Date(post.data.pubDate)
|
|
|
|
|
.toISOString()
|
|
|
|
|
.split("T")[0];
|
2026-03-24 17:33:01 +00:00
|
|
|
|
2026-03-25 10:06:37 +00:00
|
|
|
const pathname = `/${post.id}/`;
|
2026-03-25 08:43:02 +00:00
|
|
|
|
|
|
|
|
const matchedDiscussion = discussions.find((d: DiscussionNode) => {
|
|
|
|
|
return normalizePath(d.title) === normalizePath(pathname);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const reactionCount =
|
|
|
|
|
matchedDiscussion?.reactionGroups?.reduce(
|
|
|
|
|
(sum: number, group: ReactionGroup) =>
|
|
|
|
|
sum + (group.users?.totalCount ?? 0),
|
|
|
|
|
0,
|
|
|
|
|
) ?? 0;
|
|
|
|
|
|
|
|
|
|
const commentCount = matchedDiscussion?.comments?.totalCount ?? 0;
|
|
|
|
|
|
2026-03-24 18:01:45 +00:00
|
|
|
return (
|
|
|
|
|
<PostItem
|
2026-03-24 22:50:33 +00:00
|
|
|
url={`/${lang}/posts/${post.id}/`}
|
2026-03-24 18:01:45 +00:00
|
|
|
title={post.data.title}
|
2026-03-25 08:12:49 +00:00
|
|
|
description={post.data.description}
|
2026-03-24 18:01:45 +00:00
|
|
|
date={formattedDate}
|
2026-03-24 23:40:19 +00:00
|
|
|
img={post.data.image.url}
|
2026-03-25 08:43:02 +00:00
|
|
|
commentCount={commentCount}
|
|
|
|
|
reactionCount={reactionCount}
|
2026-03-24 18:01:45 +00:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-24 17:33:01 +00:00
|
|
|
</ul>
|
2026-03-24 23:40:19 +00:00
|
|
|
|
2026-03-25 10:38:01 +00:00
|
|
|
<div class="more-link-wrap">
|
|
|
|
|
<a href={`/${lang}/timeline`} class="more-link">
|
|
|
|
|
{lang === "zh" ? ">>>更多" : ">>>More"}
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-24 23:40:19 +00:00
|
|
|
<style>
|
|
|
|
|
ul {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding-left: 0;
|
|
|
|
|
}
|
2026-03-25 10:38:01 +00:00
|
|
|
|
|
|
|
|
.more-link-wrap {
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-link {
|
|
|
|
|
color: #6f8090;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
font-size: 1.14rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-link:hover {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:global(.dark) .more-link {
|
|
|
|
|
color: #aab7c4;
|
|
|
|
|
}
|
2026-03-24 23:40:19 +00:00
|
|
|
</style>
|