diff options
| author | Bertrand Yuan <bert.yuan@outlook.com> | 2025-12-16 00:25:04 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-16 00:25:04 +0800 |
| commit | 39c83fbb69ef06d2d56790d75abc254ba7e34394 (patch) | |
| tree | dd006593448c3500bdcb414af3b4656f7a7683d4 /src/lib | |
| parent | 48b07bc308a35734a6a7a305c8fdccbfa47de7d8 (diff) | |
| parent | 785371bb3eccca455e5ce5fccbe9b6e3752a03f6 (diff) | |
Merge pull request #1 from bertyuan/feat-introduce-payloadv1.0
Feat: introduce payload
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/metadata-image.ts | 7 | ||||
| -rw-r--r-- | src/lib/payload-posts.ts | 192 | ||||
| -rw-r--r-- | src/lib/source.ts | 40 |
3 files changed, 192 insertions, 47 deletions
diff --git a/src/lib/metadata-image.ts b/src/lib/metadata-image.ts deleted file mode 100644 index f2b91b6..0000000 --- a/src/lib/metadata-image.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { source } from '@/lib/source'; -import { createMetadataImage } from 'fumadocs-core/server'; - -export const metadataImage = createMetadataImage({ - source, - imageRoute: 'og', -}); diff --git a/src/lib/payload-posts.ts b/src/lib/payload-posts.ts new file mode 100644 index 0000000..7cf1a56 --- /dev/null +++ b/src/lib/payload-posts.ts @@ -0,0 +1,192 @@ +import { getPayload } from 'payload' +import config from '@payload-config' +import type { Media } from '@/../payload-types' + +// Payload 文章类型 +export interface PayloadPost { + id: number + title: string + slug: string + description?: string + content: unknown // Lexical 富文本内容 + featuredImage?: Media | number + author?: string + tags?: { tag: string; id?: string }[] + status: 'draft' | 'published' + publishedAt?: string + createdAt: string + updatedAt: string +} + +// 转换后的文章类型(用于前端展示) +export interface BlogPost { + id: number + title: string + slug: string + url: string + description: string + content: unknown + image?: string + author: string + tags: string[] + date: Date + createdAt: Date + updatedAt: Date +} + +// 获取 Payload 实例 +async function getPayloadClient() { + return getPayload({ config }) +} + +// 将 Payload 文章转换为博客文章格式 +function transformPost(post: PayloadPost): BlogPost { + // 处理封面图片 URL + let imageUrl: string | undefined + if (post.featuredImage && typeof post.featuredImage === 'object') { + imageUrl = post.featuredImage.url || undefined + } + + // 处理标签数组 + const tags = post.tags?.map((t) => t.tag).filter(Boolean) || [] + + // 处理日期 + const date = post.publishedAt ? new Date(post.publishedAt) : new Date(post.createdAt) + + return { + id: post.id, + title: post.title, + slug: post.slug, + url: `/posts/${post.slug}`, + description: post.description || '', + content: post.content, + image: imageUrl, + author: post.author || 'Admin', + tags, + date, + createdAt: new Date(post.createdAt), + updatedAt: new Date(post.updatedAt), + } +} + +// 获取所有已发布的文章(按发布时间排序) +export async function getPublishedPosts(options?: { + limit?: number + page?: number +}): Promise<{ posts: BlogPost[]; totalDocs: number; totalPages: number }> { + const payload = await getPayloadClient() + + const result = await payload.find({ + collection: 'posts', + where: { + status: { equals: 'published' }, + }, + sort: '-publishedAt', + limit: options?.limit || 10, + page: options?.page || 1, + depth: 1, // 获取关联的 media 数据 + }) + + return { + posts: result.docs.map((doc) => transformPost(doc as unknown as PayloadPost)), + totalDocs: result.totalDocs, + totalPages: result.totalPages, + } +} + +// 根据 slug 获取单篇文章 +export async function getPostBySlug(slug: string): Promise<BlogPost | null> { + const payload = await getPayloadClient() + + const result = await payload.find({ + collection: 'posts', + where: { + slug: { equals: slug }, + status: { equals: 'published' }, + }, + limit: 1, + depth: 1, + }) + + if (result.docs.length === 0) { + return null + } + + return transformPost(result.docs[0] as unknown as PayloadPost) +} + +// 获取所有已发布文章的 slug(用于静态生成) +export async function getAllPostSlugs(): Promise<string[]> { + const payload = await getPayloadClient() + + const result = await payload.find({ + collection: 'posts', + where: { + status: { equals: 'published' }, + }, + limit: 1000, + depth: 0, + }) + + return result.docs.map((doc) => (doc as unknown as PayloadPost).slug) +} + +// 根据标签获取文章 +export async function getPostsByTag( + tag: string, + options?: { limit?: number; page?: number } +): Promise<{ posts: BlogPost[]; totalDocs: number; totalPages: number }> { + const payload = await getPayloadClient() + + const result = await payload.find({ + collection: 'posts', + where: { + and: [ + { status: { equals: 'published' } }, + { 'tags.tag': { equals: tag } }, + ], + }, + sort: '-publishedAt', + limit: options?.limit || 10, + page: options?.page || 1, + depth: 1, + }) + + return { + posts: result.docs.map((doc) => transformPost(doc as unknown as PayloadPost)), + totalDocs: result.totalDocs, + totalPages: result.totalPages, + } +} + +// 获取所有标签 +export async function getAllTags(): Promise<{ tag: string; count: number }[]> { + const payload = await getPayloadClient() + + const result = await payload.find({ + collection: 'posts', + where: { + status: { equals: 'published' }, + }, + limit: 1000, + depth: 0, + }) + + // 统计标签 + const tagCounts = new Map<string, number>() + + for (const doc of result.docs) { + const post = doc as unknown as PayloadPost + if (post.tags) { + for (const { tag } of post.tags) { + if (tag) { + tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1) + } + } + } + } + + return Array.from(tagCounts.entries()) + .map(([tag, count]) => ({ tag, count })) + .sort((a, b) => b.count - a.count) +} diff --git a/src/lib/source.ts b/src/lib/source.ts deleted file mode 100644 index 34bc7ac..0000000 --- a/src/lib/source.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { loader } from 'fumadocs-core/source'; -import type { InferMetaType, InferPageType } from 'fumadocs-core/source'; -import { createMDXSource } from 'fumadocs-mdx'; -import { blog } from '.source'; - -export const source = loader({ - baseUrl: '/posts', - source: createMDXSource(blog), -}); -export const { getPage: getPost, getPages: getPosts, pageTree } = source; - -export type Post = ReturnType<typeof getPost>; - -const posts = getPosts(); - -export const getSortedByDatePosts = () => - posts.toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime()); - -export const getTags = () => { - const tagSet = new Set<string>(); - - for (const post of posts) { - if (post.data.tags) { - for (const tag of post.data.tags) { - tagSet.add(tag); - } - } - } - - return Array.from(tagSet).toSorted(); -}; - -export const getPostsByTag = (tag: string) => { - return posts - .filter((post) => post.data.tags?.includes(tag)) - .toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime()); -}; - -export type Page = InferPageType<typeof source>; -export type Meta = InferMetaType<typeof source>; |
