import { ReactElement } from 'react'; import rehypeToc from '@jsdevtools/rehype-toc'; import fs from 'fs'; import { compileMDX } from 'next-mdx-remote/rsc'; import path from 'path'; import rehypeSlug from 'rehype-slug'; import gfm from 'remark-gfm'; interface ItemInfo { title: string; position?: number; path: string; type: 'file' | 'directory'; icon?: string; } export interface FileContent { content: ReactElement; itemInfo: ItemInfo; } export interface Directory { [key: string]: FileContent | Directory | ItemInfo; itemInfo: ItemInfo; } async function getFiles( filePath: string, basePath: string, position: number = 0, ): Promise { const entries = fs.readdirSync(filePath, { withFileTypes: true }); const urlpath = path.toString().split(basePath); const pathName = urlpath.length > 1 ? urlpath[1] : path.basename(filePath); const directory: Directory = { itemInfo: { title: path.basename(filePath), position, type: 'directory', path: pathName, }, }; for (const entry of entries) { if (entry.isDirectory()) { directory[entry.name] = await getFiles( path.join(filePath, entry.name), basePath, position++, ); } else if (entry.isFile() && path.extname(entry.name) === '.mdx') { const { content, frontmatter } = await compileMDXFile( path.join(filePath, entry.name), ); directory[entry.name] = { content, itemInfo: { ...frontmatter, type: 'file', path: pathName + '/' + entry.name.replace(/\.mdx$/, ''), }, }; } } return directory; } async function parseFrontMatterAndCategory( directory: Directory, dirPath: string, ): Promise { const parsedDirectory: Directory = { itemInfo: directory.itemInfo, }; for (const entry in directory) { if (entry !== 'itemInfo' && directory[entry] instanceof Object) { parsedDirectory[entry] = await parseFrontMatterAndCategory( directory[entry] as Directory, path.join(dirPath, entry), ); } } const categoryPath = path.join(dirPath, '_category_.json'); if (fs.existsSync(categoryPath)) { const categoryJson: ItemInfo = JSON.parse( fs.readFileSync(categoryPath, 'utf8'), ); parsedDirectory.itemInfo = categoryJson; } return parsedDirectory; } export async function compileMDXFile(filePath: string, addToc: boolean = true) { const fileContent = fs.readFileSync(filePath, 'utf8'); const compiled = await compileMDX<{ title: string; position?: number }>({ source: fileContent, options: { parseFrontmatter: true, mdxOptions: { remarkPlugins: [gfm], rehypePlugins: [rehypeSlug, ...(addToc ? [rehypeToc] : [])], }, }, }); return compiled; } export async function getPosts(basePath: string): Promise { const postsDirectory = path.join(process.cwd(), basePath); const directory = await getFiles(postsDirectory, basePath); return parseFrontMatterAndCategory(directory, postsDirectory); } export async function getPost( slug: string[], basePath: string, ): Promise { const postsDirectory = path.join(process.cwd(), basePath); const modifiedSlug = slug.join('/'); const filePath = path.join(postsDirectory, `${modifiedSlug}.mdx`); if (!fs.existsSync(filePath)) { return null; } const { content, frontmatter } = await compileMDXFile(filePath); return { content, itemInfo: { ...frontmatter, type: 'file', path: modifiedSlug }, }; }