mirror of
https://github.com/ClovertaTheTrilobita/SanYeCao-blog.git
synced 2026-07-03 15:41:26 +00:00
feat(latest comments): added reply to
This commit is contained in:
parent
018ad4ab1e
commit
07fbc4b731
2 changed files with 142 additions and 4 deletions
|
|
@ -75,6 +75,110 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
return item?.user?.name || (lang === "zh" ? "匿名用户" : "Anonymous");
|
||||
}
|
||||
|
||||
function normalizePath(url) {
|
||||
if (!url) return "";
|
||||
|
||||
try {
|
||||
const u = new URL(url, window.location.origin);
|
||||
let path = u.pathname;
|
||||
|
||||
if (!path.startsWith("/")) path = "/" + path;
|
||||
if (!path.endsWith("/")) path += "/";
|
||||
|
||||
return path;
|
||||
} catch {
|
||||
let path = String(url);
|
||||
|
||||
path = path.replace(/^https?:\/\/[^/]+/i, "");
|
||||
if (!path.startsWith("/")) path = "/" + path;
|
||||
if (!path.endsWith("/")) path += "/";
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
function buildUrlCandidates(rawUrl) {
|
||||
const path = normalizePath(rawUrl);
|
||||
if (!path) return [];
|
||||
|
||||
const noSlash = path.endsWith("/") ? path.slice(0, -1) : path;
|
||||
|
||||
const candidates = [
|
||||
path,
|
||||
noSlash,
|
||||
`/zh${path}`,
|
||||
`/zh${noSlash}`,
|
||||
`/en${path}`,
|
||||
`/en${noSlash}`,
|
||||
`${window.location.origin}${path}`,
|
||||
`${window.location.origin}${noSlash}`,
|
||||
];
|
||||
|
||||
return [...new Set(candidates.filter(Boolean))];
|
||||
}
|
||||
|
||||
async function fetchPostComments(host, siteId, rawUrl) {
|
||||
const candidates = buildUrlCandidates(rawUrl);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${host}/api/v1/find?site=${encodeURIComponent(siteId)}&url=${encodeURIComponent(candidate)}&format=plain`,
|
||||
{ credentials: "omit" },
|
||||
);
|
||||
|
||||
if (!res.ok) continue;
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
const comments = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data?.comments)
|
||||
? data.comments
|
||||
: [];
|
||||
|
||||
if (comments.length > 0) {
|
||||
return comments;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function enrichReplyTargets(host, siteId, comments) {
|
||||
const urls = [
|
||||
...new Set(
|
||||
comments.map((item) => item?.locator?.url).filter(Boolean),
|
||||
),
|
||||
];
|
||||
|
||||
const postMap = new Map();
|
||||
|
||||
await Promise.all(
|
||||
urls.map(async (url) => {
|
||||
const postComments = await fetchPostComments(host, siteId, url);
|
||||
postMap.set(url, postComments);
|
||||
}),
|
||||
);
|
||||
|
||||
return comments.map((item) => {
|
||||
if (!item?.pid) {
|
||||
return { ...item, replyToName: "" };
|
||||
}
|
||||
|
||||
const postComments = postMap.get(item?.locator?.url) || [];
|
||||
const parent = postComments.find(
|
||||
(c) => String(c?.id) === String(item?.pid),
|
||||
);
|
||||
|
||||
return {
|
||||
...item,
|
||||
replyToName: parent?.user?.name || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderComments(root, comments, lang) {
|
||||
const list = root.querySelector(".latest-comments-list");
|
||||
const loading = root.querySelector(".comments-loading");
|
||||
|
|
@ -90,20 +194,35 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
|
||||
list.innerHTML = comments
|
||||
.map((item) => {
|
||||
const author = escapeHtml(getUserName(item, lang));
|
||||
const rawAuthor = getUserName(item, lang);
|
||||
const author = escapeHtml(rawAuthor);
|
||||
const fallbackInitial = escapeHtml(
|
||||
rawAuthor.slice(0, 1).toUpperCase(),
|
||||
);
|
||||
|
||||
const avatar = getAvatar(item);
|
||||
const safeAvatar = escapeHtml(avatar || "");
|
||||
|
||||
const title = escapeHtml(
|
||||
item?.title ||
|
||||
(lang === "zh" ? "未命名文章" : "Untitled post"),
|
||||
);
|
||||
const href = rewritePostUrl(item?.locator?.url || "", lang);
|
||||
|
||||
// Remark42 返回的 text 是处理后的 HTML,适合直接渲染
|
||||
const html = item?.text || "";
|
||||
const time = formatTime(item?.time, lang);
|
||||
|
||||
const replyTo = escapeHtml(item?.replyToName || "");
|
||||
const authorLine = replyTo
|
||||
? `<span class="comment-author">@${author}</span><span class="comment-reply-sep">${
|
||||
lang === "zh" ? "回复" : "replied to"
|
||||
}</span><span class="comment-reply-to">@${replyTo}</span>`
|
||||
: `<span class="comment-author">@${author}</span>`;
|
||||
|
||||
const avatarHtml = avatar
|
||||
? `<img class="comment-avatar-img" src="${safeAvatar}" alt="${author}" loading="lazy" referrerpolicy="no-referrer" />`
|
||||
: `<div class="comment-avatar-fallback" aria-hidden="true">${author.slice(0, 1).toUpperCase()}</div>`;
|
||||
: `<div class="comment-avatar-fallback" aria-hidden="true">${fallbackInitial}</div>`;
|
||||
|
||||
return `
|
||||
<article class="comment-card">
|
||||
|
|
@ -115,7 +234,7 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
|
||||
<div class="comment-meta">
|
||||
<div class="comment-author-row">
|
||||
<span class="comment-author">@${author}</span>
|
||||
${authorLine}
|
||||
${time ? `<span class="comment-time">${escapeHtml(time)}</span>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -159,7 +278,12 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
}
|
||||
|
||||
const data = await res.json();
|
||||
renderComments(section, data, lang);
|
||||
const enriched = await enrichReplyTargets(
|
||||
host,
|
||||
siteId,
|
||||
Array.isArray(data) ? data : [],
|
||||
);
|
||||
renderComments(section, enriched, lang);
|
||||
} catch (err) {
|
||||
loading.style.display = "none";
|
||||
list.innerHTML = `<p class="latest-comments-empty">${
|
||||
|
|
|
|||
|
|
@ -361,6 +361,20 @@ img {
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
.comment-reply-sep {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.latest-comments .comment-reply-to {
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
color: gray;
|
||||
opacity: 0.9;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
html.dark .latest-comments .comment-card,
|
||||
.dark .latest-comments .comment-card {
|
||||
border-color: #7f8c97;
|
||||
|
|
|
|||
Loading…
Reference in a new issue