Compare commits

...

5 commits

Author SHA1 Message Date
github-actions[bot]
1fb3da96c1 Merge remote-tracking branch 'origin/master' into blog-content 2026-09-19 06:21:58 +00:00
ClovertaTheTrilobita
db18c94c46
Merge pull request #45 from ClovertaTheTrilobita/dev
Some checks are pending
Sync and Deploy Astro Blog / sync-blog-content (push) Waiting to run
Sync and Deploy Astro Blog / build-and-deploy (push) Blocked by required conditions
Sync and Deploy Astro Blog / manual-deploy (push) Waiting to run
Add loading hint for comments
2026-09-19 14:21:49 +08:00
7606be8ba0 Update loading style 2026-09-19 14:19:01 +08:00
88f4ac2c82 Added loading hint for comments 2026-09-19 14:06:35 +08:00
ef530b99d9 update hint font size 2026-09-19 13:48:49 +08:00
4 changed files with 191 additions and 13 deletions

View file

@ -1,11 +1,16 @@
---
import Remark42Status from "./Remark42Status.astro";
const { slug } = Astro.props;
const pagePath = `/posts/${slug}/`;
const host = import.meta.env.PUBLIC_REMARK42_HOST;
const siteId = import.meta.env.PUBLIC_REMARK42_SITE_ID;
---
<div class="remark42-embed">
<Remark42Status />
<div id="remark42"></div>
</div>
<script define:vars={{ pagePath, host, siteId }} is:inline data-astro-rerun>
function getTheme() {
return document.documentElement.classList.contains("dark")
@ -55,28 +60,99 @@ const siteId = import.meta.env.PUBLIC_REMARK42_SITE_ID;
}
script.onload = () => resolve(true);
script.onerror = reject;
script.onerror = (error) => {
script.remove();
reject(error);
};
document.head.appendChild(script);
});
}
async function mountRemark42() {
const node = document.getElementById("remark42");
if (!node) return;
const status = document.getElementById("remark42-status");
const loadingStatus = status?.querySelector("[data-remark42-loading]");
const failedStatus = status?.querySelector("[data-remark42-failed]");
if (!node || !loadingStatus || !failedStatus) return;
window.__remark42LoadCleanup?.();
let active = true;
let iframe = null;
let timer;
const onIframeLoad = () => {
if (!active || !iframe || !node.contains(iframe)) return;
loadingStatus.hidden = true;
failedStatus.hidden = true;
status.hidden = true;
clearTimeout(timer);
observer.disconnect();
};
const watchIframe = () => {
const nextIframe = node.querySelector("iframe");
if (nextIframe === iframe) return;
iframe?.removeEventListener("load", onIframeLoad);
iframe = nextIframe;
iframe?.addEventListener("load", onIframeLoad, { once: true });
};
const observer = new MutationObserver(watchIframe);
observer.observe(node, { childList: true, subtree: true });
watchIframe();
const showFailure = () => {
if (!active || loadingStatus.hidden) return;
loadingStatus.hidden = true;
failedStatus.hidden = false;
};
window.__remark42LoadCleanup = () => {
active = false;
clearTimeout(timer);
observer.disconnect();
iframe?.removeEventListener("load", onIframeLoad);
};
loadingStatus.hidden = false;
failedStatus.hidden = true;
status.hidden = false;
timer = setTimeout(showFailure, 15000);
if (!host || !siteId) {
showFailure();
return;
}
try {
const alreadyLoaded = Boolean(window.REMARK42);
setRemarkConfig();
await ensureScript();
if (!window.REMARK42) return;
if (!active) return;
if (!window.REMARK42) {
showFailure();
return;
}
if (typeof window.REMARK42.destroy === "function") {
if (alreadyLoaded && typeof window.REMARK42.destroy === "function") {
window.REMARK42.destroy();
}
if (typeof window.REMARK42.createInstance === "function") {
// The script creates the first iframe itself. Only remount when
// reusing it on a new page/theme, or if it did not create one.
if (
(alreadyLoaded || !node.querySelector("iframe")) &&
typeof window.REMARK42.createInstance === "function"
) {
node.innerHTML = "";
window.REMARK42.createInstance(window.remark_config);
}
} catch {
showFailure();
}
}
function setupRemark42ThemeObserver() {
@ -106,3 +182,18 @@ const siteId = import.meta.env.PUBLIC_REMARK42_SITE_ID;
mountRemark42();
setupRemark42ThemeObserver();
</script>
<style>
.remark42-embed {
position: relative;
min-width: 0;
}
/* Let the iframe load in the same space occupied by the centered status. */
:global(#remark42-status:not([hidden])) + #remark42 {
position: absolute;
inset: 0;
visibility: hidden;
pointer-events: none;
}
</style>

View file

@ -0,0 +1,75 @@
---
import { getLangFromUrl } from "@/i18n";
const lang = getLangFromUrl(Astro.url);
const loadingText = lang === "zh" ? "评论区加载中……" : "Loading comments…";
const failedText = lang === "zh" ? "评论区加载失败" : "Failed to load comments";
---
<div id="remark42-status" role="status" aria-atomic="true">
<p class="remark42-loading" data-remark42-loading>
<span class="status-spinner" aria-hidden="true"></span>
<span>{loadingText}</span>
</p>
<p class="remark42-failed" data-remark42-failed hidden>
<span>{failedText}</span>
</p>
</div>
<style>
#remark42-status {
--status-color: #6b7680;
--status-accent: var(--deep-blue);
display: grid;
place-items: center;
min-block-size: 10rem;
padding: 2rem 1rem;
text-align: center;
}
.remark42-loading,
.remark42-failed {
display: flex;
align-items: center;
justify-content: center;
gap: 0.65rem;
margin: 0;
color: var(--status-color);
font-size: 0.85rem;
font-weight: 400;
line-height: 1.6;
}
.status-spinner {
flex: 0 0 auto;
width: 1rem;
height: 1rem;
box-sizing: border-box;
border: 1.5px solid color-mix(in srgb, var(--status-accent) 20%, transparent);
border-top-color: var(--status-accent);
border-radius: 50%;
animation: status-spin 1s linear infinite;
}
#remark42-status[hidden],
[hidden] {
display: none;
}
:global(html.dark) #remark42-status {
--status-color: #aab7c4;
--status-accent: #9ecbff;
}
@keyframes status-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.status-spinner {
animation: none;
}
}
</style>

View file

@ -17,7 +17,7 @@ export default {
publishedOn: "Published on",
commentsDesc: [
"Feel free to leave your thoughts here. 💭💡",
"After signing in, you can click the “Subscribe by Email” button at the bottom right of the text box to receive notifications of new interactions via email."
"After signing in, you can click “Subscribe by Email” at the bottom right of the text box to receive notifications via E-mail."
]
},
theme: {

View file

@ -103,7 +103,9 @@ const publishedDate = new Intl.DateTimeFormat(
aria-labelledby="comments-heading"
>
<h2 id="comments-heading">{comments}</h2>
{t.post.commentsDesc.map((line: string) => <p set:html={line} />)}
{t.post.commentsDesc.map((line: string) => (
<p class="comments-description" set:html={line} />
))}
<Remark42Embed slug={slug} />
</section>
</article>
@ -653,6 +655,16 @@ const publishedDate = new Intl.DateTimeFormat(
scroll-margin-top: 5rem;
}
.comments-description {
color: #6b7680;
font-size: 0.85rem;
line-height: 1.6;
}
:global(html.dark) .comments-description {
color: #aab7c4;
}
:global(html.dark) .post-content {
--article-muted: #aab7c4;
--article-rule: rgba(170, 183, 196, 0.28);