Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/routes.res
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ let stdlibRoutes =
let beltRoutes =
beltPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path}))

let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")
let blogArticleRoutes =
MdxFile.scanPaths(~dir="markdown-pages/blog", ~alias="blog")->Array.map(path =>
route(path, "./routes/BlogArticleRoute.jsx", ~options={id: path})
)

let mdxRoutes =
mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r =>
!(r.path->Option.map(String.includes(_, "blog"))->Option.getOr(false))
)

let default = [
index("./routes/LandingPageRoute.jsx"),
Expand All @@ -44,6 +52,7 @@ let default = [
route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}),
...stdlibRoutes,
...beltRoutes,
...blogArticleRoutes,
...mdxRoutes,
route("*", "./routes/NotFoundRoute.jsx"),
]
52 changes: 52 additions & 0 deletions app/routes/BlogArticleRoute.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
type loaderData = {
content: string,
blogPost: BlogApi.post,
title: string,
}

let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
let {pathname} = WebAPI.URL.make(~url=request.url)
let filePath = MdxFile.resolveFilePath(
(pathname :> string),
~dir="markdown-pages/blog",
~alias="blog",
)
let {content, frontmatter} = await MdxFile.loadFile(filePath)

let frontmatter = switch BlogFrontmatter.decode(frontmatter) {
| Ok(fm) => fm
| Error(msg) => JsExn.throw(msg)
}

let archived = filePath->String.includes("/archived/")

let slug =
filePath
->Node.Path.basename
->String.replace(".mdx", "")
->String.replaceRegExp(/^\d\d\d\d-\d\d-\d\d-/, "")

let path = archived ? "archived/" ++ slug : slug

let blogPost: BlogApi.post = {
path,
archived,
frontmatter,
}

{
content,
blogPost,
title: `${frontmatter.title} | ReScript Blog`,
}
}

let default = () => {
let {content, blogPost: {frontmatter, archived, path}} = ReactRouter.useLoaderData()

<BlogArticle frontmatter isArchived=archived path>
<ReactMarkdown components=MarkdownComponents.default rehypePlugins=[Rehype.Plugin(Rehype.raw)]>
content
</ReactMarkdown>
</BlogArticle>
}
9 changes: 9 additions & 0 deletions app/routes/BlogArticleRoute.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type loaderData = {
content: string,
blogPost: BlogApi.post,
title: string,
}

let loader: ReactRouter.Loader.t<loaderData>

