mirror of
https://github.com/ClovertaTheTrilobita/SanYeCao-blog.git
synced 2026-07-03 15:41:26 +00:00
341 lines
8.4 KiB
Text
341 lines
8.4 KiB
Text
---
|
|
import { getLangFromUrl } from "@/i18n";
|
|
|
|
const limit = Number(Astro.props.limit ?? 5);
|
|
const lang = getLangFromUrl(Astro.url);
|
|
|
|
function formatDate(iso: string) {
|
|
return new Date(iso).toISOString().slice(0, 10);
|
|
}
|
|
---
|
|
|
|
<section
|
|
id="latest-comments"
|
|
class="latest-comments"
|
|
data-lang={lang}
|
|
data-limit={limit}
|
|
>
|
|
<h2>{lang === "zh" ? "最新评论" : "Latest Comments"}</h2>
|
|
|
|
<div class="comments-loading" id="comments-loading">
|
|
<div class="loading-card"></div>
|
|
<div class="loading-card"></div>
|
|
<div class="loading-card"></div>
|
|
</div>
|
|
|
|
<ul class="comment-list" id="comment-list" style="display: none;"></ul>
|
|
|
|
<p class="empty" id="comments-empty" style="display: none;">
|
|
{lang === "zh" ? "还没有评论。" : "No comments yet."}
|
|
</p>
|
|
|
|
<p class="empty" id="comments-error" style="display: none;">
|
|
{lang === "zh" ? "评论加载失败。" : "Failed to load comments."}
|
|
</p>
|
|
</section>
|
|
|
|
<script>
|
|
type CommentAuthor = {
|
|
login?: string;
|
|
avatarUrl?: string;
|
|
url?: string;
|
|
};
|
|
|
|
type LatestCommentItem = {
|
|
id: string;
|
|
body?: string;
|
|
updatedAt: string;
|
|
author?: CommentAuthor | null;
|
|
postId: string;
|
|
postTitle: string;
|
|
localUrl: string;
|
|
isReply: boolean;
|
|
replyTo?: string;
|
|
};
|
|
|
|
type CommentsResponse = {
|
|
comments?: LatestCommentItem[];
|
|
error?: string;
|
|
};
|
|
|
|
const root = document.getElementById(
|
|
"latest-comments",
|
|
) as HTMLElement | null;
|
|
if (root) {
|
|
const lang = root.dataset.lang || "zh";
|
|
const limit = root.dataset.limit || "5";
|
|
|
|
const loadingEl = root.querySelector("#comments-loading");
|
|
const listEl = root.querySelector("#comment-list");
|
|
const emptyEl = root.querySelector("#comments-empty");
|
|
const errorEl = root.querySelector("#comments-error");
|
|
|
|
if (
|
|
!(loadingEl instanceof HTMLElement) ||
|
|
!(listEl instanceof HTMLElement) ||
|
|
!(emptyEl instanceof HTMLElement) ||
|
|
!(errorEl instanceof HTMLElement)
|
|
) {
|
|
throw new Error("Comments elements not found");
|
|
}
|
|
|
|
const formatDate = (iso: string) =>
|
|
new Date(iso).toISOString().slice(0, 10);
|
|
|
|
const renderComment = (comment: LatestCommentItem) => {
|
|
console.log("Start render");
|
|
const li = document.createElement("li");
|
|
li.className = `comment-card ${comment.isReply ? "is-reply" : ""}`;
|
|
|
|
li.innerHTML = `
|
|
<a href="${comment.localUrl}" class="comment-link" data-astro-reload>
|
|
<div class="comment-top">
|
|
${
|
|
comment.author?.avatarUrl
|
|
? `<img
|
|
src="${comment.author.avatarUrl}"
|
|
alt="${comment.author?.login ?? "author"}"
|
|
class="comment-avatar"
|
|
/>`
|
|
: ""
|
|
}
|
|
|
|
<div class="comment-meta">
|
|
<span class="comment-author">${comment.author?.login ?? "Unknown"}</span>
|
|
<span class="comment-date">${formatDate(comment.updatedAt)}</span>
|
|
</div>
|
|
|
|
<span class="comment-post-title" title="${comment.postTitle}">
|
|
${comment.postTitle}
|
|
</span>
|
|
</div>
|
|
|
|
${
|
|
comment.isReply
|
|
? `<p class="comment-kind">${
|
|
lang === "zh"
|
|
? `回复 @${comment.replyTo ?? "Unknown"}`
|
|
: `Reply to @${comment.replyTo ?? "Unknown"}`
|
|
}</p>`
|
|
: ""
|
|
}
|
|
|
|
<p class="comment-body">${comment.body ?? ""}</p>
|
|
</a>
|
|
`;
|
|
|
|
return li;
|
|
};
|
|
|
|
fetch(`/api/comments.json?lang=${lang}&limit=${limit}`)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error("Request failed");
|
|
return res.json() as Promise<CommentsResponse>;
|
|
})
|
|
.then((data) => {
|
|
loadingEl.style.display = "none";
|
|
|
|
const comments = data.comments ?? [];
|
|
|
|
if (!comments.length) {
|
|
emptyEl.style.display = "block";
|
|
return;
|
|
}
|
|
|
|
listEl.style.display = "grid";
|
|
|
|
comments.forEach((comment) => {
|
|
listEl.appendChild(renderComment(comment));
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
console.error("Comments fetch failed:", err);
|
|
loadingEl.style.display = "none";
|
|
errorEl.style.display = "block";
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<style is:global>
|
|
.latest-comments {
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.latest-comments h2 {
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.comment-list {
|
|
font-family:
|
|
system-ui,
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
"Segoe UI",
|
|
"PingFang SC",
|
|
"Hiragino Sans GB",
|
|
"Microsoft YaHei",
|
|
"Noto Sans CJK SC",
|
|
"Source Han Sans SC",
|
|
sans-serif;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: grid;
|
|
gap: 0.9rem;
|
|
}
|
|
|
|
.comment-card {
|
|
margin: 0;
|
|
}
|
|
|
|
.comment-link {
|
|
display: block;
|
|
padding: 0.9rem 1rem;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
border: 1px dashed #aeb8c2;
|
|
background: rgba(160, 175, 190, 0.06);
|
|
}
|
|
|
|
.comment-card.is-reply .comment-link {
|
|
border-style: dashed;
|
|
background: rgba(160, 175, 190, 0.1);
|
|
}
|
|
|
|
.comment-top {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.comment-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
|
|
.comment-post-title {
|
|
margin-left: auto;
|
|
text-align: right;
|
|
font-size: 0.92rem;
|
|
font-weight: 700;
|
|
color: #6f8090;
|
|
max-width: 45%;
|
|
line-height: 1.35;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.comment-avatar {
|
|
width: 42px;
|
|
height: 42px;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.comment-author {
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.comment-date {
|
|
font-size: 0.9rem;
|
|
color: #6f8090;
|
|
}
|
|
|
|
.comment-kind {
|
|
margin: 0 0 0.35rem 0;
|
|
font-size: 0.82rem;
|
|
color: #8c8c8c;
|
|
font-style: italic;
|
|
}
|
|
|
|
.comment-body {
|
|
margin: 0;
|
|
color: #555;
|
|
line-height: 1.6;
|
|
font-style: italic;
|
|
}
|
|
|
|
.empty {
|
|
color: #6f8090;
|
|
}
|
|
|
|
.comments-loading {
|
|
display: grid;
|
|
gap: 0.9rem;
|
|
}
|
|
|
|
.loading-card {
|
|
height: 92px;
|
|
border: 1px dashed #aeb8c2;
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(160, 175, 190, 0.06) 25%,
|
|
rgba(160, 175, 190, 0.16) 50%,
|
|
rgba(160, 175, 190, 0.06) 75%
|
|
);
|
|
background-size: 200% 100%;
|
|
animation: shimmer 1.2s infinite linear;
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
from {
|
|
background-position: 200% 0;
|
|
}
|
|
to {
|
|
background-position: -200% 0;
|
|
}
|
|
}
|
|
|
|
:global(.dark) .comment-link {
|
|
border-color: #7f8c97;
|
|
background: rgba(180, 190, 200, 0.06);
|
|
}
|
|
|
|
:global(.dark) .comment-card.is-reply .comment-link {
|
|
background: rgba(180, 190, 200, 0.1);
|
|
}
|
|
|
|
:global(.dark) .comment-post-title {
|
|
color: #c8d2dc;
|
|
}
|
|
|
|
:global(.dark) .comment-date,
|
|
:global(.dark) .empty {
|
|
color: #aab7c4;
|
|
}
|
|
|
|
:global(.dark) .comment-kind {
|
|
color: #b8b8b8;
|
|
}
|
|
|
|
:global(.dark) .comment-body {
|
|
color: #d3d7db;
|
|
}
|
|
|
|
:global(.dark) .loading-card {
|
|
border-color: #7f8c97;
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(180, 190, 200, 0.06) 25%,
|
|
rgba(180, 190, 200, 0.16) 50%,
|
|
rgba(180, 190, 200, 0.06) 75%
|
|
);
|
|
background-size: 200% 100%;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.comment-top {
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.comment-post-title {
|
|
margin-left: 0;
|
|
max-width: 100%;
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
}
|
|
</style>
|