Skip to content
This repository was archived by the owner on Feb 15, 2022. It is now read-only.

Cleanup markdown table of contents code #322

Merged
5 commits merged into from
May 5, 2021
Merged
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
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,3 @@ Go to `http://localhost:3000`
## Architecture

We have prepared some diagrams and explanations to orient new developers in the wiki area. The site expands upon the default build process in NextJS to accommodate more sophisticated markdown transformations.

8 changes: 7 additions & 1 deletion bindings/GrayMatter.res
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ type output = {data: pageContent, content: string}
@module("gray-matter") external matter: string => output = "default"

let forceInvalidException: JsYaml.forceInvalidException<pageContent> = c => {
let length = Js.Option.getWithDefault("", c.pageDescription) |> Js.String.length
let length = Js.Option.getWithDefault("", c.pageDescription)->Js.String.length
let _ = (Js.String.length(c.title), length)
}

let ofMarkdown = fileContents => {
let parsed = matter(fileContents)
forceInvalidException(parsed.data)
parsed
}
4 changes: 1 addition & 3 deletions bindings/GrayMatter.resi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@ type pageContent = {

type output = {data: pageContent, content: string}

let matter: string => output

let forceInvalidException: JsYaml.forceInvalidException<pageContent>
let ofMarkdown: string => output
33 changes: 16 additions & 17 deletions common/MdastToc.res
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ let transformer = (rootnode: Unified.rootnode, file: Unified.vfile) => {
let rec collect = (nodes, inProgress) => {
switch nodes {
| list{} =>
switch inProgress {
| None => list{}
| Some(_, entry) => list{entry}
}
inProgress
->Belt.Option.map(((_, entry)) => entry)
->Belt.Option.mapWithDefault(list{}, e => list{e})
| list{h: Unified.headingnode, ...tail} =>
let d = h.depth
if d >= 2 || d <= 3 {
Expand All @@ -22,13 +21,14 @@ let transformer = (rootnode: Unified.rootnode, file: Unified.vfile) => {
}
switch inProgress {
| None => collect(tail, Some(d, entry))
| Some(lastRootDepth, inProgress) if d <= lastRootDepth => list{
inProgress,
| Some(lastRootDepth, completed) if d <= lastRootDepth => list{
completed,
...collect(tail, Some(d, entry)),
}
| Some(lastRootDepth, inProgress) =>
let inProgress = {
...inProgress,
// TODO: use add and perform reverse when inProgress is complete
children: Belt.List.concat(inProgress.children, list{entry}),
}
collect(tail, Some(lastRootDepth, inProgress))
Expand All @@ -42,17 +42,16 @@ let transformer = (rootnode: Unified.rootnode, file: Unified.vfile) => {
}
}
}
let headings = collect(
Array.to_list(
Belt.Array.keepMap(rootnode.children, ch =>
switch ch {
| {\"type": "heading", depth: Some(_)} => Some(Unified.asHeadingNode(ch))
| _ => None
}
),
),
None,
)
let headings =
rootnode.children
->Belt.Array.keepMap(ch =>
switch ch {
| {\"type": "heading", depth: Some(_)} => Some(Unified.asHeadingNode(ch))
| _ => None
}
)
->Array.to_list
->collect(None)

file.toc = headings
}
Expand Down
45 changes: 29 additions & 16 deletions components/MarkdownPage.res
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ module TableOfContents = {
toc: Unified.MarkdownTableOfContents.t,
}

exception UnexpectedTOCHeadingDepth(int)

let headingLink = (depth, id, label, ~idx=?, ()) => {
let indent = switch depth {
// NOTE: we aren't building a dynamic classname string to be
// compatible with the tailwind css purging logic
| 2 => ""
| 3 => "pl-6"
| _ => raise(UnexpectedTOCHeadingDepth(depth))
}
let href = j`#${id}`
let className = `${indent} block text-gray-600 hover:text-gray-900 pr-2 py-2 text-sm font-medium`
switch idx {
| None => <a href className> {s(label)} </a>
| Some(idx) => <a key={Js.Int.toString(idx)} href className> {s(label)} </a>
}
}

@react.component
let make = (~content) =>
<div
Expand All @@ -26,25 +44,20 @@ module TableOfContents = {
{content.toc
->Belt.List.mapWithIndex((idx, hdg) =>
<div key={Js.Int.toString(idx)} className="space-y-1">
// Expanded: "text-gray-400 rotate-90", Collapsed: "text-gray-300"
<a
href={"#" ++ hdg.id}
className="block text-gray-600 hover:text-gray-900 pr-2 py-2 text-sm font-medium">
{s(hdg.label)}
</a>
{
// Expanded: "text-gray-400 rotate-90", Collapsed: "text-gray-300"
headingLink(2, hdg.id, hdg.label, ())
}
{hdg.children
->Belt.List.mapWithIndex((idx, sub) =>
<a
href={"#" ++ sub.id}
className="block pl-6 pr-2 py-2 text-sm font-medium text-gray-600 hover:text-gray-900"
key={Js.Int.toString(idx)}>
{s(sub.label)}
</a>
)
->Belt.List.toArray |> React.array}
->Belt.List.mapWithIndex((idx, sub) => {
headingLink(3, sub.id, sub.label, ~idx, ())
})
->Belt.List.toArray
->React.array}
</div>
)
->Belt.List.toArray |> React.array}
->Belt.List.toArray
->React.array}
</nav>
</div>
</div>
Expand Down
35 changes: 13 additions & 22 deletions pages/resources/[tutorial].res
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,19 @@ let getStaticProps = ctx => {
let baseDirectory = "data/tutorials/"
// TODO: find the location of the tutorial
let contentFilePath = baseDirectory ++ tutorial ++ "/" ++ tutorial ++ ".md"
let fileContents = Fs.readFileSync(contentFilePath)
let parsed = GrayMatter.matter(fileContents)
// TODO: move this into GrayMatter or another module
GrayMatter.forceInvalidException(parsed.data)
let source = parsed.content
let parsed = Fs.readFileSync(contentFilePath)->GrayMatter.ofMarkdown

let resPromise = Unified.process(
Unified.use(
Unified.use(
Unified.use(
Unified.use(
Unified.use(Unified.use(Unified.unified(), Unified.remarkParse), Unified.remarkSlug),
MdastToc.plugin,
),
Unified.remark2rehype,
),
Unified.rehypeHighlight,
),
Unified.rehypeStringify,
),
source,
)
let resPromise =
parsed.content->Unified.process(
Unified.unified()
->Unified.use(Unified.remarkParse)
->Unified.use(Unified.remarkSlug)
->Unified.use(MdastToc.plugin)
->Unified.use(Unified.remark2rehype)
->Unified.use(Unified.rehypeHighlight)
->Unified.use(Unified.rehypeStringify),
_,
)

Js.Promise.then_((res: Unified.vfile) => {
let props = {
Expand All @@ -86,7 +77,7 @@ let getStaticProps = ctx => {

let getStaticPaths: Next.GetStaticPaths.t<Params.t> = () => {
// TODO: move this logic into a module dedicated to fetching tutorials
// TODO: throw exception if any tutorials have the same filename or add a more parts to the tutorials path
// TODO: throw exception if any tutorials have the same filename or add more parts to the tutorials path
// TODO: throw exception if any entry is not a directory
let markdownFiles = Fs.readdirSyncEntries("data/tutorials/")

Expand Down