阅读进度 + 继续阅读 + 文章系列 + 完整 SEO

This commit is contained in:
biss committed 2026-07-17 12:34:19 +08:00
1 parent 336dcc7670
commit 6ac59f404b
9 files changed
+424 -4

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
---
import type { BlogPost } from '@/lib/posts';
import { siteConfig } from '../../site.config.mjs';
interface Props {
currentPost: BlogPost;
posts: BlogPost[];
}
const { currentPost, posts } = Astro.props;
const currentIndex = posts.findIndex((post) => post.slug === currentPost.slug);
const previousPost = currentIndex > 0 ? posts[currentIndex - 1] : undefined;
const nextPost = currentIndex >= 0 && currentIndex < posts.length - 1 ? posts[currentIndex + 1] : undefined;
const labels = siteConfig.series?.labels ?? {};
const seriesKey = currentPost.series ?? '';
const seriesTitle = labels[seriesKey] ?? seriesKey;
---
<section class="post-series" aria-labelledby="post-series-title">
<header class="post-series-head">
<div>
<span>ARTICLE SERIES</span>
<h2 id="post-series-title">{seriesTitle}</h2>
</div>
<strong>第 {currentIndex + 1} / {posts.length} 篇</strong>
</header>
<ol class="post-series-list">
{posts.map((post, index) => (
<li class:list={{ 'is-current': post.slug === currentPost.slug }}>
<a href={post.href} aria-current={post.slug === currentPost.slug ? 'page' : undefined}>
<span>{String(index + 1).padStart(2, '0')}</span>
<strong>{post.title}</strong>
</a>
</li>
))}
</ol>
{(previousPost || nextPost) && (
<nav class="post-series-nav" aria-label="系列文章导航">
{previousPost ? (
<a href={previousPost.href} rel="prev"><span>上一篇</span><strong>{previousPost.title}</strong></a>
) : <span></span>}
{nextPost ? (
<a href={nextPost.href} rel="next"><span>下一篇</span><strong>{nextPost.title}</strong></a>
) : <span></span>}
</nav>
)}
</section>
+3
View File
@@ -6,6 +6,7 @@ const posts = defineCollection({
schema: z.object({
title: z.any().optional(),
date: z.any().optional(),
updated: z.any().optional(),
abbrlink: z.any().optional(),
tags: z.any().optional(),
category: z.any().optional(),
@@ -14,6 +15,8 @@ const posts = defineCollection({
description: z.any().optional(),
comments: z.any().optional(),
cover: z.any().optional(),
series: z.any().optional(),
seriesOrder: z.any().optional(),
}).passthrough(),
});
+42 -1
View File
@@ -7,11 +7,31 @@ import { publicSiteConfig, siteConfig } from '../../site.config.mjs';
interface Props {
title?: string;
description?: string;
canonicalPath?: string;
image?: string;
type?: 'website' | 'article';
publishedTime?: string;
modifiedTime?: string;
keywords?: string[];
jsonLd?: Record<string, unknown>;
}
const siteTitle = siteConfig.site.name;
const { title = siteTitle, description = siteConfig.site.description } = Astro.props;
const {
title = siteTitle,
description = siteConfig.site.description,
canonicalPath = Astro.url.pathname,
image = siteConfig.author.avatar,
type = 'website',
publishedTime,
modifiedTime,
keywords = [],
jsonLd,
} = Astro.props;
const pageTitle = title === siteTitle ? siteTitle : `${title} | ${siteTitle}`;
const canonicalUrl = new URL(canonicalPath, siteConfig.site.url).href;
const socialImageUrl = new URL(image, siteConfig.site.url).href;
const jsonLdHtml = jsonLd ? JSON.stringify(jsonLd).replace(/</g, '\\u003c') : '';
const currentYear = new Date().getFullYear();
const publicConfigJson = JSON.stringify(publicSiteConfig).replace(/</g, '\\u003c');
const consoleSites = [
@@ -48,6 +68,27 @@ const consoleSites = [
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<meta name="author" content={siteConfig.author.name} />
{keywords.length > 0 && <meta name="keywords" content={keywords.join(', ')} />}
<link rel="canonical" href={canonicalUrl} />
<link rel="alternate" type="application/rss+xml" title={`${siteTitle} RSS`} href={new URL('/rss.xml', siteConfig.site.url).href} />
<meta property="og:site_name" content={siteTitle} />
<meta property="og:locale" content="zh_CN" />
<meta property="og:type" content={type} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:image" content={socialImageUrl} />
<meta property="og:image:alt" content={title} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={socialImageUrl} />
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
{type === 'article' && <meta property="article:author" content={siteConfig.author.name} />}
{keywords.map((keyword) => <meta property="article:tag" content={keyword} />)}
{jsonLd && <script type="application/ld+json" is:inline set:html={jsonLdHtml}></script>}
<link rel="icon" href={siteConfig.assets.icon} />
<script defer src={siteConfig.analytics.umami.script} data-website-id={siteConfig.analytics.umami.websiteId}></script>
<script is:inline set:html={`window.SITE_CONFIG = ${publicConfigJson};`}></script>
Loaded 3 of 9 files, more files were not shown because too many files have changed in this diff. Show more