let default: unit => React.element
20 changes: 1 addition & 19 deletions app/routes/MdxRoute.res
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ type loaderData = {
...Mdx.t,
categories: array<SidebarLayout.Sidebar.Category.t>,
entries: array<TableOfContents.entry>,
blogPost?: BlogApi.post,
mdxSources?: array<SyntaxLookup.item>,
activeSyntaxItem?: SyntaxLookup.item,
breadcrumbs?: list<Url.breadcrumb>,
Expand Down Expand Up @@ -134,18 +133,7 @@ let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {

let mdx = await loadMdx(request, ~options={remarkPlugins: Mdx.plugins})

if pathname->String.includes("blog") {
let res: loaderData = {
__raw: mdx.__raw,
attributes: mdx.attributes,
entries: [],
categories: [],
blogPost: mdx.attributes->BlogLoader.transform,
title: `${mdx.attributes.title} | ReScript Blog`,
filePath: None,
}
res
} else if pathname->String.includes("syntax-lookup") {
if pathname->String.includes("syntax-lookup") {
let mdxSources =
(await allMdx(~filterByPaths=["markdown-pages/syntax-lookup"]))
->Array.filter(page =>
Expand Down Expand Up @@ -418,12 +406,6 @@ let default = () => {
<CommunityLayout categories entries>
<div className="markdown-body"> {component()} </div>
</CommunityLayout>
} else if (pathname :> string)->String.includes("blog") {
switch loaderData.blogPost {
| Some({frontmatter, archived, path}) =>
<BlogArticle frontmatter isArchived=archived path> {component()} </BlogArticle>
| None => React.null // TODO: Post RR7 show an error?
}
} else {
switch loaderData.mdxSources {
| Some(mdxSources) =>
Expand Down
1 change: 0 additions & 1 deletion app/routes/MdxRoute.resi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ type loaderData = {
...Mdx.t,
categories: array<SidebarLayout.Sidebar.Category.t>,
entries: array<TableOfContents.entry>,
blogPost?: BlogApi.post,
mdxSources?: array<SyntaxLookup.item>,
activeSyntaxItem?: SyntaxLookup.item,
breadcrumbs?: list<Url.breadcrumb>,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"react-router": "^7.12.0",
"react-router-dom": "^7.9.4",
"react-router-mdx": "patch:react-router-mdx@npm%3A1.0.8#~/.yarn/patches/react-router-mdx-npm-1.0.8-d4402c3003.patch",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
Expand Down
43 changes: 43 additions & 0 deletions src/MdxFile.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
type fileData = {
content: string,
frontmatter: JSON.t,
}

let resolveFilePath = (pathname, ~dir, ~alias) => {
let path = if pathname->String.startsWith("/") {
pathname->String.slice(~start=1, ~end=String.length(pathname))
} else {
pathname
}
let relativePath = path->String.replace(alias, dir)
relativePath ++ ".mdx"
}

let loadFile = async filePath => {
let raw = await Node.Fs.readFile(filePath, "utf-8")
let {frontmatter, content}: MarkdownParser.result = MarkdownParser.parseSync(raw)
{content, frontmatter}
}

// Recursively scan a directory for .mdx files
let rec scanDir = (baseDir, currentDir) => {
let entries = Node.Fs.readdirSync(currentDir)
entries->Array.flatMap(entry => {
let fullPath = Node.Path.join2(currentDir, entry)
if Node.Fs.statSync(fullPath)["isDirectory"]() {
scanDir(baseDir, fullPath)
} else if Node.Path.extname(entry) === ".mdx" {
// Get the relative path from baseDir
let relativePath = fullPath->String.replace(baseDir ++ "/", "")->String.replace(".mdx", "")
[relativePath]
} else {
[]
}
})
}

let scanPaths = (~dir, ~alias) => {
scanDir(dir, dir)->Array.map(relativePath => {
alias ++ "/" ++ relativePath
})
}
19 changes: 19 additions & 0 deletions src/MdxFile.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type fileData = {
content: string,
frontmatter: JSON.t,
}

/** Maps a URL pathname to an .mdx file path on disk.
* e.g. `/blog/release-12-0-0` with ~dir="markdown-pages/blog" ~alias="blog"
* → `markdown-pages/blog/release-12-0-0.mdx`
*/
let resolveFilePath: (string, ~dir: string, ~alias: string) => string

/** Read a file from disk and parse its frontmatter using MarkdownParser. */
let loadFile: string => promise<fileData>

/** Scan a directory recursively for .mdx files and return URL paths.
* e.g. scanPaths(~dir="markdown-pages/blog", ~alias="blog")
* → ["blog/release-12-0-0", "blog/archived/some-post", ...]
*/
let scanPaths: (~dir: string, ~alias: string) => array<string>
1 change: 1 addition & 0 deletions src/bindings/Rehype.res
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ type rec rehypePlugin =
| WithOptions(array<pluginOrOption>)

@module("rehype-slug") external slug: plugin = "default"
@module("rehype-raw") external raw: plugin = "default"
105 changes: 104 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5877,6 +5877,22 @@ __metadata:
languageName: node
linkType: hard

"hast-util-from-parse5@npm:^8.0.0":
version: 8.0.3
resolution: "hast-util-from-parse5@npm:8.0.3"
dependencies:
"@types/hast": "npm:^3.0.0"
"@types/unist": "npm:^3.0.0"
devlop: "npm:^1.0.0"
hastscript: "npm:^9.0.0"
property-information: "npm:^7.0.0"
vfile: "npm:^6.0.0"
vfile-location: "npm:^5.0.0"
web-namespaces: "npm:^2.0.0"
checksum: 10c0/40ace6c0ad43c26f721c7499fe408e639cde917b2350c9299635e6326559855896dae3c3ebf7440df54766b96c4276a7823e8f376a2b6a28b37b591f03412545
languageName: node
linkType: hard

"hast-util-heading-rank@npm:^3.0.0":
version: 3.0.0
resolution: "hast-util-heading-rank@npm:3.0.0"
Expand All @@ -5886,6 +5902,36 @@ __metadata:
languageName: node
linkType: hard

"hast-util-parse-selector@npm:^4.0.0":
version: 4.0.0
resolution: "hast-util-parse-selector@npm:4.0.0"
dependencies:
"@types/hast": "npm:^3.0.0"
checksum: 10c0/5e98168cb44470dc274aabf1a28317e4feb09b1eaf7a48bbaa8c1de1b43a89cd195cb1284e535698e658e3ec26ad91bc5e52c9563c36feb75abbc68aaf68fb9f
languageName: node
linkType: hard

"hast-util-raw@npm:^9.0.0":
version: 9.1.0
resolution: "hast-util-raw@npm:9.1.0"
dependencies:
"@types/hast": "npm:^3.0.0"
"@types/unist": "npm:^3.0.0"
"@ungap/structured-clone": "npm:^1.0.0"
hast-util-from-parse5: "npm:^8.0.0"
hast-util-to-parse5: "npm:^8.0.0"
html-void-elements: "npm:^3.0.0"
mdast-util-to-hast: "npm:^13.0.0"
parse5: "npm:^7.0.0"
unist-util-position: "npm:^5.0.0"
unist-util-visit: "npm:^5.0.0"
vfile: "npm:^6.0.0"
web-namespaces: "npm:^2.0.0"
zwitch: "npm:^2.0.0"
checksum: 10c0/d0d909d2aedecef6a06f0005cfae410d6475e6e182d768bde30c3af9fcbbe4f9beb0522bdc21d0679cb3c243c0df40385797ed255148d68b3d3f12e82d12aacc
languageName: node
linkType: hard

"hast-util-to-estree@npm:^3.0.0":
version: 3.1.3
resolution: "hast-util-to-estree@npm:3.1.3"
Expand Down Expand Up @@ -5952,6 +5998,21 @@ __metadata:
languageName: node
linkType: hard

"hast-util-to-parse5@npm:^8.0.0":
version: 8.0.1
resolution: "hast-util-to-parse5@npm:8.0.1"
dependencies:
"@types/hast": "npm:^3.0.0"
comma-separated-tokens: "npm:^2.0.0"
devlop: "npm:^1.0.0"
property-information: "npm:^7.0.0"
space-separated-tokens: "npm:^2.0.0"
web-namespaces: "npm:^2.0.0"
zwitch: "npm:^2.0.0"
checksum: 10c0/8e8a1817c7ff8906ac66e7201b1b8d19d9e1b705e695a6e71620270d498d982ec1ecc0e227bd517f723e91e7fdfb90ef75f9ae64d14b3b65239a7d5e1194d7dd
languageName: node
linkType: hard

"hast-util-to-string@npm:^3.0.0":
version: 3.0.1
resolution: "hast-util-to-string@npm:3.0.1"
Expand All @@ -5970,6 +6031,19 @@ __metadata:
languageName: node
linkType: hard

"hastscript@npm:^9.0.0":
version: 9.0.1
resolution: "hastscript@npm:9.0.1"
dependencies:
"@types/hast": "npm:^3.0.0"
comma-separated-tokens: "npm:^2.0.0"
hast-util-parse-selector: "npm:^4.0.0"
property-information: "npm:^7.0.0"
space-separated-tokens: "npm:^2.0.0"
checksum: 10c0/18dc8064e5c3a7a2ae862978e626b97a254e1c8a67ee9d0c9f06d373bba155ed805fc5b5ce21b990fb7bc174624889e5e1ce1cade264f1b1d58b48f994bc85ce
languageName: node
linkType: hard

"help-me@npm:^5.0.0":
version: 5.0.0
resolution: "help-me@npm:5.0.0"
Expand Down Expand Up @@ -8597,7 +8671,7 @@ __metadata:
languageName: node
linkType: hard

"parse5@npm:^7.2.1":
"parse5@npm:^7.0.0, parse5@npm:^7.2.1":
version: 7.3.0
resolution: "parse5@npm:7.3.0"
dependencies:
Expand Down Expand Up @@ -9301,6 +9375,17 @@ __metadata:
languageName: node
linkType: hard

"rehype-raw@npm:^7.0.0":
version: 7.0.0
resolution: "rehype-raw@npm:7.0.0"
dependencies:
"@types/hast": "npm:^3.0.0"
hast-util-raw: "npm:^9.0.0"
vfile: "npm:^6.0.0"
checksum: 10c0/1435b4b6640a5bc3abe3b2133885c4dbff5ef2190ef9cfe09d6a63f74dd7d7ffd0cede70603278560ccf1acbfb9da9faae4b68065a28bc5aa88ad18e40f32d52
languageName: node
linkType: hard

"rehype-recma@npm:^1.0.0":
version: 1.0.0
resolution: "rehype-recma@npm:1.0.0"
Expand Down Expand Up @@ -9525,6 +9610,7 @@ __metadata:
react-router: "npm:^7.12.0"
react-router-dom: "npm:^7.9.4"
react-router-mdx: "patch:react-router-mdx@npm%3A1.0.8#~/.yarn/patches/react-router-mdx-npm-1.0.8-d4402c3003.patch"
rehype-raw: "npm:^7.0.0"
rehype-slug: "npm:^6.0.0"
rehype-stringify: "npm:^10.0.1"
remark: "npm:^15.0.1"
Expand Down Expand Up @@ -11132,6 +11218,16 @@ __metadata:
languageName: node
linkType: hard

"vfile-location@npm:^5.0.0":
version: 5.0.3
resolution: "vfile-location@npm:5.0.3"
dependencies:
"@types/unist": "npm:^3.0.0"
vfile: "npm:^6.0.0"
checksum: 10c0/1711f67802a5bc175ea69750d59863343ed43d1b1bb25c0a9063e4c70595e673e53e2ed5cdbb6dcdc370059b31605144d95e8c061b9361bcc2b036b8f63a4966
languageName: node
linkType: hard

"vfile-matter@npm:^5.0.0":
version: 5.0.1
resolution: "vfile-matter@npm:5.0.1"
Expand Down Expand Up @@ -11456,6 +11552,13 @@ __metadata:
languageName: node
linkType: hard

"web-namespaces@npm:^2.0.0":
version: 2.0.1
resolution: "web-namespaces@npm:2.0.1"
checksum: 10c0/df245f466ad83bd5cd80bfffc1674c7f64b7b84d1de0e4d2c0934fb0782e0a599164e7197a4bce310ee3342fd61817b8047ff04f076a1ce12dd470584142a4bd
languageName: node
linkType: hard

"webidl-conversions@npm:^7.0.0":
version: 7.0.0
resolution: "webidl-conversions@npm:7.0.0"
Expand Down