blob: 0f6889acb7901a6bf29c0c9122f80bde1cb67773 [file] [log] [blame]
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);
}
});