Make markdown work recursively
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 2m53s
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Failing after 2m53s
This commit is contained in:
parent
a665d5d0ba
commit
5e67ce1f34
Binary file not shown.
@ -39,6 +39,7 @@
|
|||||||
"react-hook-form": "^7.51.3",
|
"react-hook-form": "^7.51.3",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"restfulmc-lib": "^1.1.3",
|
"restfulmc-lib": "^1.1.3",
|
||||||
|
"shadcn-ui": "0.8.0",
|
||||||
"sharp": "^0.33.3",
|
"sharp": "^0.33.3",
|
||||||
"sonner": "^1.4.41",
|
"sonner": "^1.4.41",
|
||||||
"tailwind-merge": "^2.2.2",
|
"tailwind-merge": "^2.2.2",
|
||||||
|
@ -3,6 +3,15 @@ import { getDocsContent } from "@/lib/mdxUtils";
|
|||||||
import { PageProps } from "@/types/page";
|
import { PageProps } from "@/types/page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from "@/components/ui/breadcrumb";
|
||||||
|
import { capitalize } from "@/lib/stringUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The page to display content
|
* The page to display content
|
||||||
* from an MDX file on the docs.
|
* from an MDX file on the docs.
|
||||||
@ -10,17 +19,47 @@ import { notFound } from "next/navigation";
|
|||||||
* @param slug the slug of the mdx file
|
* @param slug the slug of the mdx file
|
||||||
* @return the page jsx
|
* @return the page jsx
|
||||||
*/
|
*/
|
||||||
const ContentPage = ({ params: { slug } }: PageProps): ReactElement => {
|
const ContentPage = ({ params }: PageProps): ReactElement => {
|
||||||
|
const slug: string = ((params.slug as string[]) || undefined)?.join("/"); // The slug of the content
|
||||||
|
|
||||||
const content: DocsContentMetadata | undefined = getDocsContent().find(
|
const content: DocsContentMetadata | undefined = getDocsContent().find(
|
||||||
(metadata: DocsContentMetadata): boolean =>
|
(metadata: DocsContentMetadata): boolean =>
|
||||||
metadata.slug === (slug ? slug[0] : "home")
|
metadata.slug === (slug || "home")
|
||||||
); // Get the content to display based on the provided slug
|
); // Get the content to display based on the provided slug
|
||||||
|
|
||||||
// Return a 404 if the content is not found
|
// Return a 404 if the content is not found
|
||||||
if (!content) {
|
if (!content) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
const splitSlug: string[] = content.slug?.split("/") || [];
|
||||||
|
|
||||||
return <main>{content.title}</main>;
|
return (
|
||||||
|
<main>
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList className="text-minecraft-green-4">
|
||||||
|
{["docs", ...splitSlug].map(
|
||||||
|
(part: string, index: number): ReactElement => (
|
||||||
|
<div className="flex items-center" key={index}>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink
|
||||||
|
href={
|
||||||
|
(index > 0 ? "/docs" : "") +
|
||||||
|
`/${part}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{capitalize(part)}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
{index < splitSlug.length && (
|
||||||
|
<BreadcrumbSeparator className="pl-1.5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default ContentPage;
|
export default ContentPage;
|
||||||
|
@ -12,9 +12,11 @@ const DocumentationLayout = ({
|
|||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}>): ReactElement => (
|
}>): ReactElement => (
|
||||||
<section className="h-screen flex flex-col justify-center items-center">
|
<section className="h-screen flex justify-center items-center">
|
||||||
<Sidebar />
|
<div className="flex gap-10">
|
||||||
{children}
|
<Sidebar />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
export default DocumentationLayout;
|
export default DocumentationLayout;
|
||||||
|
@ -1,25 +1,40 @@
|
|||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { Stats } from "node:fs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The regex to match for metadata.
|
* The regex to match for metadata.
|
||||||
*/
|
*/
|
||||||
const METADATA_REGEX: RegExp = /---\s*([\s\S]*?)\s*---/;
|
const METADATA_REGEX: RegExp = /---\s*([\s\S]*?)\s*---/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory docs are stored in.
|
||||||
|
*/
|
||||||
|
const DOCS_DIR: string = path.join(process.cwd(), "docs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the content to
|
* Get the content to
|
||||||
* display in the docs.
|
* display in the docs.
|
||||||
*/
|
*/
|
||||||
export const getDocsContent = (): DocsContentMetadata[] =>
|
export const getDocsContent = (): DocsContentMetadata[] => {
|
||||||
getMetadata<DocsContentMetadata>(path.join(process.cwd(), "docs"));
|
const content: DocsContentMetadata[] = [];
|
||||||
|
for (let directory of getRecursiveDirectories(DOCS_DIR)) {
|
||||||
|
content.push(...getMetadata<DocsContentMetadata>(DOCS_DIR, directory));
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the metadata of mdx
|
* Get the metadata of mdx
|
||||||
* files in the given directory.
|
* files in the given directory.
|
||||||
*
|
*
|
||||||
|
* @param parent the parent directory to search
|
||||||
* @param directory the directory to search
|
* @param directory the directory to search
|
||||||
*/
|
*/
|
||||||
export const getMetadata = <T extends MDXMetadata>(directory: string): T[] => {
|
const getMetadata = <T extends MDXMetadata>(
|
||||||
|
parent: string,
|
||||||
|
directory: string
|
||||||
|
): T[] => {
|
||||||
const files: string[] = fs
|
const files: string[] = fs
|
||||||
.readdirSync(directory)
|
.readdirSync(directory)
|
||||||
.filter((file: string): boolean => {
|
.filter((file: string): boolean => {
|
||||||
@ -29,8 +44,12 @@ export const getMetadata = <T extends MDXMetadata>(directory: string): T[] => {
|
|||||||
return files.map((file: string): T => {
|
return files.map((file: string): T => {
|
||||||
const filePath: string = path.join(directory, file); // The path of the file
|
const filePath: string = path.join(directory, file); // The path of the file
|
||||||
return {
|
return {
|
||||||
|
slug: filePath
|
||||||
|
.replace(parent, "")
|
||||||
|
.replace(/\\/g, "/") // Normalize the path
|
||||||
|
.replace(/\.mdx?$/, "")
|
||||||
|
.substring(1),
|
||||||
...parseMetadata<T>(fs.readFileSync(filePath, "utf-8")),
|
...parseMetadata<T>(fs.readFileSync(filePath, "utf-8")),
|
||||||
slug: path.basename(file, path.extname(file)),
|
|
||||||
}; // Map each file to its metadata
|
}; // Map each file to its metadata
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -65,3 +84,24 @@ const parseMetadata = <T extends MDXMetadata>(content: string): T => {
|
|||||||
// slug is empty, and is defined later on.
|
// slug is empty, and is defined later on.
|
||||||
return { ...metadata, content } as T;
|
return { ...metadata, content } as T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get directories recursively
|
||||||
|
* in the given directory.
|
||||||
|
*
|
||||||
|
* @param directory the directory to search
|
||||||
|
* @return the directories
|
||||||
|
*/
|
||||||
|
const getRecursiveDirectories = (directory: string): string[] => {
|
||||||
|
const directories: string[] = [directory]; // The directories to return
|
||||||
|
|
||||||
|
for (let sub of fs.readdirSync(directory)) {
|
||||||
|
const subDirPath: string = path.join(directory, sub); // The sub dir path
|
||||||
|
const stats: Stats = fs.statSync(subDirPath); // Get file stats
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
directories.push(...getRecursiveDirectories(subDirPath)); // Recursively get directories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return directories;
|
||||||
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sidebar for the docs page.
|
* The sidebar for the docs page.
|
||||||
@ -6,8 +7,14 @@ import { ReactElement } from "react";
|
|||||||
* @returns the sidebar jsx
|
* @returns the sidebar jsx
|
||||||
*/
|
*/
|
||||||
const Sidebar = (): ReactElement => (
|
const Sidebar = (): ReactElement => (
|
||||||
<div className="absolute left-20 h-72 p-5 bg-muted rounded-xl">
|
<div className="w-60 h-80 px-3 py-5 flex justify-center bg-muted border border-zinc-700/70 rounded-lg">
|
||||||
<h1 className="font-semibold uppercase">SIDEBAR</h1>
|
{/* Search */}
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
name="search"
|
||||||
|
placeholder="Quick search..."
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
2
Frontend/src/app/types/mdx.d.ts
vendored
2
Frontend/src/app/types/mdx.d.ts
vendored
@ -25,7 +25,7 @@ type MDXMetadata = {
|
|||||||
/**
|
/**
|
||||||
* The slug of the file, defined once read.
|
* The slug of the file, defined once read.
|
||||||
*/
|
*/
|
||||||
slug: string;
|
slug?: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata of the file.
|
* The metadata of the file.
|
||||||
|
4
Frontend/src/app/types/page.d.ts
vendored
4
Frontend/src/app/types/page.d.ts
vendored
@ -2,6 +2,6 @@
|
|||||||
* Props for a page.
|
* Props for a page.
|
||||||
*/
|
*/
|
||||||
export type PageProps = {
|
export type PageProps = {
|
||||||
params: { slug: string };
|
params: { slug: string[] };
|
||||||
searchParams: { [key: string]: string | string[] | undefined };
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user