heavily optimize tree related code

i18n
mrshmllow 2 years ago committed by Michal
parent e779ccca77
commit 3ad2b2286d
No known key found for this signature in database
GPG Key ID: 52F8801FC912EB4F

@ -1,8 +1,8 @@
import Link from "next/link";
import { FC } from "react";
import { TreeItem } from "../lib/tree";
import { ITreeItem } from "../lib/tree";
const TreeNode: FC<{ node: TreeItem; path: string }> = ({
const TreeNode: FC<{ node: ITreeItem; path: string }> = ({
node: { children, value, current, pretty },
path,
}) => {

@ -1,35 +1,147 @@
export interface TreeItem {
import fm from "front-matter";
import { readdir, readFile, stat } from "fs/promises";
import { load } from "js-yaml";
import { join, resolve } from "path";
import { removeExt } from "./files";
export interface ITreeItem {
value: string;
current: boolean;
children: TreeItem[];
children: ITreeItem[];
weight: number;
pretty: string | null;
}
export class TreeItemConstructor {
export class TreeItem {
value: string;
current: boolean;
children: TreeItemConstructor[] = [];
children: TreeItem[] = [];
weight: number;
pretty: string | null;
contents: string | null = null;
constructor(
value = "root",
current = false,
pretty: string | null = null,
weight = 0
weight = 0,
contents: string | null = null,
children: TreeItem[] = []
) {
this.value = value;
this.current = current;
this.current = false;
this.pretty = pretty;
this.weight = weight;
this.contents = contents;
this.children = children;
}
async addEntry(dir: string, name: string) {
const contents = (await readFile(resolve(dir, name))).toString();
const {
attributes: { title, weight },
} = fm<FrontMatter>(contents);
this.addChild(
new TreeItem(
removeExt(name),
title ? title : null,
weight ? weight : 0
).setContents(contents)
);
}
async walk(dir: string) {
const dirents = await readdir(resolve(process.cwd(), dir), {
withFileTypes: true,
});
for (const dirent of dirents) {
if (dirent.name.startsWith(".")) continue;
if (dirent.isDirectory()) {
const resolvedDir = resolve(dir, dirent.name);
this.addChild(
await (
await new TreeItem(removeExt(dirent.name), null, 0).walk(
resolvedDir
)
).readConfigYaml(resolvedDir)
);
} else {
await this.addEntry(dir, dirent.name);
}
}
return this;
}
async readConfigYaml(dir: string) {
try {
const configFile = join(dir, ".config.yaml");
const config = await stat(configFile);
if (config.isFile()) {
const { title, weight } = load(
(await readFile(configFile)).toString(),
{}
) as FrontMatter;
if (weight) this.weight = weight;
if (title) this.pretty = title;
}
} catch (e) {
console.info("Could not load .config.yaml for ", dir);
console.error(e);
}
return this;
}
setContents(contents: string) {
this.contents = contents;
return this;
}
find(slug: string[], current: boolean = false, i = 0): TreeItem | undefined {
if (slug[i] !== this.value) return;
for (const child of this.children) {
const match = child.find(slug, current, i + 1);
if (match !== undefined) return match;
}
if (this.children.length === 0) return this;
}
walkCurrents(aim: string[], i = 0) {
this.current = aim[i] === this.value;
if (this.current) {
for (const child of this.children) {
child.walkCurrents(aim, i + 1);
}
}
}
copy(): TreeItem {
return new TreeItem(
this.value,
this.pretty,
this.weight,
this.contents,
this.children.map((child) => child.copy())
);
}
addChild(child: TreeItemConstructor) {
addChild(child: TreeItem) {
this.children.push(child);
}
plain(): TreeItem {
plain(): ITreeItem {
return {
value: this.value,
current: this.current,
@ -47,7 +159,7 @@ export class TreeItemConstructor {
}
}
export const findCurrentDir = (node: TreeItem): TreeItem | null => {
export const findCurrentDir = (node: ITreeItem): ITreeItem | null => {
if (!node.current) {
return null;
}

@ -0,0 +1,14 @@
import { i18n } from "../next-i18next.config.js";
import { TreeItem } from "./tree";
const trees: {
[key: string]: TreeItem;
} = {};
for (const locale of i18n.locales) {
trees[locale] = await new TreeItem("root").walk(`_docs/${locale}/`);
trees[locale].sort();
}
export default trees;

@ -1,17 +1,13 @@
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
import { ReactElement, useEffect } from "react";
import { join, resolve } from "path";
import { resolve } from "path";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { removeExt, walkFiles } from "../../lib/files";
import { readFile, stat } from "fs/promises";
import { readdir } from "fs/promises";
import remarkGfm from "remark-gfm";
import TreeNode from "../../components/TreeItem";
import fm from "front-matter";
import DocWrapper from "../../components/DocWrapper";
import { findCurrentDir, TreeItem, TreeItemConstructor } from "../../lib/tree";
import { load } from "js-yaml";
import { findCurrentDir, ITreeItem } from "../../lib/tree";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug";
import rehypeHighlight from "rehype-highlight";
@ -20,6 +16,7 @@ import { useRouter } from "next/router";
import Link from "next/link";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import TranslationInfo from "../../components/TranslationInfo";
import trees from "../../lib/trees";
export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
const paths: GetStaticPathsResult["paths"] = [];
@ -54,67 +51,14 @@ export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
"meta",
]);
let path = ["_docs", locale, ...slug].join("/") + ".mdx";
let isDir: boolean = false;
const walk = async (node: TreeItemConstructor, dir: string, i = 0) => {
const dirents = await readdir(resolve(process.cwd(), dir), {
withFileTypes: true,
});
for (const dirent of dirents.filter(
(dirent) => !dirent.name.startsWith(".")
)) {
const current = slug[i] === removeExt(dirent.name) && node.current;
if (dirent.isDirectory()) {
node.addChild(
await walk(
new TreeItemConstructor(removeExt(dirent.name), current, null, 0),
resolve(dir, dirent.name),
i + 1
)
);
try {
const configFile = join(resolve(dir, dirent.name), ".config.yaml");
const config = await stat(configFile);
if (config.isFile()) {
const { title, weight } = load(
(await readFile(configFile)).toString(),
{}
) as FrontMatter;
if (title) node.children.at(-1)!.pretty = title;
if (weight) node.children.at(-1)!.weight = weight;
}
} catch (_) { }
if (current && i + 1 == slug.length && node.children.length > 0) {
isDir = true;
}
} else {
const contents = (await readFile(resolve(dir, dirent.name))).toString();
const {
attributes: { title, weight },
} = fm<FrontMatter>(contents);
node.addChild(
new TreeItemConstructor(
removeExt(dirent.name),
current,
title ? title : null,
weight ? weight : 0
)
);
}
}
return node;
};
const rootySlug = ["root", ...slug];
const tree = trees[locale!].copy();
tree.walkCurrents(rootySlug);
const tree = (
await walk(new TreeItemConstructor("root", true), `_docs/${locale}/`)
).sort();
const me = tree.find(rootySlug);
if (isDir || slug.length === 0) {
if (slug.length === 0 || me === undefined) {
const plain = tree.plain();
return {
props: {
@ -126,29 +70,24 @@ export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
};
}
const mdxSource = await serialize(
(await readFile(resolve(process.cwd(), path))).toString(),
{
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
behavior: "wrap",
},
],
rehypeHighlight,
],
},
}
);
return {
props: {
source: mdxSource,
source: await serialize(me.contents!, {
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
behavior: "wrap",
},
],
rehypeHighlight,
],
},
}),
tree: tree.plain(),
...translations,
},
@ -157,18 +96,19 @@ export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
const DocPage: NextPageWithLayout<{
source: MDXRemoteSerializeResult | null;
tree: TreeItem;
dir: TreeItem | null;
tree: ITreeItem;
dir: ITreeItem | null;
}> = ({ source, tree, dir }) => {
const {
query: { slug }, push
query: { slug },
push,
} = useRouter();
useEffect(() => {
if (slug === undefined) {
push("/docs/crystal-linux/getting-started")
push("/docs/crystal-linux/getting-started");
}
}, [push, slug])
}, [push, slug]);
return (
<div className="mx-auto min-h-screen max-w-8xl space-y-12 space-x-4 px-4 pb-16 pt-24 md:px-8 md:pb-28 lg:pt-28">
@ -182,13 +122,16 @@ const DocPage: NextPageWithLayout<{
{dir.pretty !== null && <h1>{dir.pretty}</h1>}
<TranslationInfo />
<ul>
{slug && dir.children.map((child) => (
<li key={child.value}>
<Link href={`${(slug as string[]).join("/")}/${child.value}`}>
<a>{child.pretty ? child.pretty : child.value}</a>
</Link>
</li>
))}
{slug &&
dir.children.map((child) => (
<li key={child.value}>
<Link
href={`${(slug as string[]).join("/")}/${child.value}`}
>
<a>{child.pretty ? child.pretty : child.value}</a>
</Link>
</li>
))}
</ul>
</>
) : (

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,

Loading…
Cancel
Save