diff --git a/app/routes.res b/app/routes.res index db86b1ff5..f321f382e 100644 --- a/app/routes.res +++ b/app/routes.res @@ -47,6 +47,11 @@ let blogArticleRoutes = route(path, "./routes/BlogArticleRoute.jsx", ~options={id: path}) ) +let docsReactRoutes = + MdxFile.scanPaths(~dir="markdown-pages/docs/react", ~alias="docs/react")->Array.map(path => + route(path, "./routes/DocsReactRoute.jsx", ~options={id: path}) + ) + let communityRoutes = MdxFile.scanPaths(~dir="markdown-pages/community", ~alias="community")->Array.map(path => route(path, "./routes/CommunityRoute.jsx", ~options={id: path}) @@ -58,11 +63,11 @@ let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r => ->Option.map(path => path === "blog" || String.startsWith(path, "blog/") || + path === "docs/react" || + String.startsWith(path, "docs/react/") || path === "community" || String.startsWith(path, "community/") || - path === "docs/manual/api" || - path === "community" || - String.startsWith(path, "community/") + path === "docs/manual/api" ) ->Option.getOr(false) ) @@ -85,6 +90,7 @@ let default = [ ...beltRoutes, ...domRoutes, ...blogArticleRoutes, + ...docsReactRoutes, ...communityRoutes, ...mdxRoutes, route("*", "./routes/NotFoundRoute.jsx"), diff --git a/app/routes/DocsReactRoute.res b/app/routes/DocsReactRoute.res new file mode 100644 index 000000000..9b91965e8 --- /dev/null +++ b/app/routes/DocsReactRoute.res @@ -0,0 +1,171 @@ +type loaderData = { + compiledMdx: CompiledMdx.t, + categories: array, + entries: array, + title: string, + description: string, + filePath: string, +} + +let convertToNavItems = (items, rootPath) => + Array.map(items, (item): SidebarLayout.Sidebar.NavItem.t => { + let href = switch item.Mdx.slug { + | Some(slug) => `${rootPath}/${slug}` + | None => rootPath + } + { + name: item.title, + href, + } + }) + +let getGroup = (groups, groupName): SidebarLayout.Sidebar.Category.t => { + { + name: groupName, + items: groups + ->Dict.get(groupName) + ->Option.getOr([]), + } +} + +let getAllGroups = (groups, groupNames): array => + groupNames->Array.map(item => getGroup(groups, item)) + +// Build sidebar categories from all React docs, sorted by their "order" field in frontmatter +let reactTableOfContents = async () => { + let groups = + (await Mdx.allMdx(~filterByPaths=["markdown-pages/docs"])) + ->Mdx.filterMdxPages("docs/react") + ->Mdx.groupBySection + ->Dict.mapValues(values => values->Mdx.sortSection->convertToNavItems("/docs/react")) + + getAllGroups(groups, ["Overview", "Main Concepts", "Hooks & State Management", "Guides"]) +} + +let loader: ReactRouter.Loader.t = async ({request}) => { + let {pathname} = WebAPI.URL.make(~url=request.url) + let filePath = MdxFile.resolveFilePath( + (pathname :> string), + ~dir="markdown-pages/docs/react", + ~alias="docs/react", + ) + + let raw = await Node.Fs.readFile(filePath, "utf-8") + let {frontmatter}: MarkdownParser.result = MarkdownParser.parseSync(raw) + + let description = switch frontmatter { + | Object(dict) => + switch dict->Dict.get("description") { + | Some(String(s)) => s + | _ => "" + } + | _ => "" + } + + let title = switch frontmatter { + | Object(dict) => + switch dict->Dict.get("title") { + | Some(String(s)) => s + | _ => "" + } + | _ => "" + } + + let categories = await reactTableOfContents() + + let compiledMdx = await MdxFile.compileMdx(raw, ~filePath, ~remarkPlugins=Mdx.plugins) + + // Build table of contents entries from markdown headings + let markdownTree = Mdast.fromMarkdown(raw) + let tocResult = Mdast.toc(markdownTree, {maxDepth: 2}) + + let headers = Dict.make() + Mdast.reduceHeaders(tocResult.map, headers) + + let entries = + headers + ->Dict.toArray + ->Array.map(((header, url)): TableOfContents.entry => { + header, + href: (url :> string), + }) + ->Array.slice(~start=2) // skip document entry and H1 title, keep h2 sections + + { + compiledMdx, + categories, + entries, + title: `${title} | ReScript React`, + description, + filePath, + } +} + +let default = () => { + let {pathname} = ReactRouter.useLocation() + let {compiledMdx, categories, entries, title, description, filePath} = ReactRouter.useLoaderData() + + let breadcrumbs = list{ + {Url.name: "Docs", href: "/docs/react/introduction"}, + { + Url.name: "rescript-react", + href: "/docs/react/introduction", + }, + } + + let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}` + + let sidebarContent = + + + <> + + + + + + {React.string("Edit")} + + + +
+ +
+
+ +} diff --git a/app/routes/DocsReactRoute.resi b/app/routes/DocsReactRoute.resi new file mode 100644 index 000000000..a8a275a4c --- /dev/null +++ b/app/routes/DocsReactRoute.resi @@ -0,0 +1,12 @@ +type loaderData = { + compiledMdx: CompiledMdx.t, + categories: array, + entries: array, + title: string, + description: string, + filePath: string, +} + +let loader: ReactRouter.Loader.t + +let default: unit => React.element diff --git a/app/routes/MdxRoute.res b/app/routes/MdxRoute.res index 889a7c890..3634e9bc0 100644 --- a/app/routes/MdxRoute.res +++ b/app/routes/MdxRoute.res @@ -99,22 +99,6 @@ let manualTableOfContents = async () => { categories } -let reactTableOfContents = async () => { - let groups = - (await allMdx(~filterByPaths=["markdown-pages/docs"])) - ->filterMdxPages("docs/react") - ->groupBySection - ->Dict.mapValues(values => values->sortSection->convertToNavItems("/docs/react")) - - // these are the categories that appear in the sidebar - let categories: array = getAllGroups( - groups, - ["Overview", "Main Concepts", "Hooks & State Management", "Guides"], - ) - - categories -} - let loader: ReactRouter.Loader.t = async ({request}) => { let {pathname} = WebAPI.URL.make(~url=request.url) @@ -148,8 +132,6 @@ let loader: ReactRouter.Loader.t = async ({request}) => { let categories = { if pathname->String.includes("docs/manual") { await manualTableOfContents() - } else if pathname->String.includes("docs/react") { - await reactTableOfContents() } else { [] } @@ -205,21 +187,11 @@ let loader: ReactRouter.Loader.t = async ({request}) => { href: "/docs/manual/" ++ "introduction", }, }) - : pathname->String.includes("docs/react") - ? Some(list{ - {Url.name: "Docs", href: "/docs/"}, - { - Url.name: "rescript-react", - href: "/docs/react/" ++ "introduction", - }, - }) : None let metaTitleCategory = { let path = (pathname :> string) - let title = if path->String.includes("docs/react") { - "ReScript React" - } else if path->String.includes("docs/manual") { + let title = if path->String.includes("docs/manual") { "ReScript Language Manual" } else { "ReScript" @@ -255,8 +227,7 @@ let default = () => { <> {if ( (pathname :> string)->String.includes("docs/manual") || - (pathname :> string)->String.includes("docs/react") || - (pathname :> string)->String.includes("docs/guidelines") + (pathname :> string)->String.includes("docs/guidelines") ) { <> Nullable.getOr("")} /> @@ -267,8 +238,6 @@ let default = () => { if index === 0 { if (pathname :> string)->String.includes("docs/manual") { {...item, href: "/docs/manual/introduction"} - } else if (pathname :> string)->String.includes("docs/react") { - {...item, href: "/docs/react/introduction"} } else { item } diff --git a/src/Mdx.res b/src/Mdx.res index cd6b388db..b7840e20e 100644 --- a/src/Mdx.res +++ b/src/Mdx.res @@ -82,7 +82,9 @@ let sortSection = mdxPages => Array.toSorted(mdxPages, (a: attributes, b: attributes) => switch (a.order, b.order) { | (Some(a), Some(b)) => a > b ? 1.0 : -1.0 - | _ => -1.0 + | (Some(_), None) => -1.0 + | (None, Some(_)) => 1.0 + | (None, None) => 0.0 } )