| import { defineDocumentType, ComputedFields, makeSource } from 'contentlayer/source-files'; |
| import { writeFileSync } from 'fs'; |
| import readingTime from 'reading-time'; |
| import GithubSlugger from 'github-slugger'; |
| import path from 'path'; |
| // Remark packages |
| import remarkGfm from 'remark-gfm'; |
| import remarkMath from 'remark-math'; |
| import { |
| remarkExtractFrontmatter, |
| remarkCodeTitles, |
| remarkImgToJsx, |
| extractTocHeadings |
| } from 'pliny/mdx-plugins/index.js'; |
| // Rehype packages |
| import rehypeSlug from 'rehype-slug'; |
| import rehypeAutolinkHeadings from 'rehype-autolink-headings'; |
| import rehypeKatex from 'rehype-katex'; |
| import rehypeCitation from 'rehype-citation'; |
| import rehypePrismPlus from 'rehype-prism-plus'; |
| import rehypePresetMinify from 'rehype-preset-minify'; |
| import siteMetadata from './data/siteMetadata'; |
| import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer.js'; |
| import { type Blog as BlogType } from '.contentlayer/generated/types'; |
| import { format } from 'date-fns'; |
| import { generateCustomPath, generateCustomSlug } from './app/lib/contentlayer.utils'; |
| |
| const root = process.cwd(); |
| const isProduction = process.env.NODE_ENV === 'production'; |
| |
| const hyerarchycalComputedFields: ComputedFields = { |
| readingTime: { type: 'json', resolve: (doc) => readingTime(doc.body.raw) }, |
| slug: { |
| type: 'string', |
| resolve: generateCustomSlug |
| }, |
| customSlug: { |
| type: 'string', |
| resolve: generateCustomSlug |
| }, |
| path: { |
| type: 'string', |
| resolve: generateCustomPath |
| }, |
| customPath: { |
| type: 'string', |
| resolve: generateCustomPath |
| }, |
| filePath: { |
| type: 'string', |
| resolve: (doc) => doc._raw.sourceFilePath |
| }, |
| toc: { |
| type: 'string', |
| resolve: (doc) => extractTocHeadings(doc.body.raw) |
| } |
| }; |
| |
| const authorComputedFields: ComputedFields = { |
| readingTime: { type: 'json', resolve: (doc) => readingTime(doc.body.raw) }, |
| slug: { |
| type: 'string', |
| resolve: (doc) => doc._raw.flattenedPath.replace(/^.+?(\/)/, '') |
| }, |
| path: { |
| type: 'string', |
| resolve: (doc) => doc._raw.flattenedPath |
| }, |
| filePath: { |
| type: 'string', |
| resolve: (doc) => doc._raw.sourceFilePath |
| }, |
| toc: { type: 'string', resolve: (doc) => extractTocHeadings(doc.body.raw) } |
| }; |
| |
| /** |
| * Count the occurrences of all tags across blog posts and write to json file |
| */ |
| function createTagCount(allBlogs: BlogType[]) { |
| const tagCount: Record<string, number> = {}; |
| allBlogs.forEach((file) => { |
| if (file.tags && (!isProduction || file.draft !== true)) { |
| file.tags.forEach((tag) => { |
| const formattedTag = GithubSlugger.slug(tag); |
| if (formattedTag in tagCount) { |
| tagCount[formattedTag] += 1; |
| } else { |
| tagCount[formattedTag] = 1; |
| } |
| }); |
| } |
| }); |
| writeFileSync('./app/tag-data.json', JSON.stringify(tagCount)); |
| } |
| |
| function createSearchIndex(allBlogs: BlogType[]) { |
| if ( |
| siteMetadata?.search?.provider === 'kbar' && |
| siteMetadata.search.kbarConfig.searchDocumentsPath |
| ) { |
| writeFileSync( |
| `public/${siteMetadata.search.kbarConfig.searchDocumentsPath}`, |
| JSON.stringify(allCoreContent(sortPosts(allBlogs))) |
| ); |
| console.log('Local search index generated...'); |
| } |
| } |
| |
| export const Blog = defineDocumentType(() => ({ |
| name: 'Blog', |
| filePathPattern: 'blog/**/*.mdx', |
| contentType: 'mdx', |
| fields: { |
| title: { type: 'string', required: true }, |
| date: { type: 'date', required: true }, |
| tags: { type: 'list', of: { type: 'string' }, default: [] }, |
| lastmod: { type: 'date' }, |
| draft: { type: 'boolean' }, |
| summary: { type: 'string' }, |
| images: { type: 'json' }, |
| authors: { type: 'list', of: { type: 'string' } }, |
| layout: { type: 'string' }, |
| bibliography: { type: 'string' }, |
| canonicalUrl: { type: 'string' } |
| }, |
| computedFields: { |
| ...hyerarchycalComputedFields, |
| structuredData: { |
| type: 'json', |
| resolve: (doc) => ({ |
| '@context': 'https://schema.org', |
| '@type': 'BlogPosting', |
| headline: doc.title, |
| datePublished: doc.date, |
| dateModified: doc.lastmod || doc.date, |
| description: doc.summary, |
| image: doc.images ? doc.images[0] : siteMetadata.socialBanner, |
| url: `${siteMetadata.siteUrl}/${doc._raw.flattenedPath}` |
| }) |
| } |
| } |
| })); |
| |
| export const Authors = defineDocumentType(() => ({ |
| name: 'Authors', |
| filePathPattern: 'authors/**/*.mdx', |
| contentType: 'mdx', |
| fields: { |
| name: { type: 'string', required: true }, |
| avatar: { type: 'string' }, |
| occupation: { type: 'string' }, |
| company: { type: 'string' }, |
| email: { type: 'string' }, |
| twitter: { type: 'string' }, |
| linkedin: { type: 'string' }, |
| github: { type: 'string' }, |
| layout: { type: 'string' } |
| }, |
| computedFields: { |
| ...authorComputedFields |
| } |
| })); |
| |
| export const Downloads = defineDocumentType(() => ({ |
| name: 'Downloads', |
| filePathPattern: 'downloads/**/*.mdx', |
| contentType: 'mdx', |
| fields: { |
| version: { type: 'string', required: true }, |
| date: { type: 'date', required: true }, |
| href: { type: 'string', required: true }, |
| officialSource: { type: 'json', required: true }, |
| binary: { type: 'json', required: true }, |
| releaseNotes: { type: 'markdown' } |
| // releaseNotes: { type: 'string', required: true } |
| }, |
| computedFields: { |
| ...hyerarchycalComputedFields |
| } |
| })); |
| |
| export default makeSource({ |
| contentDirPath: 'data', |
| documentTypes: [Blog, Authors, Downloads], |
| mdx: { |
| cwd: process.cwd(), |
| remarkPlugins: [ |
| remarkExtractFrontmatter, |
| remarkGfm, |
| remarkCodeTitles, |
| remarkMath, |
| remarkImgToJsx |
| ], |
| rehypePlugins: [ |
| rehypeSlug, |
| rehypeAutolinkHeadings, |
| rehypeKatex, |
| [rehypeCitation, { path: path.join(root, 'data') }], |
| [rehypePrismPlus, { defaultLanguage: 'js', ignoreMissing: true }], |
| rehypePresetMinify |
| ] |
| }, |
| onSuccess: async (importData) => { |
| const { allBlogs } = await importData(); |
| // createTagCount(allBlogs); |
| createSearchIndex(allBlogs); |
| } |
| }); |