11---
22import SiteLayout from ' ../layouts/SiteLayout.astro' ;
3- import { allPosts , dateLabel , seriesSlug , taxonomyUrl , courseOrder } from ' ../lib/posts' ;
3+ import ArchivePagination from ' ../components/ArchivePagination.astro' ;
4+ import { allPosts , dateLabel , seriesSlug , taxonomyUrl , courseOrder , ARCHIVE_PAGE_SIZE , archivePageUrl } from ' ../lib/posts' ;
45import legacyRoutes from ' ../../migration/legacy-routes.json' ;
56import baseline from ' ../../migration/baseline.json' ;
67export async function getStaticPaths() {
78 const posts = await allPosts ();
89 const paths = posts .map (post => ({ params: { path: post .url .replace (/ ^ \/ + | \/ + $ / g , ' ' ) }, props: { post , posts } }));
910 const occupied = new Set (posts .map (post => post .url ));
1011 for (const listing of legacyRoutes ) {
11- if (listing .kind === ' links' ) continue ;
12+ if (listing .kind === ' links' || listing . kind === ' archives ' ) continue ;
1213 if (! occupied .has (listing .url )) paths .push ({ params: { path: listing .url .replace (/ ^ \/ + | \/ + $ / g , ' ' ) }, props: { listing , posts } });
1314 }
14- for (const kind of [' tags' , ' categories' , ' archives ' ]) {
15- if (! legacyRoutes .some (route => route .url === ` /${kind }/ ` )) paths .push ({ params: { path: kind }, props: { listing: { url: ` /${kind }/ ` , kind , title: kind === ' tags' ? ' 标签' : kind === ' categories ' ? ' 分类 ' : ' 文章归档 ' }, posts } });
15+ for (const kind of [' tags' , ' categories' ]) {
16+ if (! legacyRoutes .some (route => route .url === ` /${kind }/ ` )) paths .push ({ params: { path: kind }, props: { listing: { url: ` /${kind }/ ` , kind , title: kind === ' tags' ? ' 标签' : ' 分类 ' }, posts } });
1617 }
1718 const existing = new Set (paths .map (item => item .params .path ));
1819 for (const kind of [' tags' , ' categories' ] as const ) {
@@ -26,10 +27,35 @@ export async function getStaticPaths() {
2627 }
2728 }
2829 }
30+ const archiveGroups = new Map ([[' /archives/' , posts ]]);
31+ for (const post of posts ) {
32+ const date = dateLabel (post .date );
33+ for (const prefix of [date .slice (0 , 4 ), date .slice (0 , 7 ).replace (' -' , ' /' )]) {
34+ const baseUrl = ` /archives/${prefix }/ ` ;
35+ if (! archiveGroups .has (baseUrl )) archiveGroups .set (baseUrl , []);
36+ archiveGroups .get (baseUrl ).push (post );
37+ }
38+ }
39+ for (const [baseUrl, members] of archiveGroups ) {
40+ const totalPages = Math .max (1 , Math .ceil (members .length / ARCHIVE_PAGE_SIZE ));
41+ const period = baseUrl .replace (/ ^ \/ archives\/ / , ' ' ).replace (/ \/ $ / , ' ' ).replace (' /' , ' -' );
42+ for (let page = 1 ; page <= totalPages ; page ++ ) {
43+ const url = archivePageUrl (baseUrl , page );
44+ const route = url .replace (/ ^ \/ + | \/ + $ / g , ' ' );
45+ if (! existing .has (route )) {
46+ paths .push ({ params: { path: route }, props: {
47+ listing: { url , kind: ' archives' , title: period ? ` ${period } · 文章归档 ` : ' 归档' },
48+ posts ,
49+ archive: { baseUrl , currentPage: page , totalPages , totalItems: members .length , items: members .slice ((page - 1 ) * ARCHIVE_PAGE_SIZE , page * ARCHIVE_PAGE_SIZE ) },
50+ } });
51+ existing .add (route );
52+ }
53+ }
54+ }
2955 return paths ;
3056}
31- const { post, listing, posts } = Astro .props ;
32- let selected = posts ;
57+ const { post, listing, posts, archive } = Astro .props ;
58+ let selected = archive ?. items ?? posts ;
3359let listingTitle = listing ?.title ;
3460if (listing ?.kind === ' tags' || listing ?.kind === ' categories' ) {
3561 const prefix = listing .kind === ' tags' ? ' 标签' : ' 分类' ;
@@ -42,10 +68,7 @@ if (listing?.kind === 'tags' || listing?.kind === 'categories') {
4268 }
4369 }
4470}
45- if (listing ?.kind === ' archives' ) {
46- const match = listing .url .match (/ ^ \/ archives\/ (\d {4} )(?:\/ (\d {2} ))? / );
47- if (match ) { const prefix = match [1 ] + (match [2 ] ? ` -${match [2 ]} ` : ' ' ); selected = posts .filter (item => dateLabel (item .date ).startsWith (prefix )); listingTitle = ` ${prefix } · 文章归档 ` ; }
48- }
71+ const pageTitle = archive ?.currentPage > 1 ? ` ${listingTitle } · 第 ${archive .currentPage } 页 ` : listingTitle ;
4972const taxonomy = listing ?.url === ' /tags/' ? ' tags' : listing ?.url === ' /categories/' ? ' categories' : null ;
5073const labels = taxonomy ? [... new Set (posts .flatMap (item => item [taxonomy ]))].sort ((a ,b )=> a .localeCompare (b ,' zh-CN' )) : [];
5174const inSeries = post ?.series ? posts .filter (item => item .series === post .series ).sort ((a ,b )=> (a .series_order ?? 999 )- (b .series_order ?? 999 )|| a .title .localeCompare (b .title ,' zh-CN' )) : [];
@@ -57,15 +80,15 @@ const courseIndex = post ? coursePosts.findIndex(item => item.url === post.url)
5780const previousCoursePost = courseIndex > 0 ? coursePosts [courseIndex - 1 ] : null ;
5881const nextCoursePost = courseIndex >= 0 ? coursePosts [courseIndex + 1 ] : null ;
5982---
60- <SiteLayout title ={ post ?.title || listingTitle } description ={ post ?.description || ' SpriCoder 的文章归档与分类。' } article ={ Boolean (post )} >
83+ <SiteLayout title ={ post ?.title || pageTitle } description ={ post ?.description || ' SpriCoder 的文章归档与分类。' } article ={ Boolean (post )} >
6184 { post ? <div class = " container article-container" >
6285 <header class = " article-header" ><a class = " breadcrumb" href = { post .course ? ` /courses/${post .course }/ ` : post .series ? ` /series/${seriesSlug (post .series )}/ ` : ' /archives/' } >← { post .courseName || post .series || ' 文章归档' } </a ><h1 >{ post .title } </h1 ><div class = " article-meta" ><time datetime = { post .date } >{ dateLabel (post .date )} </time ><span >约 { post .readingMinutes } 分钟阅读</span ><span id = " leancloud-page-views-container" data-counter-path = { post .counterPath } >阅读 <span id = " leancloud-page-views" >—</span > 次</span ></div ></header >
6386 <div class = " article-layout" ><article class = " article-content" data-pagefind-body data-pagefind-meta = { ` title:${post .title } ` } set :html = { post .html } ></article >{ post .headings .length > 0 && <aside class = " article-toc" aria-label = " 文章目录" ><details ><summary >本文目录</summary ><nav >{ post .headings .map (heading => <a class :list = { [{' toc-sub' :heading .level === 3 }]} href = { ` #${heading .id } ` } >{ heading .text } </a >)} </nav ></details ></aside >} </div >
6487 { post .tags .length > 0 && <div class = " tags" >{ post .tags .map (tag => <a href = { taxonomyUrl (' tags' ,tag )} >#{ tag } </a >)} </div >}
6588 { post .course && <section class = " series-navigation" ><a class = " text-link" href = { ` /courses/${post .course }/ ` } >返回「{ post .courseName } 」课程目录 →</a ><div class = " post-grid" >{ previousCoursePost && <div ><p class = " eyebrow" >上一篇课程笔记</p ><a href = { previousCoursePost .url } >{ previousCoursePost .title } </a ></div >} { nextCoursePost && <div ><p class = " eyebrow" >下一篇课程笔记</p ><a href = { nextCoursePost .url } >{ nextCoursePost .title } </a ></div >} </div ></section >}
6689 { post .series && <section class = " series-navigation" ><a class = " text-link" href = { ` /series/${seriesSlug (post .series )}/ ` } >查看「{ post .series } 」完整目录 →</a ><div class = " post-grid" >{ previous && <div ><p class = " eyebrow" >上一篇</p ><a href = { previous .url } >{ previous .title } </a ></div >} { next && <div ><p class = " eyebrow" >下一篇</p ><a href = { next .url } >{ next .title } </a ></div >} </div ></section >}
6790 <section class = " comments" aria-label = " 文章评论" ><h2 >评论</h2 ><script is :inline src = " https://utteranc.es/client.js" repo = " SpriCoder/spricoder-blog-comment" issue-term = { post .counterPath !== post .url ? encodeURI (post .counterPath ) : ' pathname' } theme = " preferred-color-scheme" crossorigin = " anonymous" async ></script ></section >
68- </div > : <div class = " container" ><header class = " page-header" ><h1 >{ listingTitle } </h1 ><p >{ taxonomy ? ` ${labels .length } 个${taxonomy === ' tags' ? ' 标签' : ' 分类' } ` : ` ${selected .length } 篇文章 ` } </ p ><div class = " archive-nav" ><a href = " /archives/" >全部文章</a ><a href = " /categories/" >按分类</a ><a href = " /tags/" >按标签</a ></div ></header >{ taxonomy ? <div class = " tags taxonomy-tags" >{ labels .map (label => <a href = { taxonomyUrl (taxonomy ,label )} >{ label } <span >{ posts .filter (item => item [taxonomy ].includes (label )).length } </span ></a >)} </div > : <ul class = " archive-list" >{ selected .map (item => <li ><time datetime = { item .date } >{ dateLabel (item .date )} </time ><a href = { item .url } >{ item .title } </a ></li >)} </ul >} </div >}
91+ </div > : <div class = " container" ><header class = " page-header" ><h1 >{ listingTitle } </h1 ><p >{ taxonomy ? ` ${labels .length } 个${taxonomy === ' tags' ? ' 标签' : ' 分类' } ` : ` ${archive ?. totalItems ?? selected .length } 篇文章 ` } { archive && < span > · 第 { archive . currentPage } / { archive . totalPages } 页</ span > } </ p ><div class = " archive-nav" ><a href = " /archives/" >全部文章</a ><a href = " /categories/" >按分类</a ><a href = " /tags/" >按标签</a ></div ></header >{ taxonomy ? <div class = " tags taxonomy-tags" >{ labels .map (label => <a href = { taxonomyUrl (taxonomy ,label )} >{ label } <span >{ posts .filter (item => item [taxonomy ].includes (label )).length } </span ></a >)} </div > : <ul class = " archive-list" >{ selected .map (item => <li ><time datetime = { item .date } >{ dateLabel (item .date )} </time ><a href = { item .url } >{ item .title } </a ></li >)} </ul > } { archive && < ArchivePagination currentPage = { archive . currentPage } totalPages = { archive . totalPages } baseUrl = { archive . baseUrl } / >} </div >}
6992</SiteLayout >
7093<script >
7194 const content = document.querySelector('.article-content');
0 commit comments