From 0dcd3a5838224d014731feb2a20b20e5be2304db Mon Sep 17 00:00:00 2001 From: radishzzz Date: Tue, 18 Mar 2025 01:19:24 +0000 Subject: [PATCH] refactor: content collection config --- pnpm-lock.yaml | 16 +++--- src/utils/content.ts | 114 ++++++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc6d179..75e74d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/utils/content.ts b/src/utils/content.ts index b2dfdaa..ee9ed8f 100644 --- a/src/utils/content.ts +++ b/src/utils/content.ts @@ -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 -// Get post metadata including reading time -async function getPostMeta(post: CollectionEntry<'posts'>): Promise { +// Add metadata including reading time to post +async function addMetaToPost(post: CollectionEntry<'posts'>): Promise { 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 { +export async function checkPostSlugDuplication(posts: CollectionEntry<'posts'>[]): Promise { const slugMap = new Map>() 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 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 { +/** + * 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> { const posts = await getRegularPosts(lang) const yearMap = new Map() @@ -99,26 +109,25 @@ export async function getPostsByYear(lang?: string): Promise { 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() 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) || [] }