Skip to content

Commit 01770da

Browse files
committed
feat: add DocsReactRoute and fix sortSection comparator
- Create DocsReactRoute.res/.resi for /docs/react/* pages, following the same pattern as DocsManualRoute with React-specific sidebar categories (Overview, Main Concepts, Hooks & State Management, Guides) and breadcrumbs (Docs > rescript-react) - Register docsReactRoutes in app/routes.res via MdxFile.scanPaths and filter docs/react paths out of the legacy mdxRoutes - Fix inconsistent comparator in Mdx.sortSection: items with an order field now always sort before items without one, and items without order preserve their relative position (was _ => -1.0, now handles all four cases explicitly)
1 parent 6e9dd68 commit 01770da

4 files changed

Lines changed: 176 additions & 2 deletions

File tree

app/routes.res

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,16 @@ let docsManualRoutes =
3838
->Array.filter(path => !String.includes(path, "docs/manual/api"))
3939
->Array.map(path => route(path, "./routes/DocsManualRoute.jsx", ~options={id: path}))
4040

41+
let docsReactRoutes =
42+
MdxFile.scanPaths(~dir="markdown-pages/docs/react", ~alias="docs/react")->Array.map(path =>
43+
route(path, "./routes/DocsReactRoute.jsx", ~options={id: path})
44+
)
45+
4146
let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r => {
4247
let path = r.path->Option.getOr("")
43-
!(path->String.startsWith("blog")) && !(path->String.startsWith("docs/manual"))
48+
!(path->String.startsWith("blog")) &&
49+
!(path->String.startsWith("docs/manual")) &&
50+
!(path->String.startsWith("docs/react"))
4451
})
4552

4653
let default = [
@@ -59,6 +66,7 @@ let default = [
5966
...beltRoutes,
6067
...blogArticleRoutes,
6168
...docsManualRoutes,
69+
...docsReactRoutes,
6270
...mdxRoutes,
6371
route("*", "./routes/NotFoundRoute.jsx"),
6472
]

app/routes/DocsReactRoute.res

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
type loaderData = {
2+
compiledMdx: CompiledMdx.t,
3+
categories: array<SidebarLayout.Sidebar.Category.t>,
4+
entries: array<TableOfContents.entry>,
5+
title: string,
6+
description: string,
7+
filePath: string,
8+
}
9+
10+
// Build sidebar categories from all React docs, sorted by their "order" field in frontmatter
11+
let reactTableOfContents = async () => {
12+
let groups =
13+
(await MdxFile.loadAllAttributes(~dir="markdown-pages/docs"))
14+
->Mdx.filterMdxPages("docs/react")
15+
->Mdx.groupBySection
16+
->Dict.mapValues(values =>
17+
values->Mdx.sortSection->SidebarHelpers.convertToNavItems("/docs/react")
18+
)
19+
20+
SidebarHelpers.getAllGroups(
21+
groups,
22+
["Overview", "Main Concepts", "Hooks & State Management", "Guides"],
23+
)
24+
}
25+
26+
let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
27+
let {pathname} = WebAPI.URL.make(~url=request.url)
28+
let filePath = MdxFile.resolveFilePath(
29+
(pathname :> string),
30+
~dir="markdown-pages/docs/react",
31+
~alias="docs/react",
32+
)
33+
34+
let raw = await Node.Fs.readFile(filePath, "utf-8")
35+
let {frontmatter}: MarkdownParser.result = MarkdownParser.parseSync(raw)
36+
37+
let description = switch frontmatter {
38+
| Object(dict) =>
39+
switch dict->Dict.get("description") {
40+
| Some(String(s)) => s
41+
| _ => ""
42+
}
43+
| _ => ""
44+
}
45+
46+
let title = switch frontmatter {
47+
| Object(dict) =>
48+
switch dict->Dict.get("title") {
49+
| Some(String(s)) => s
50+
| _ => ""
51+
}
52+
| _ => ""
53+
}
54+
55+
let categories = await reactTableOfContents()
56+
57+
let compiledMdx = await MdxFile.compileMdx(raw, ~filePath, ~remarkPlugins=Mdx.plugins)
58+
59+
// Build table of contents entries from markdown headings
60+
let markdownTree = Mdast.fromMarkdown(raw)
61+
let tocResult = Mdast.toc(markdownTree, {maxDepth: 2})
62+
63+
let headers = Dict.make()
64+
Mdast.reduceHeaders(tocResult.map, headers)
65+
66+
let entries =
67+
headers
68+
->Dict.toArray
69+
->Array.map(((header, url)): TableOfContents.entry => {
70+
header,
71+
href: (url :> string),
72+
})
73+
->Array.slice(~start=2) // skip document entry and H1 title, keep h2 sections
74+
75+
{
76+
compiledMdx,
77+
categories,
78+
entries,
79+
title: `${title} | ReScript React`,
80+
description,
81+
filePath,
82+
}
83+
}
84+
85+
let default = () => {
86+
let {pathname} = ReactRouter.useLocation()
87+
let {compiledMdx, categories, entries, title, description, filePath} = ReactRouter.useLoaderData()
88+
89+
let breadcrumbs = list{
90+
{Url.name: "Docs", href: "/docs/react/introduction"},
91+
{
92+
Url.name: "rescript-react",
93+
href: "/docs/react/introduction",
94+
},
95+
}
96+
97+
let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}`
98+
99+
let sidebarContent =
100+
<aside className="px-4 w-full block">
101+
<div className="flex justify-between items-baseline">
102+
<div className="flex flex-col text-fire font-medium">
103+
<VersionSelect />
104+
</div>
105+
<button
106+
className="flex items-center" onClick={_ => NavbarUtils.closeMobileTertiaryDrawer()}
107+
>
108+
<Icon.Close />
109+
</button>
110+
</div>
111+
<div className="mb-56">
112+
{categories
113+
->Array.map(category => {
114+
let isItemActive = (navItem: SidebarLayout.Sidebar.NavItem.t) =>
115+
navItem.href === (pathname :> string)
116+
let getActiveToc = (navItem: SidebarLayout.Sidebar.NavItem.t) =>
117+
if navItem.href === (pathname :> string) {
118+
Some({TableOfContents.title, entries})
119+
} else {
120+
None
121+
}
122+
<div key=category.name>
123+
<SidebarLayout.Sidebar.Category
124+
isItemActive
125+
getActiveToc
126+
category
127+
onClick={_ => NavbarUtils.closeMobileTertiaryDrawer()}
128+
/>
129+
</div>
130+
})
131+
->React.array}
132+
</div>
133+
</aside>
134+
135+
<>
136+
<Meta title description />
137+
<NavbarSecondary />
138+
<NavbarTertiary sidebar=sidebarContent>
139+
<SidebarLayout.BreadCrumbs crumbs=breadcrumbs />
140+
<a
141+
href=editHref className="inline text-14 hover:underline text-fire" rel="noopener noreferrer"
142+
>
143+
{React.string("Edit")}
144+
</a>
145+
</NavbarTertiary>
146+
<DocsLayout categories activeToc={title, entries}>
147+
<div className="markdown-body">
148+
<MdxContent compiledMdx />
149+
</div>
150+
</DocsLayout>
151+
</>
152+
}

app/routes/DocsReactRoute.resi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
type loaderData = {
2+
compiledMdx: CompiledMdx.t,
3+
categories: array<SidebarLayout.Sidebar.Category.t>,
4+
entries: array<TableOfContents.entry>,
5+
title: string,
6+
description: string,
7+
filePath: string,
8+
}
9+
10+
let loader: ReactRouter.Loader.t<loaderData>
11+
12+
let default: unit => React.element

src/Mdx.res

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ let sortSection = mdxPages =>
8282
Array.toSorted(mdxPages, (a: attributes, b: attributes) =>
8383
switch (a.order, b.order) {
8484
| (Some(a), Some(b)) => a > b ? 1.0 : -1.0
85-
| _ => -1.0
85+
| (Some(_), None) => -1.0
86+
| (None, Some(_)) => 1.0
87+
| (None, None) => 0.0
8688
}
8789
)
8890

0 commit comments

Comments
 (0)