refactor: content collection config

This commit is contained in:
radishzzz 2025-03-18 01:19:24 +00:00
parent 473b13d0ab
commit 0dcd3a5838
2 changed files with 72 additions and 58 deletions

16
pnpm-lock.yaml generated
View file

@ -1158,8 +1158,8 @@ packages:
cpu: [x64]
os: [win32]
'@vitest/eslint-plugin@1.1.37':
resolution: {integrity: sha512-cnlBV8zr0oaBu1Vk6ruqWzpPzFCtwY0yuwUQpNIeFOUl3UhXVwNUoOYfWkZzeToGuNBaXvIsr6/RgHrXiHXqXA==}
'@vitest/eslint-plugin@1.1.38':
resolution: {integrity: sha512-KcOTZyVz8RiM5HyriiDVrP1CyBGuhRxle+lBsmSs6NTJEO/8dKVAq+f5vQzHj1/Kc7bYXSDO6yBe62Zx0t5iaw==}
peerDependencies:
'@typescript-eslint/utils': ^8.24.0
eslint: '>= 8.57.0'
@ -1690,8 +1690,8 @@ packages:
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
electron-to-chromium@1.5.119:
resolution: {integrity: sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==}
electron-to-chromium@1.5.120:
resolution: {integrity: sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==}
emmet@2.4.11:
resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==}
@ -3852,7 +3852,7 @@ snapshots:
'@stylistic/eslint-plugin': 4.2.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/eslint-plugin': 8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@vitest/eslint-plugin': 1.1.37(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@vitest/eslint-plugin': 1.1.38(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
ansis: 3.17.0
cac: 6.7.14
eslint: 9.22.0(jiti@2.4.2)
@ -4933,7 +4933,7 @@ snapshots:
'@unrs/rspack-resolver-binding-win32-x64-msvc@1.1.2':
optional: true
'@vitest/eslint-plugin@1.1.37(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@vitest/eslint-plugin@1.1.38(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint: 9.22.0(jiti@2.4.2)
@ -5349,7 +5349,7 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001705
electron-to-chromium: 1.5.119
electron-to-chromium: 1.5.120
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
@ -5608,7 +5608,7 @@ snapshots:
duplexer@0.1.2: {}
electron-to-chromium@1.5.119: {}
electron-to-chromium@1.5.120: {}
emmet@2.4.11:
dependencies:

View file

@ -1,34 +1,31 @@
import type { CollectionEntry } from 'astro:content'
import themeConfig from '@/config'
import { supportedLangs } from '@/i18n/config'
import { defaultLocale } from '@/config'
import { getCollection } from 'astro:content'
// Type definitions
export type Post = CollectionEntry<'posts'> & {
remarkPluginFrontmatter?: {
minutes?: number
remarkPluginFrontmatter: {
minutes: number
}
}
export type PostData = Post['data']
export type PostsGroupByYear = Map<number, Post[]>
// Get post metadata including reading time
async function getPostMeta(post: CollectionEntry<'posts'>): Promise<Post> {
// Add metadata including reading time to post
async function addMetaToPost(post: CollectionEntry<'posts'>): Promise<Post> {
const { remarkPluginFrontmatter } = await post.render()
return { ...post, remarkPluginFrontmatter }
return { ...post, remarkPluginFrontmatter: remarkPluginFrontmatter as { minutes: number } }
}
/**
* Check if the post slug is duplicated under the same language
* Find duplicate post slugs within the same language
* @param posts Array of blog posts
* @returns Array of duplicate slugs with language information
* @returns Array of descriptive error messages for duplicate slugs
*/
export async function checkPostSlugDuplication(posts: Post[]): Promise<string[]> {
export async function checkPostSlugDuplication(posts: CollectionEntry<'posts'>[]): Promise<string[]> {
const slugMap = new Map<string, Set<string>>()
const duplicates: string[] = []
posts.forEach((post) => {
const lang = post.data.lang || ''
const lang = post.data.lang
const slug = post.data.abbrlink || post.slug
if (!slugMap.has(lang)) {
@ -52,36 +49,45 @@ export async function checkPostSlugDuplication(posts: Post[]): Promise<string[]>
return duplicates
}
// Get all posts except drafts (include pinned)
/**
* Get all posts (including pinned ones, excluding drafts in production)
* @param lang Language code, optional, defaults to site's default language
* @returns Posts filtered by language, enhanced with metadata, sorted by date
*/
export async function getPosts(lang?: string) {
const defaultLocale = themeConfig.global.locale
const currentLang = lang || defaultLocale
const posts = await getCollection(
const filteredPosts = await getCollection(
'posts',
({ data }: Post) => {
({ data }: CollectionEntry<'posts'>) => {
// Show drafts in dev mode only
const shouldInclude = import.meta.env.DEV || !data.draft
if (!supportedLangs.includes(currentLang)) {
return shouldInclude && data.lang === ''
}
return shouldInclude && (data.lang === currentLang || data.lang === '')
},
)
const postsWithMeta = await Promise.all(posts.map(getPostMeta))
const enhancedPosts = await Promise.all(filteredPosts.map(addMetaToPost))
return postsWithMeta.sort((a: Post, b: Post) =>
return enhancedPosts.sort((a: Post, b: Post) =>
b.data.published.valueOf() - a.data.published.valueOf(),
)
}
// Get all posts except drafts (not pinned)
/**
* Get all non-pinned posts
* @param lang Language code, optional, defaults to site's default language
* @returns Regular posts (not pinned), already filtered by language and drafts
*/
export async function getRegularPosts(lang?: string) {
const posts = await getPosts(lang)
return posts.filter(post => !post.data.pin || post.data.pin === 0)
return posts.filter(post => !post.data.pin)
}
// Get pinned posts
/**
* Get pinned posts and sort by pin priority
* @param lang Language code, optional, defaults to site's default language
* @returns Pinned posts sorted by pin value in descending order
*/
export async function getPinnedPosts(lang?: string) {
const posts = await getPosts(lang)
return posts
@ -89,8 +95,12 @@ export async function getPinnedPosts(lang?: string) {
.sort((a, b) => (b.data.pin || 0) - (a.data.pin || 0))
}
// Get posts grouped by year (not pinned)
export async function getPostsByYear(lang?: string): Promise<PostsGroupByYear> {
/**
* Group posts by year and sort within each year
* @param lang Language code, optional, defaults to site's default language
* @returns Map of posts grouped by year (descending), with posts in each year sorted by date (descending)
*/
export async function getPostsByYear(lang?: string): Promise<Map<number, Post[]>> {
const posts = await getRegularPosts(lang)
const yearMap = new Map<number, Post[]>()
@ -99,26 +109,25 @@ export async function getPostsByYear(lang?: string): Promise<PostsGroupByYear> {
if (!yearMap.has(year)) {
yearMap.set(year, [])
}
yearMap.get(year)?.push(post)
yearMap.get(year)!.push(post)
})
yearMap.forEach((yearPosts) => {
yearPosts.sort((a: Post, b: Post) => {
yearPosts.sort((a, b) => {
const aDate = a.data.published
const bDate = b.data.published
const monthDiff = bDate.getMonth() - aDate.getMonth()
if (monthDiff !== 0) {
return monthDiff
}
return bDate.getDate() - aDate.getDate()
return bDate.getMonth() - aDate.getMonth() || bDate.getDate() - aDate.getDate()
})
})
return new Map([...yearMap.entries()].sort((a, b) => b[0] - a[0]))
}
// Get all tags sorted by post count
/**
* Get all tags sorted by post count
* @param lang Language code, optional, defaults to site's default language
* @returns Array of tags sorted by popularity (most posts first)
*/
export async function getAllTags(lang?: string) {
const tagMap = await getPostsGroupByTags(lang)
const tagsWithCount = Array.from(tagMap.entries())
@ -127,29 +136,34 @@ export async function getAllTags(lang?: string) {
return tagsWithCount.map(([tag]) => tag)
}
// Get posts grouped by tags
/**
* Group posts by their tags
* @param lang Language code, optional, defaults to site's default language
* @returns Map where keys are tag names and values are arrays of posts with that tag
*/
export async function getPostsGroupByTags(lang?: string) {
const posts = await getPosts(lang)
const tagMap = new Map<string, Post[]>()
posts.forEach((post: Post) => {
if (post.data.tags && post.data.tags.length > 0) {
post.data.tags.forEach((tag: string) => {
if (!tagMap.has(tag)) {
tagMap.set(tag, [])
}
tagMap.get(tag)?.push(post)
})
}
post.data.tags?.forEach((tag: string) => {
if (!tagMap.has(tag)) {
tagMap.set(tag, [])
}
tagMap.get(tag)!.push(post)
})
})
return tagMap
}
// Get all posts by specific tag
/**
* Get all posts that contain a specific tag
* @param tag The tag name to filter posts by
* @param lang Language code, optional, defaults to site's default language
* @returns Array of posts that contain the specified tag
*/
export async function getPostsByTag(tag: string, lang?: string) {
const posts = await getPosts(lang)
return posts.filter((post: Post) =>
post.data.tags?.includes(tag),
)
const tagMap = await getPostsGroupByTags(lang)
return tagMap.get(tag) || []
}