feat(docs): add docs and restructure
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { mdxAnnotations } from "mdx-annotations";
|
||||
|
||||
export const recmaPlugins = [mdxAnnotations.recma];
|
||||
@@ -0,0 +1,124 @@
|
||||
import { slugifyWithCounter } from "@sindresorhus/slugify";
|
||||
import * as acorn from "acorn";
|
||||
import { toString } from "mdast-util-to-string";
|
||||
import { mdxAnnotations } from "mdx-annotations";
|
||||
import shiki from "shiki";
|
||||
import { visit } from "unist-util-visit";
|
||||
|
||||
function rehypeParseCodeBlocks() {
|
||||
return (tree) => {
|
||||
visit(tree, "element", (node, _nodeIndex, parentNode) => {
|
||||
if (node.tagName === "code" && node.properties.className) {
|
||||
parentNode.properties.language = node.properties.className[0]?.replace(
|
||||
/^language-/,
|
||||
""
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
let highlighter;
|
||||
|
||||
function rehypeShiki() {
|
||||
return async (tree) => {
|
||||
highlighter =
|
||||
highlighter ?? (await shiki.getHighlighter({ theme: "css-variables" }));
|
||||
|
||||
visit(tree, "element", (node) => {
|
||||
if (node.tagName === "pre" && node.children[0]?.tagName === "code") {
|
||||
let codeNode = node.children[0];
|
||||
let textNode = codeNode.children[0];
|
||||
|
||||
node.properties.code = textNode.value;
|
||||
|
||||
if (node.properties.language) {
|
||||
let tokens = highlighter.codeToThemedTokens(
|
||||
textNode.value,
|
||||
node.properties.language
|
||||
);
|
||||
|
||||
textNode.value = shiki.renderToHtml(tokens, {
|
||||
elements: {
|
||||
pre: ({ children }) => children,
|
||||
code: ({ children }) => children,
|
||||
line: ({ children }) => `<span>${children}</span>`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function rehypeSlugify() {
|
||||
return (tree) => {
|
||||
let slugify = slugifyWithCounter();
|
||||
visit(tree, "element", (node) => {
|
||||
if (node.tagName === "h2" && !node.properties.id) {
|
||||
node.properties.id = slugify(toString(node));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function rehypeAddMDXExports(getExports) {
|
||||
return (tree) => {
|
||||
let exports = Object.entries(getExports(tree));
|
||||
|
||||
for (let [name, value] of exports) {
|
||||
for (let node of tree.children) {
|
||||
if (
|
||||
node.type === "mdxjsEsm" &&
|
||||
new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let exportStr = `export const ${name} = ${value}`;
|
||||
|
||||
tree.children.push({
|
||||
type: "mdxjsEsm",
|
||||
value: exportStr,
|
||||
data: {
|
||||
estree: acorn.parse(exportStr, {
|
||||
sourceType: "module",
|
||||
ecmaVersion: "latest",
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getSections(node) {
|
||||
let sections = [];
|
||||
|
||||
for (let child of node.children ?? []) {
|
||||
if (child.type === "element" && child.tagName === "h2") {
|
||||
sections.push(`{
|
||||
title: ${JSON.stringify(toString(child))},
|
||||
id: ${JSON.stringify(child.properties.id)},
|
||||
...${child.properties.annotation}
|
||||
}`);
|
||||
} else if (child.children) {
|
||||
sections.push(...getSections(child));
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
export const rehypePlugins = [
|
||||
mdxAnnotations.rehype,
|
||||
rehypeParseCodeBlocks,
|
||||
rehypeShiki,
|
||||
rehypeSlugify,
|
||||
[
|
||||
rehypeAddMDXExports,
|
||||
(tree) => ({
|
||||
sections: `[${getSections(tree).join()}]`,
|
||||
}),
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
import { mdxAnnotations } from 'mdx-annotations'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
export const remarkPlugins = [mdxAnnotations.remark, remarkGfm]
|
||||
@@ -0,0 +1,137 @@
|
||||
import { slugifyWithCounter } from "@sindresorhus/slugify";
|
||||
import glob from "fast-glob";
|
||||
import * as fs from "fs";
|
||||
import { toString } from "mdast-util-to-string";
|
||||
import * as path from "path";
|
||||
import { remark } from "remark";
|
||||
import remarkMdx from "remark-mdx";
|
||||
import { createLoader } from "simple-functional-loader";
|
||||
import { filter } from "unist-util-filter";
|
||||
import { SKIP, visit } from "unist-util-visit";
|
||||
import * as url from "url";
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
const processor = remark().use(remarkMdx).use(extractSections);
|
||||
const slugify = slugifyWithCounter();
|
||||
|
||||
function isObjectExpression(node) {
|
||||
return (
|
||||
node.type === "mdxTextExpression" &&
|
||||
node.data?.estree?.body?.[0]?.expression?.type === "ObjectExpression"
|
||||
);
|
||||
}
|
||||
|
||||
function excludeObjectExpressions(tree) {
|
||||
return filter(tree, (node) => !isObjectExpression(node));
|
||||
}
|
||||
|
||||
function extractSections() {
|
||||
return (tree, { sections }) => {
|
||||
slugify.reset();
|
||||
|
||||
visit(tree, (node) => {
|
||||
if (node.type === "heading" || node.type === "paragraph") {
|
||||
let content = toString(excludeObjectExpressions(node));
|
||||
if (node.type === "heading" && node.depth <= 2) {
|
||||
let hash = node.depth === 1 ? null : slugify(content);
|
||||
sections.push([content, hash, []]);
|
||||
} else {
|
||||
sections.at(-1)?.[2].push(content);
|
||||
}
|
||||
return SKIP;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default function (nextConfig = {}) {
|
||||
let cache = new Map();
|
||||
|
||||
return Object.assign({}, nextConfig, {
|
||||
webpack(config, options) {
|
||||
config.module.rules.push({
|
||||
test: __filename,
|
||||
use: [
|
||||
createLoader(function () {
|
||||
let appDir = path.resolve("./src/app/(docs)/docs");
|
||||
this.addContextDependency(appDir);
|
||||
|
||||
let files = glob.sync("**/*.mdx", { cwd: appDir });
|
||||
let data = files.map((file) => {
|
||||
let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`;
|
||||
let mdx = fs.readFileSync(path.join(appDir, file), "utf8");
|
||||
|
||||
let sections = [];
|
||||
|
||||
if (cache.get(file)?.[0] === mdx) {
|
||||
sections = cache.get(file)[1];
|
||||
} else {
|
||||
let vfile = { value: mdx, sections };
|
||||
processor.runSync(processor.parse(vfile), vfile);
|
||||
cache.set(file, [mdx, sections]);
|
||||
}
|
||||
|
||||
url = `/docs/${url}`;
|
||||
|
||||
return { url, sections };
|
||||
});
|
||||
|
||||
// When this file is imported within the application
|
||||
// the following module is loaded:
|
||||
return `
|
||||
import FlexSearch from 'flexsearch'
|
||||
|
||||
let sectionIndex = new FlexSearch.Document({
|
||||
tokenize: 'full',
|
||||
document: {
|
||||
id: 'url',
|
||||
index: 'content',
|
||||
store: ['title', 'pageTitle'],
|
||||
},
|
||||
context: {
|
||||
resolution: 9,
|
||||
depth: 2,
|
||||
bidirectional: true
|
||||
}
|
||||
})
|
||||
|
||||
let data = ${JSON.stringify(data)}
|
||||
|
||||
for (let { url, sections } of data) {
|
||||
for (let [title, hash, content] of sections) {
|
||||
sectionIndex.add({
|
||||
url: url + (hash ? ('#' + hash) : ''),
|
||||
title,
|
||||
content: [title, ...content].join('\\n'),
|
||||
pageTitle: hash ? sections[0][0] : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function search(query, options = {}) {
|
||||
let result = sectionIndex.search(query, {
|
||||
...options,
|
||||
enrich: true,
|
||||
})
|
||||
if (result.length === 0) {
|
||||
return []
|
||||
}
|
||||
return result[0].result.map((item) => ({
|
||||
url: item.id,
|
||||
title: item.doc.title,
|
||||
pageTitle: item.doc.pageTitle,
|
||||
}))
|
||||
}
|
||||
`;
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof nextConfig.webpack === "function") {
|
||||
return nextConfig.webpack(config, options);
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user