SanYeCao-blog/src/layouts/MarkdownPostLayout.astro

462 lines
10 KiB
Text
Raw Normal View History

2026-03-24 17:33:01 +00:00
---
2026-03-24 18:01:45 +00:00
import BaseLayout from "./BaseLayout.astro";
import Remark42Embed from "@/components/Remark42Embed.astro";
2026-04-18 18:38:42 +00:00
import PostNav from "@/components/Posts/PostNav.astro";
2026-09-17 07:05:12 +00:00
import TagLink from "@/components/TagLink.astro";
2026-04-18 19:29:51 +00:00
import PostMenu from "@/components/Posts/PostMenu.astro";
2026-04-24 22:12:09 +00:00
import Dialog from "@/components/Posts/Dialog.astro";
2026-04-15 23:43:42 +00:00
import { fade } from "astro:transitions";
2026-04-18 18:38:42 +00:00
import { getTranslations } from "@/i18n";
2026-03-24 18:01:45 +00:00
import "@/styles/global.css";
2026-03-25 07:25:40 +00:00
2026-04-18 19:29:51 +00:00
const { post, frontmatter, lang, slug, postId, headings } = Astro.props;
const comments = lang === "zh" ? "评论区" : "comments";
2026-03-25 07:25:40 +00:00
const t = getTranslations(lang);
2026-09-17 07:26:33 +00:00
const publishedDate = new Intl.DateTimeFormat(
lang === "zh" ? "zh-CN" : "en-US",
{
year: "numeric",
month: "long",
day: "numeric",
},
).format(frontmatter.pubDate);
2026-03-24 17:33:01 +00:00
---
2026-03-24 18:01:45 +00:00
2026-03-25 15:26:22 +00:00
<BaseLayout
pageTitle=`${frontmatter.title} - ${t.banner.title}`
2026-03-25 15:26:22 +00:00
description={frontmatter.description}
image={frontmatter.image?.url}
>
2026-09-17 05:35:59 +00:00
<main class="post-layout">
<div id="reading-progress" aria-hidden="true"></div>
<a
href="/"
class="back-button"
id="back-button"
aria-label="Go back"
title="返回"
>
2026-09-17 05:35:59 +00:00
<svg
viewBox="0 0 24 24"
width="20"
height="20"
fill="none"
stroke="currentColor"
stroke-width="2.2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M15 18l-6-6 6-6"></path>
</svg>
</a>
<PostMenu headings={headings} />
<article class="post-article">
<div class="post-header">
<div class="post-meta">
<h1 class="post-title" transition:name={`post-title-${postId}`}>
{frontmatter.title}
</h1>
<p class="description">
<em>{frontmatter.description}</em>
</p>
2026-09-17 07:26:33 +00:00
<p class="post-byline">
<span>{t.post.writtenBy} {frontmatter.author}</span>
<span class="byline-separator" aria-hidden="true">·</span>
<time datetime={frontmatter.pubDate.toISOString()}>
{t.post.publishedOn} {publishedDate}
</time>
2026-09-17 05:35:59 +00:00
</p>
<div class="tags" transition:name={`post-tags-${postId}`}>
{
frontmatter.tags.map((tag: string) => (
2026-09-17 07:05:12 +00:00
<TagLink tag={tag} lang={lang} />
2026-09-17 05:35:59 +00:00
))
}
</div>
</div>
2026-03-25 07:25:40 +00:00
2026-09-17 05:35:59 +00:00
<img
src={frontmatter.image.url}
alt={frontmatter.image.alt}
class="post-cover"
transition:name={`post-image-${postId}`}
/>
</div>
2026-03-24 18:01:45 +00:00
2026-09-17 05:35:59 +00:00
<div class="post-divider"></div>
2026-09-17 05:35:59 +00:00
<div class="post-content" transition:animate={fade({ duration: "0.2s" })}>
<slot />
</div>
2026-03-24 18:39:50 +00:00
2026-09-17 05:35:59 +00:00
<Dialog />
2026-04-24 22:05:50 +00:00
2026-09-17 05:35:59 +00:00
<PostNav post={post} lang={lang} />
2026-04-18 18:38:42 +00:00
2026-09-17 05:35:59 +00:00
<h2>{comments}</h2>
{t.post.commentsDesc.map((line: string) => <p set:html={line} />)}
<Remark42Embed slug={slug} />
</article>
</main>
2026-03-24 18:01:45 +00:00
</BaseLayout>
2026-03-25 07:25:40 +00:00
2026-03-25 15:26:22 +00:00
<script
type="application/ld+json"
set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: frontmatter.title,
description: frontmatter.description,
image: frontmatter.image
? [
new URL(
frontmatter.image.url ?? frontmatter.image,
Astro.site,
).toString(),
]
: undefined,
datePublished: frontmatter.pubDate,
dateModified: frontmatter.updatedDate ?? frontmatter.pubDate,
author: {
"@type": "Person",
name: "CLoverta",
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": new URL(Astro.url.pathname, Astro.site).toString(),
},
})}
/>
<script>
const initFloatingActions = () => {
const backButton = document.getElementById("back-button");
if (backButton && backButton.dataset.bound !== "true") {
const toggleBackButton = () => {
if (window.scrollY > 300) {
backButton.classList.add("show");
} else {
backButton.classList.remove("show");
}
};
backButton.addEventListener("click", () => {
if (window.history.length > 1) {
window.history.back();
} else {
window.location.href = "/";
}
});
window.addEventListener("scroll", toggleBackButton, {
passive: true,
});
toggleBackButton();
backButton.dataset.bound = "true";
}
};
2026-04-15 23:43:42 +00:00
const updateProgress = () => {
2026-04-11 17:46:53 +00:00
const doc = document.documentElement;
const scrollTop = window.scrollY || doc.scrollTop;
const scrollHeight = doc.scrollHeight - window.innerHeight;
const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
const bar = document.getElementById("reading-progress");
if (bar) {
bar.style.width = `${Math.min(progress, 100)}%`;
}
};
updateProgress();
window.addEventListener("scroll", updateProgress, { passive: true });
window.addEventListener("resize", updateProgress);
initFloatingActions();
document.addEventListener("astro:page-load", initFloatingActions);
</script>
2026-03-24 18:01:45 +00:00
<style>
2026-09-17 05:35:59 +00:00
.post-layout {
max-width: 850px;
margin: 0 auto;
}
2026-04-15 23:43:42 +00:00
#reading-progress {
2026-04-11 17:46:53 +00:00
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 0;
height: 3px;
background: #5a89ff;
transition: width 0.08s linear;
pointer-events: none;
}
.back-button {
position: fixed;
top: 1.25rem;
left: 1.25rem;
width: 3rem;
height: 3rem;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
2026-04-24 21:01:08 +00:00
border: #536493 1.5px solid;
border-top: #536493 2.5px dotted;
border-left: #536493 2.5px dotted;
border-right: #536493 2.5px solid;
border-bottom: #536493 2.5px solid;
background: rgba(255, 255, 255, 0.92);
color: #222;
2026-04-11 17:46:53 +00:00
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
backdrop-filter: blur(6px);
z-index: 1000;
visibility: hidden;
2026-04-11 17:46:53 +00:00
transform: translateY(-80px);
/* opacity: 0; */
transition:
opacity 0.2s ease,
2026-04-15 23:43:42 +00:00
transform 0.27s ease,
visibility 0.2s ease,
box-shadow 0.2s ease,
background 0.2s ease,
color 0.2s ease;
}
.back-button.show {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.back-button.show:hover {
transform: translateY(-2.5px);
box-shadow: 2px 3px 0px var(--deep-blue);
}
.back-button:hover {
transform: translateY(-2px);
}
:global(.dark) .back-button {
background: rgba(34, 34, 34, 0.92);
color: #f5f5f5;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
border: #2f406d 2px solid;
}
@media (max-width: 640px) {
.back-button {
top: 1rem;
left: 1rem;
width: 2.75rem;
height: 2.75rem;
}
}
.post-content {
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;
2026-04-16 17:50:59 +00:00
font-weight: 400;
}
.post-content :global(code),
.post-content :global(pre),
.post-content :global(kbd),
.post-content :global(samp) {
font-family: "Maple Mono", "Maple Mono CN", monospace;
}
2026-03-24 18:01:45 +00:00
a {
2026-03-25 07:25:40 +00:00
color: var(--post-link-color);
}
.post-header {
display: grid;
grid-template-columns: minmax(0, 1fr) 300px;
align-items: start;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.post-meta {
min-width: 0;
}
.post-title {
margin: 0 0 0.5rem 0;
line-height: 1.25;
overflow-wrap: anywhere;
word-break: break-word;
}
.description {
margin: 0 0 0.7rem 0;
opacity: 0.9;
line-height: 1.6;
overflow-wrap: anywhere;
word-break: break-word;
}
2026-09-17 07:26:33 +00:00
.post-byline {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.3rem 0.55rem;
2026-03-25 07:25:40 +00:00
margin: 0.3rem 0;
2026-09-17 07:26:33 +00:00
color: #66717c;
font-family: "Noto Sans SC", system-ui, sans-serif;
font-size: 0.8rem;
font-weight: 400;
line-height: 1.5;
2026-03-25 07:25:40 +00:00
overflow-wrap: anywhere;
word-break: break-word;
}
2026-09-17 07:26:33 +00:00
.byline-separator {
opacity: 0.65;
}
:global(html.dark) .post-byline {
color: #aab7c4;
}
2026-03-25 07:25:40 +00:00
.post-cover {
width: 300px;
height: auto;
2026-04-15 23:43:42 +00:00
aspect-ratio: 16 / 10;
2026-03-25 07:25:40 +00:00
object-fit: cover;
object-position: center;
display: block;
2026-03-24 18:01:45 +00:00
}
.post-divider {
border-top: 1px dashed #dfe4e9;
margin: 1.2rem 0 1.5rem;
}
:global(.dark) .post-divider {
border-top-color: #7f91a3;
}
2026-03-24 18:01:45 +00:00
.tags {
display: flex;
flex-wrap: wrap;
2026-09-17 07:05:12 +00:00
gap: 0.25rem 0.9rem;
2026-03-25 07:25:40 +00:00
margin-top: 0.8rem;
min-width: 0;
font-size: 0.88rem;
}
2026-09-17 07:10:30 +00:00
@media (max-width: 560px) {
2026-03-25 07:25:40 +00:00
.post-header {
grid-template-columns: 1fr;
}
.post-cover {
width: 100%;
max-width: 100%;
2026-04-15 23:43:42 +00:00
aspect-ratio: 16 / 9;
2026-03-25 07:25:40 +00:00
}
2026-03-24 18:01:45 +00:00
}
2026-06-01 18:35:05 +00:00
details {
margin: 1.5rem 0;
overflow: hidden;
border-top: 2px solid #1e3a5f;
border-radius: 0 0 0.5rem 0.5rem;
background: rgba(128, 128, 128, 0.14);
}
details summary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.8rem 0.9rem;
cursor: pointer;
font-weight: 600;
user-select: none;
list-style: none;
}
details summary::-webkit-details-marker {
display: none;
}
details summary::before {
content: "›";
display: inline-block;
font-size: 1.25rem;
line-height: 1;
transition: transform 0.2s ease;
}
details[open] summary::before {
transform: rotate(90deg);
}
/* 展开内容 */
details > :not(summary) {
box-sizing: border-box;
margin-top: 0;
margin-bottom: 0;
padding-right: 1rem;
}
2026-06-01 18:35:05 +00:00
/* 为列表序号预留空间 */
details > ol,
details > ul {
padding: 0.6rem 1rem 1rem 3rem;
}
details li {
margin: 0.8rem 0;
}
blockquote {
margin: 1.25rem 0;
padding: 0.75rem 1rem;
border-left: 4px solid var(--deep-red);
background: rgba(128, 128, 128, 0.1);
border-radius: 0 0.4rem 0.4rem 0;
}
blockquote p {
margin: 0;
}
blockquote p + p {
margin-top: 0.75rem;
}
/* 块级公式:超出屏幕时只滚动公式,不撑宽整篇文章 */
.katex-display {
2026-06-01 18:35:05 +00:00
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 0.25rem 0;
-webkit-overflow-scrolling: touch;
}
2026-06-01 18:35:05 +00:00
/* 避免 KaTeX 内部元素强行把父容器撑宽 */
.katex-display > .katex {
2026-06-01 18:35:05 +00:00
min-width: max-content;
}
2026-03-24 18:01:45 +00:00
</style>