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 Link from "next/link";
import { FC } from "react"; 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 }, node: { children, value, current, pretty },
path, 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; value: string;
current: boolean; current: boolean;
children: TreeItem[]; children: ITreeItem[];
weight: number; weight: number;
pretty: string | null; pretty: string | null;
} }
export class TreeItemConstructor { export class TreeItem {
value: string; value: string;
current: boolean; current: boolean;
children: TreeItemConstructor[] = []; children: TreeItem[] = [];
weight: number; weight: number;
pretty: string | null; pretty: string | null;
contents: string | null = null;
constructor( constructor(
value = "root", value = "root",
current = false,
pretty: string | null = null, pretty: string | null = null,
weight = 0 weight = 0,
contents: string | null = null,
children: TreeItem[] = []
) { ) {
this.value = value; this.value = value;
this.current = current; this.current = false;
this.pretty = pretty; this.pretty = pretty;
this.weight = weight; 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); this.children.push(child);
} }
plain(): TreeItem { plain(): ITreeItem {
return { return {
value: this.value, value: this.value,
current: this.current, 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) { if (!node.current) {
return null; 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 { serialize } from "next-mdx-remote/serialize";
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
import { ReactElement, useEffect } from "react"; import { ReactElement, useEffect } from "react";
import { join, resolve } from "path"; import { resolve } from "path";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { removeExt, walkFiles } from "../../lib/files"; import { removeExt, walkFiles } from "../../lib/files";
import { readFile, stat } from "fs/promises";
import { readdir } from "fs/promises";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import TreeNode from "../../components/TreeItem"; import TreeNode from "../../components/TreeItem";
import fm from "front-matter";
import DocWrapper from "../../components/DocWrapper"; import DocWrapper from "../../components/DocWrapper";
import { findCurrentDir, TreeItem, TreeItemConstructor } from "../../lib/tree"; import { findCurrentDir, ITreeItem } from "../../lib/tree";
import { load } from "js-yaml";
import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug"; import rehypeSlug from "rehype-slug";
import rehypeHighlight from "rehype-highlight"; import rehypeHighlight from "rehype-highlight";
@ -20,6 +16,7 @@ import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import TranslationInfo from "../../components/TranslationInfo"; import TranslationInfo from "../../components/TranslationInfo";
import trees from "../../lib/trees";
export const getStaticPaths: GetStaticPaths = async ({ locales }) => { export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
@ -54,67 +51,14 @@ export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
"meta", "meta",
]); ]);
let path = ["_docs", locale, ...slug].join("/") + ".mdx"; const rootySlug = ["root", ...slug];
let isDir: boolean = false;
const tree = trees[locale!].copy();
const walk = async (node: TreeItemConstructor, dir: string, i = 0) => { tree.walkCurrents(rootySlug);
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 tree = ( const me = tree.find(rootySlug);
await walk(new TreeItemConstructor("root", true), `_docs/${locale}/`)
).sort();
if (isDir || slug.length === 0) { if (slug.length === 0 || me === undefined) {
const plain = tree.plain(); const plain = tree.plain();
return { return {
props: { 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 { return {
props: { props: {
source: mdxSource, source: await serialize(me.contents!, {
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
behavior: "wrap",
},
],
rehypeHighlight,
],
},
}),
tree: tree.plain(), tree: tree.plain(),
...translations, ...translations,
}, },
@ -157,18 +96,19 @@ export const getStaticProps: GetStaticProps = async ({ params, locale }) => {
const DocPage: NextPageWithLayout<{ const DocPage: NextPageWithLayout<{
source: MDXRemoteSerializeResult | null; source: MDXRemoteSerializeResult | null;
tree: TreeItem; tree: ITreeItem;
dir: TreeItem | null; dir: ITreeItem | null;
}> = ({ source, tree, dir }) => { }> = ({ source, tree, dir }) => {
const { const {
query: { slug }, push query: { slug },
push,
} = useRouter(); } = useRouter();
useEffect(() => { useEffect(() => {
if (slug === undefined) { if (slug === undefined) {
push("/docs/crystal-linux/getting-started") push("/docs/crystal-linux/getting-started");
} }
}, [push, slug]) }, [push, slug]);
return ( 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"> <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>} {dir.pretty !== null && <h1>{dir.pretty}</h1>}
<TranslationInfo /> <TranslationInfo />
<ul> <ul>
{slug && dir.children.map((child) => ( {slug &&
<li key={child.value}> dir.children.map((child) => (
<Link href={`${(slug as string[]).join("/")}/${child.value}`}> <li key={child.value}>
<a>{child.pretty ? child.pretty : child.value}</a> <Link
</Link> href={`${(slug as string[]).join("/")}/${child.value}`}
</li> >
))} <a>{child.pretty ? child.pretty : child.value}</a>
</Link>
</li>
))}
</ul> </ul>
</> </>
) : ( ) : (

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

Loading…
Cancel
Save