diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc index e5023cb..201f9d2 100644 --- a/.cursor/rules/project-structure.mdc +++ b/.cursor/rules/project-structure.mdc @@ -30,7 +30,7 @@ alwaysApply: false ## Content Management - `content/`: MDX content files -- `content-collections.ts`: Content collection configuration +- `source.config.ts`: Fumadocs source configuration ## Environment - `env.example`: Environment variables template diff --git a/.vscode/settings.json b/.vscode/settings.json index 992492f..88531ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,8 @@ "search.exclude": { "**/node_modules": true, ".next": true, - ".content-collections": true, + ".source": true, + ".wrangler": true, + ".open-next": true } } \ No newline at end of file diff --git a/biome.json b/biome.json index 3883917..e0d7566 100644 --- a/biome.json +++ b/biome.json @@ -11,18 +11,17 @@ ".next/**", ".cursor/**", ".vscode/**", - ".content-collections/**", + ".source/**", "node_modules/**", "dist/**", "build/**", - "drizzle/**", + "src/db/**", "tailwind.config.ts", "src/components/ui/*.tsx", "src/components/magicui/*.tsx", "src/components/animate-ui/*.tsx", "src/components/tailark/*.tsx", "src/app/[[]locale]/preview/**", - "src/db/schema.ts", "src/payment/types.ts", "src/types/index.d.ts", "public/sw.js" @@ -70,18 +69,17 @@ ".next/**", ".cursor/**", ".vscode/**", - ".content-collections/**", + ".source/**", "node_modules/**", "dist/**", "build/**", - "drizzle/**", + "src/db/**", "tailwind.config.ts", "src/components/ui/*.tsx", "src/components/magicui/*.tsx", "src/components/animate-ui/*.tsx", "src/components/tailark/*.tsx", "src/app/[[]locale]/preview/**", - "src/db/schema.ts", "src/payment/types.ts", "src/types/index.d.ts", "public/sw.js" diff --git a/content-collections.ts b/content-collections.ts deleted file mode 100644 index 9afa2b8..0000000 --- a/content-collections.ts +++ /dev/null @@ -1,324 +0,0 @@ -import path from 'path'; -import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing'; -import { defineCollection, defineConfig } from '@content-collections/core'; -import { transformMDX } from '@fumadocs/content-collections/configuration'; - -/** - * 1. Content Collections documentation - * https://www.content-collections.dev/docs/quickstart/next - * https://www.content-collections.dev/docs/configuration - * https://www.content-collections.dev/docs/transform#join-collections - * - * 2. Use Content Collections for Fumadocs - * https://fumadocs.dev/docs/headless/content-collections - */ -// const docs = defineCollection({ -// name: 'docs', -// directory: 'content/docs', -// include: '**/*.mdx', -// schema: (z) => ({ -// ...createDocSchema(z), -// preview: z.string().optional(), -// index: z.boolean().default(false), -// }), -// transform: transformMDX, -// }); - -// const metas = defineCollection({ -// name: 'meta', -// directory: 'content/docs', -// include: '**/meta**.json', -// parser: 'json', -// schema: createMetaSchema, -// }); - -/** - * Blog Author collection - * - * Authors are identified by their slug across all languages - * New format: content/author/authorname.{locale}.mdx - * Example: content/author/mksaas.mdx (default locale) and content/author/mksaas.zh.mdx (Chinese) - * - * For author, slug is slugAsParams - */ -export const authors = defineCollection({ - name: 'author', - directory: 'content/author', - include: '**/*.mdx', - schema: (z) => ({ - slug: z.string(), - name: z.string(), - avatar: z.string(), - locale: z.string().optional().default(DEFAULT_LOCALE), - }), - transform: async (data, context) => { - // Get the filename from the path - const filePath = data._meta.path; - const fileName = filePath.split(path.sep).pop() || ''; - - // Extract locale and base from filename - const { locale, base } = extractLocaleAndBase(fileName); - // console.log(`author processed: ${fileName}, locale=${locale}`); - - return { - ...data, - locale, - }; - }, -}); - -/** - * Blog Category collection - * - * Categories are identified by their slug across all languages - * New format: content/category/categoryname.{locale}.mdx - * Example: content/category/tutorial.mdx (default locale) and content/category/tutorial.zh.mdx (Chinese) - * - * For category, slug is slugAsParams - */ -export const categories = defineCollection({ - name: 'category', - directory: 'content/category', - include: '**/*.mdx', - schema: (z) => ({ - slug: z.string(), - name: z.string(), - description: z.string(), - locale: z.string().optional().default(DEFAULT_LOCALE), - }), - transform: async (data, context) => { - // Get the filename from the path - const filePath = data._meta.path; - const fileName = filePath.split(path.sep).pop() || ''; - - // Extract locale and base from filename - const { locale, base } = extractLocaleAndBase(fileName); - // console.log(`category processed: ${fileName}, locale=${locale}`); - - return { - ...data, - locale, - }; - }, -}); - -/** - * Blog Post collection - * - * New format: content/blog/post-slug.{locale}.mdx - * - * slug: /blog/first-post, used in URL or sitemap - * slugAsParams: first-post, used in route params - * - * 1. For a blog post at content/blog/first-post.mdx (default locale): - * locale: en - * slug: /blog/first-post - * slugAsParams: first-post - * - * 2. For a blog post at content/blog/first-post.zh.mdx (Chinese locale): - * locale: zh - * slug: /blog/first-post - * slugAsParams: first-post - */ -export const posts = defineCollection({ - name: 'post', - directory: 'content/blog', - include: '**/*.mdx', - schema: (z) => ({ - title: z.string(), - description: z.string(), - image: z.string(), - date: z.string().datetime(), - published: z.boolean().default(true), - categories: z.array(z.string()), - author: z.string(), - estimatedTime: z.number().optional(), // Reading time in minutes - }), - transform: async (data, context) => { - // Use Fumadocs transformMDX for consistent MDX processing - const transformedData = await transformMDX(data, context); - - // Get the filename from the path - const filePath = data._meta.path; - const fileName = filePath.split(path.sep).pop() || ''; - - // Extract locale and base from filename - const { locale, base } = extractLocaleAndBase(fileName); - // console.log(`post processed: ${fileName}, base=${base}, locale=${locale}`); - - // Find the author by matching slug and locale - const blogAuthor = context - .documents(authors) - .find((a) => a.slug === data.author && a.locale === locale); - - // Find categories by matching slug and locale - const blogCategories = data.categories - .map((categorySlug) => { - const category = context - .documents(categories) - .find((c) => c.slug === categorySlug && c.locale === locale); - - return category; - }) - .filter(Boolean); // Remove null values - - // Create the slug and slugAsParams - const slug = `/blog/${base}`; - const slugAsParams = base; - - // Calculate estimated reading time - const wordCount = data.content.split(/\s+/).length; - const wordsPerMinute = 200; // average reading speed: 200 words per minute - const estimatedTime = Math.max(Math.ceil(wordCount / wordsPerMinute), 1); - - return { - ...data, - locale, - author: blogAuthor, - categories: blogCategories, - slug, - slugAsParams, - estimatedTime, - body: transformedData.body, - toc: transformedData.toc, - }; - }, -}); - -/** - * Pages collection for policy pages like privacy-policy, terms-of-service, etc. - * - * New format: content/pages/page-slug.{locale}.mdx - * - * 1. For a page at content/pages/privacy-policy.mdx (default locale): - * locale: en - * slug: /pages/privacy-policy - * slugAsParams: privacy-policy - * - * 2. For a page at content/pages/privacy-policy.zh.mdx (Chinese locale): - * locale: zh - * slug: /pages/privacy-policy - * slugAsParams: privacy-policy - */ -// export const pages = defineCollection({ -// name: 'page', -// directory: 'content/pages', -// include: '**/*.mdx', -// schema: (z) => ({ -// title: z.string(), -// description: z.string(), -// date: z.string().datetime(), -// published: z.boolean().default(true), -// }), -// transform: async (data, context) => { -// // Use Fumadocs transformMDX for consistent MDX processing -// const transformedData = await transformMDX(data, context); - -// // Get the filename from the path -// const filePath = data._meta.path; -// const fileName = filePath.split(path.sep).pop() || ''; - -// // Extract locale and base from filename -// const { locale, base } = extractLocaleAndBase(fileName); -// // console.log(`page processed: ${fileName}, base=${base}, locale=${locale}`); - -// // Create the slug and slugAsParams -// const slug = `/pages/${base}`; -// const slugAsParams = base; - -// return { -// ...data, -// locale, -// slug, -// slugAsParams, -// body: transformedData.body, -// toc: transformedData.toc, -// }; -// }, -// }); - -/** - * Releases collection for changelog - * - * New format: content/release/version-slug.{locale}.mdx - * - * 1. For a release at content/release/v1-0-0.mdx (default locale): - * locale: en - * slug: /release/v1-0-0 - * slugAsParams: v1-0-0 - * - * 2. For a release at content/release/v1-0-0.zh.mdx (Chinese locale): - * locale: zh - * slug: /release/v1-0-0 - * slugAsParams: v1-0-0 - */ -// export const releases = defineCollection({ -// name: 'release', -// directory: 'content/release', -// include: '**/*.mdx', -// schema: (z) => ({ -// title: z.string(), -// description: z.string(), -// date: z.string().datetime(), -// version: z.string(), -// published: z.boolean().default(true), -// }), -// transform: async (data, context) => { -// // Use Fumadocs transformMDX for consistent MDX processing -// const transformedData = await transformMDX(data, context); - -// // Get the filename from the path -// const filePath = data._meta.path; -// const fileName = filePath.split(path.sep).pop() || ''; - -// // Extract locale and base from filename -// const { locale, base } = extractLocaleAndBase(fileName); -// // console.log(`release processed: ${fileName}, base=${base}, locale=${locale}`); - -// // Create the slug and slugAsParams -// const slug = `/release/${base}`; -// const slugAsParams = base; - -// return { -// ...data, -// locale, -// slug, -// slugAsParams, -// body: transformedData.body, -// toc: transformedData.toc, -// }; -// }, -// }); - -/** - * Helper function to extract locale and base name from filename - * Handles filename formats: - * - name -> locale: DEFAULT_LOCALE, base: name - * - name.zh -> locale: zh, base: name - * - * @param fileName Filename without extension (already has .mdx removed) - * @returns Object with locale and base name - */ -function extractLocaleAndBase(fileName: string): { - locale: string; - base: string; -} { - // Split filename into parts - const parts = fileName.split('.'); - - if (parts.length === 1) { - // Simple filename without locale: xxx - return { locale: DEFAULT_LOCALE, base: parts[0] }; - } - if (parts.length === 2 && LOCALES.includes(parts[1])) { - // Filename with locale: xxx.zh - return { locale: parts[1], base: parts[0] }; - } - // Unexpected format, use first part as base and default locale - console.warn(`Unexpected filename format: ${fileName}`); - return { locale: DEFAULT_LOCALE, base: parts[0] }; -} - -export default defineConfig({ - collections: [authors, categories, posts, pages], -}); diff --git a/package.json b/package.json index cd03ee8..08182f5 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "concurrently \"content-collections watch\" \"next dev\"", - "build": "content-collections build && next build", + "dev": "next dev", + "build": "next build", "start": "next start", "postinstall": "fumadocs-mdx", "lint": "biome check --write .", @@ -15,8 +15,12 @@ "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "list-contacts": "tsx scripts/list-contacts.ts", - "docs": "content-collections build", - "email": "email dev --dir src/mail/templates --port 3333" + "content": "fumadocs-mdx", + "email": "email dev --dir src/mail/templates --port 3333", + "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", + "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy", + "upload": "opennextjs-cloudflare build && opennextjs-cloudflare upload", + "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts" }, "dependencies": { "@ai-sdk/openai": "^1.1.13", diff --git a/source.config.ts b/source.config.ts index f39aa82..bb5677b 100644 --- a/source.config.ts +++ b/source.config.ts @@ -6,25 +6,21 @@ import { } from 'fumadocs-mdx/config'; import { z } from 'zod'; -const customDocsSchema = frontmatterSchema.extend({ - preview: z.string().optional(), - index: z.boolean().default(false), -}); - -const customMetaSchema = metaSchema.extend({ - description: z.string().optional(), -}); - /** * https://fumadocs.dev/docs/mdx/collections#schema-1 */ export const docs = defineDocs({ dir: 'content/docs', docs: { - schema: customDocsSchema, + schema: frontmatterSchema.extend({ + preview: z.string().optional(), + index: z.boolean().default(false), + }), }, meta: { - schema: customMetaSchema, + schema: metaSchema.extend({ + description: z.string().optional(), + }), }, }); @@ -34,25 +30,61 @@ export const docs = defineDocs({ export const changelog = defineCollections({ type: 'doc', dir: 'content/changelog', - schema: z.object({ - title: z.string(), - description: z.string(), - date: z.string().datetime(), + schema: frontmatterSchema.extend({ version: z.string(), + date: z.string().date(), published: z.boolean().default(true), }), }); /** - * Pages + * Pages, like privacy policy, terms of service, etc. */ export const pages = defineCollections({ type: 'doc', dir: 'content/pages', - schema: z.object({ - title: z.string(), - description: z.string(), - date: z.string().datetime(), + schema: frontmatterSchema.extend({ + date: z.string().date(), published: z.boolean().default(true), }), }); + +/** + * Blog authors + * + * description is optional, but we must add it to the schema + */ +export const author = defineCollections({ + type: 'doc', + dir: 'content/author', + schema: frontmatterSchema.extend({ + name: z.string(), + avatar: z.string(), + }), +}); + +/** + * Blog categories + */ +export const category = defineCollections({ + type: 'doc', + dir: 'content/category', + schema: frontmatterSchema.extend({ + name: z.string(), + }), +}); + +/** + * Blog posts + */ +export const blog = defineCollections({ + type: 'doc', + dir: 'content/blog', + schema: frontmatterSchema.extend({ + image: z.string(), + date: z.string().date(), + published: z.boolean().default(true), + categories: z.array(z.string()), + author: z.string(), + }), +}); diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index bf761cb..d6e680d 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,8 +1,6 @@ -import { websiteConfig } from '@/config/website'; import { getLocalePathname } from '@/i18n/navigation'; import { routing } from '@/i18n/routing'; import { source } from '@/lib/docs/source'; -import { allCategories, allPosts } from 'content-collections'; import type { MetadataRoute } from 'next'; import type { Locale } from 'next-intl'; import { getBaseUrl } from '../lib/urls/urls'; @@ -50,86 +48,86 @@ export default async function sitemap(): Promise { ); // add categories - sitemapList.push( - ...allCategories.flatMap((category: { slug: string }) => - routing.locales.map((locale) => ({ - url: getUrl(`/blog/category/${category.slug}`, locale), - lastModified: new Date(), - priority: 0.8, - changeFrequency: 'weekly' as const, - })) - ) - ); + // sitemapList.push( + // ...allCategories.flatMap((category: { slug: string }) => + // routing.locales.map((locale) => ({ + // url: getUrl(`/blog/category/${category.slug}`, locale), + // lastModified: new Date(), + // priority: 0.8, + // changeFrequency: 'weekly' as const, + // })) + // ) + // ); // add paginated blog list pages - routing.locales.forEach((locale) => { - const posts = allPosts.filter( - (post) => post.locale === locale && post.published - ); - const totalPages = Math.max( - 1, - Math.ceil(posts.length / websiteConfig.blog.paginationSize) - ); - // /blog/page/[page] (from 2) - for (let page = 2; page <= totalPages; page++) { - sitemapList.push({ - url: getUrl(`/blog/page/${page}`, locale), - lastModified: new Date(), - priority: 0.8, - changeFrequency: 'weekly' as const, - }); - } - }); + // routing.locales.forEach((locale) => { + // const posts = allPosts.filter( + // (post) => post.locale === locale && post.published + // ); + // const totalPages = Math.max( + // 1, + // Math.ceil(posts.length / websiteConfig.blog.paginationSize) + // ); + // // /blog/page/[page] (from 2) + // for (let page = 2; page <= totalPages; page++) { + // sitemapList.push({ + // url: getUrl(`/blog/page/${page}`, locale), + // lastModified: new Date(), + // priority: 0.8, + // changeFrequency: 'weekly' as const, + // }); + // } + // }); // add paginated category pages - routing.locales.forEach((locale) => { - const localeCategories = allCategories.filter( - (category) => category.locale === locale - ); - localeCategories.forEach((category) => { - // posts in this category and locale - const postsInCategory = allPosts.filter( - (post) => - post.locale === locale && - post.published && - post.categories.some((cat) => cat && cat.slug === category.slug) - ); - const totalPages = Math.max( - 1, - Math.ceil(postsInCategory.length / websiteConfig.blog.paginationSize) - ); - // /blog/category/[slug] (first page) - sitemapList.push({ - url: getUrl(`/blog/category/${category.slug}`, locale), - lastModified: new Date(), - priority: 0.8, - changeFrequency: 'weekly' as const, - }); - // /blog/category/[slug]/page/[page] (from 2) - for (let page = 2; page <= totalPages; page++) { - sitemapList.push({ - url: getUrl(`/blog/category/${category.slug}/page/${page}`, locale), - lastModified: new Date(), - priority: 0.8, - changeFrequency: 'weekly' as const, - }); - } - }); - }); + // routing.locales.forEach((locale) => { + // const localeCategories = allCategories.filter( + // (category) => category.locale === locale + // ); + // localeCategories.forEach((category) => { + // // posts in this category and locale + // const postsInCategory = allPosts.filter( + // (post) => + // post.locale === locale && + // post.published && + // post.categories.some((cat) => cat && cat.slug === category.slug) + // ); + // const totalPages = Math.max( + // 1, + // Math.ceil(postsInCategory.length / websiteConfig.blog.paginationSize) + // ); + // // /blog/category/[slug] (first page) + // sitemapList.push({ + // url: getUrl(`/blog/category/${category.slug}`, locale), + // lastModified: new Date(), + // priority: 0.8, + // changeFrequency: 'weekly' as const, + // }); + // // /blog/category/[slug]/page/[page] (from 2) + // for (let page = 2; page <= totalPages; page++) { + // sitemapList.push({ + // url: getUrl(`/blog/category/${category.slug}/page/${page}`, locale), + // lastModified: new Date(), + // priority: 0.8, + // changeFrequency: 'weekly' as const, + // }); + // } + // }); + // }); - // add posts (single post pages) - sitemapList.push( - ...allPosts.flatMap((post: { slugAsParams: string; locale: string }) => - routing.locales - .filter((locale) => post.locale === locale) - .map((locale) => ({ - url: getUrl(`/blog/${post.slugAsParams}`, locale), - lastModified: new Date(), - priority: 0.8, - changeFrequency: 'weekly' as const, - })) - ) - ); + // // add posts (single post pages) + // sitemapList.push( + // ...allPosts.flatMap((post: { slugAsParams: string; locale: string }) => + // routing.locales + // .filter((locale) => post.locale === locale) + // .map((locale) => ({ + // url: getUrl(`/blog/${post.slugAsParams}`, locale), + // lastModified: new Date(), + // priority: 0.8, + // changeFrequency: 'weekly' as const, + // })) + // ) + // ); // add docs const docsParams = source.generateParams(); diff --git a/src/components/changelog/release-card.tsx b/src/components/changelog/release-card.tsx index c060983..afe686d 100644 --- a/src/components/changelog/release-card.tsx +++ b/src/components/changelog/release-card.tsx @@ -1,22 +1,19 @@ import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; -import type { changelogSource } from '@/lib/docs/source'; +import type { ChangelogType } from '@/lib/docs/source'; import { formatDate } from '@/lib/formatter'; -import type { InferPageType } from 'fumadocs-core/source'; import { CalendarIcon, TagIcon } from 'lucide-react'; import { getMDXComponents } from '../custom/mdx-components'; -type ChangelogRelease = InferPageType; - interface ReleaseCardProps { - releaseItem: ChangelogRelease; + releaseItem: ChangelogType; } export function ReleaseCard({ releaseItem }: ReleaseCardProps) { const { title, description, date, version } = releaseItem.data; - const MDX = releaseItem.data.body; const formattedDate = formatDate(new Date(date)); + const MDX = releaseItem.data.body; return ( diff --git a/src/components/page/custom-page.tsx b/src/components/page/custom-page.tsx index c8ff7ac..fe93956 100644 --- a/src/components/page/custom-page.tsx +++ b/src/components/page/custom-page.tsx @@ -1,14 +1,11 @@ -import type { pagesSource } from '@/lib/docs/source'; +import type { PagesType } from '@/lib/docs/source'; import { formatDate } from '@/lib/formatter'; -import type { InferPageType } from 'fumadocs-core/source'; import { CalendarIcon } from 'lucide-react'; import { getMDXComponents } from '../custom/mdx-components'; import { Card, CardContent } from '../ui/card'; -type Page = InferPageType; - interface CustomPageProps { - page: Page; + page: PagesType; } export function CustomPage({ page }: CustomPageProps) { diff --git a/src/components/shared/custom-mdx-content.tsx b/src/components/shared/custom-mdx-content.tsx deleted file mode 100644 index 7f07e32..0000000 --- a/src/components/shared/custom-mdx-content.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ImageWrapper } from '@/components/docs/image-wrapper'; -import { Wrapper } from '@/components/docs/wrapper'; -import { YoutubeVideo } from '@/components/docs/youtube-video'; -import { MDXContent } from '@content-collections/mdx/react'; -import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; -import { Callout } from 'fumadocs-ui/components/callout'; -import { File, Files, Folder } from 'fumadocs-ui/components/files'; -import { Step, Steps } from 'fumadocs-ui/components/steps'; -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -import { TypeTable } from 'fumadocs-ui/components/type-table'; -import defaultMdxComponents from 'fumadocs-ui/mdx'; -import * as LucideIcons from 'lucide-react'; -import type { MDXComponents } from 'mdx/types'; -import type { ComponentProps, FC } from 'react'; - -interface CustomMDXContentProps { - code: string; - customComponents?: Record; -} - -/** - * Enhanced MDX Content component that includes commonly used MDX components - * It can be used for blog posts, documentation, and custom pages - */ -export async function CustomMDXContent({ - code, - customComponents = {}, -}: CustomMDXContentProps) { - // Start with default components - const baseComponents: Record = { - ...defaultMdxComponents, - ...LucideIcons, - ...((await import('lucide-react')) as unknown as MDXComponents), - YoutubeVideo, - Tabs, - Tab, - TypeTable, - Accordion, - Accordions, - Steps, - Step, - Wrapper, - File, - Folder, - Files, - blockquote: Callout as unknown as FC>, - img: ImageWrapper, - ...customComponents, - }; - - return ( - - ); -} diff --git a/src/lib/blog/data.ts b/src/lib/blog/data.ts deleted file mode 100644 index d544d2d..0000000 --- a/src/lib/blog/data.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { websiteConfig } from '@/config/website'; -import { allPosts } from 'content-collections'; - -export function getPaginatedBlogPosts({ - locale, - page = 1, - category, -}: { - locale: string; - page?: number; - category?: string; -}) { - // Filter posts by locale - let filteredPosts = allPosts.filter( - (post) => post.locale === locale && post.published - ); - // If no posts found for the current locale, show all published posts - if (filteredPosts.length === 0) { - filteredPosts = allPosts.filter((post) => post.published); - } - // Filter by category if category is provided - if (category) { - filteredPosts = filteredPosts.filter((post) => - post.categories.some((cat) => cat && cat.slug === category) - ); - } - // Sort posts by date (newest first) - const sortedPosts = [...filteredPosts].sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() - ); - // Paginate posts - const paginationSize = websiteConfig.blog.paginationSize; - const startIndex = (page - 1) * paginationSize; - const endIndex = startIndex + paginationSize; - const paginatedPosts = sortedPosts.slice(startIndex, endIndex); - const totalPages = Math.ceil(filteredPosts.length / paginationSize); - return { - paginatedPosts, - totalPages, - filteredPostsCount: filteredPosts.length, - }; -} diff --git a/src/lib/docs/source.ts b/src/lib/docs/source.ts index ad90bc0..f265948 100644 --- a/src/lib/docs/source.ts +++ b/src/lib/docs/source.ts @@ -1,8 +1,15 @@ -import { loader } from 'fumadocs-core/source'; +import { type InferPageType, loader } from 'fumadocs-core/source'; import { createMDXSource } from 'fumadocs-mdx'; import * as LucideIcons from 'lucide-react'; import { createElement } from 'react'; -import { changelog, docs, pages } from '../../../.source'; +import { + author, + blog, + category, + changelog, + docs, + pages, +} from '../../../.source'; import { docsI18nConfig } from './i18n'; /** @@ -10,7 +17,6 @@ import { docsI18nConfig } from './i18n'; * .source folder is generated by `fumadocs-mdx` * * https://fumadocs.dev/docs/headless/source-api - * https://fumadocs.dev/docs/headless/content-collections */ export const source = loader({ baseUrl: '/docs', @@ -42,9 +48,44 @@ export const changelogSource = loader({ /** * Pages source + * + * TODO: how to set the baseUrl for pages? */ export const pagesSource = loader({ baseUrl: '/pages', i18n: docsI18nConfig, source: createMDXSource(pages), }); + +/** + * Blog authors source + */ +export const authorSource = loader({ + baseUrl: '/author', + i18n: docsI18nConfig, + source: createMDXSource(author), +}); + +/** + * Blog categories source + */ +export const categorySource = loader({ + baseUrl: '/category', + i18n: docsI18nConfig, + source: createMDXSource(category), +}); + +/** + * Blog posts source + */ +export const blogSource = loader({ + baseUrl: '/blog', + i18n: docsI18nConfig, + source: createMDXSource(blog), +}); + +export type ChangelogType = InferPageType; +export type PagesType = InferPageType; +export type AuthorType = InferPageType; +export type CategoryType = InferPageType; +export type BlogType = InferPageType; diff --git a/tsconfig.json b/tsconfig.json index 2330aa4..0b9b772 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,8 +21,7 @@ "paths": { "@/*": ["./src/*"], "@/content/*": ["./content/*"], - "@/public/*": ["./public/*"], - "content-collections": ["./.content-collections/generated"] + "@/public/*": ["./public/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],