mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-15 19:22:52 +02:00
refactor: content collection config
This commit is contained in:
parent
473b13d0ab
commit
0dcd3a5838
2 changed files with 72 additions and 58 deletions
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
|
@ -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:
|
||||
|
|
|
@ -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) || []
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue