diff --git a/site.config.mjs b/site.config.mjs index 2be02d6..5247af8 100644 --- a/site.config.mjs +++ b/site.config.mjs @@ -47,6 +47,13 @@ export const siteConfig = { pagination: { postsPerPage: 16, }, + series: { + labels: { + webcustom: '博客定制手记', + opencv: 'OpenCV 学习笔记', + OpenGL: 'OpenGL 学习笔记', + }, + }, comments: { envId: 'https://comment.biss.click', region: '', diff --git a/src/components/PostSeries.astro b/src/components/PostSeries.astro new file mode 100644 index 0000000..6fcb7f0 --- /dev/null +++ b/src/components/PostSeries.astro @@ -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; +--- + +
+
+
+ ARTICLE SERIES +

{seriesTitle}

+
+ 第 {currentIndex + 1} / {posts.length} 篇 +
+ +
    + {posts.map((post, index) => ( +
  1. + + {String(index + 1).padStart(2, '0')} + {post.title} + +
  2. + ))} +
+ + {(previousPost || nextPost) && ( + + )} +
diff --git a/src/content.config.ts b/src/content.config.ts index 9ded44a..c6be834 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -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(), }); diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 55bad77..6034080 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -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; } 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(/ + + {keywords.length > 0 && } + + + + + + + + + + + + + + + {publishedTime && } + {modifiedTime && } + {type === 'article' && } + {keywords.map((keyword) => )} + {jsonLd && } diff --git a/src/lib/posts.ts b/src/lib/posts.ts index 247786c..4838c96 100644 --- a/src/lib/posts.ts +++ b/src/lib/posts.ts @@ -7,8 +7,12 @@ export type BlogPost = { slug: string; date: Date; dateText: string; + updated?: Date; + updatedText?: string; tags: string[]; categories: string[]; + series?: string; + seriesOrder?: number; summary: string; comments: boolean; cover?: string; @@ -44,6 +48,12 @@ function toDate(value: unknown): Date { return new Date(0); } +function toOptionalDate(value: unknown): Date | undefined { + if (value === undefined || value === null || value === '') return undefined; + const date = toDate(value); + return date.valueOf() > 0 ? date : undefined; +} + function formatDate(date: Date): string { return new Intl.DateTimeFormat('zh-CN', { year: 'numeric', @@ -79,8 +89,13 @@ export async function getAllPosts(): Promise { return entries .map((entry) => { const date = toDate(entry.data.date); + const updated = toOptionalDate(entry.data.updated); const slug = String(entry.data.abbrlink ?? fallbackSlug(entry.id)); const title = String(entry.data.title ?? slug); + const series = typeof entry.data.series === 'string' && entry.data.series.trim() + ? entry.data.series.trim() + : undefined; + const rawSeriesOrder = Number(entry.data.seriesOrder); return { entry, @@ -88,8 +103,12 @@ export async function getAllPosts(): Promise { slug, date, dateText: formatDate(date), + updated, + updatedText: updated ? formatDate(updated) : undefined, tags: asArray(entry.data.tags), categories: getCategories(entry), + series, + seriesOrder: Number.isFinite(rawSeriesOrder) ? rawSeriesOrder : undefined, summary: makeSummary(entry), comments: entry.data.comments !== false, cover: typeof entry.data.cover === 'string' ? entry.data.cover : undefined, diff --git a/src/pages/index.astro b/src/pages/index.astro index 5f07eee..91cf228 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -20,10 +20,10 @@ const pageData = getPageSlice(posts, 1);

{siteConfig.site.name}

把折腾、学习和生活片段安静地收在这里。

-