diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f71b648c..37f59fb5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. - diff --git a/bindings/GrayMatter.res b/bindings/GrayMatter.res index d9b73312..0a6cab66 100644 --- a/bindings/GrayMatter.res +++ b/bindings/GrayMatter.res @@ -8,6 +8,12 @@ type output = {data: pageContent, content: string} @module("gray-matter") external matter: string => output = "default" let forceInvalidException: JsYaml.forceInvalidException = 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 +} diff --git a/bindings/GrayMatter.resi b/bindings/GrayMatter.resi index 5c03a00f..68b6d71f 100644 --- a/bindings/GrayMatter.resi +++ b/bindings/GrayMatter.resi @@ -5,6 +5,4 @@ type pageContent = { type output = {data: pageContent, content: string} -let matter: string => output - -let forceInvalidException: JsYaml.forceInvalidException +let ofMarkdown: string => output diff --git a/common/MdastToc.res b/common/MdastToc.res index 154cd417..bf4a3152 100644 --- a/common/MdastToc.res +++ b/common/MdastToc.res @@ -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 { @@ -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)) @@ -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 } diff --git a/components/MarkdownPage.res b/components/MarkdownPage.res index 48297861..1067402d 100644 --- a/components/MarkdownPage.res +++ b/components/MarkdownPage.res @@ -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 => {s(label)} + | Some(idx) => {s(label)} + } + } + @react.component let make = (~content) =>
Belt.List.mapWithIndex((idx, hdg) =>
- // Expanded: "text-gray-400 rotate-90", Collapsed: "text-gray-300" - - {s(hdg.label)} - + { + // Expanded: "text-gray-400 rotate-90", Collapsed: "text-gray-300" + headingLink(2, hdg.id, hdg.label, ()) + } {hdg.children - ->Belt.List.mapWithIndex((idx, sub) => - - {s(sub.label)} - - ) - ->Belt.List.toArray |> React.array} + ->Belt.List.mapWithIndex((idx, sub) => { + headingLink(3, sub.id, sub.label, ~idx, ()) + }) + ->Belt.List.toArray + ->React.array}
) - ->Belt.List.toArray |> React.array} + ->Belt.List.toArray + ->React.array}
diff --git a/pages/resources/[tutorial].res b/pages/resources/[tutorial].res index d1194a46..4055d4d3 100644 --- a/pages/resources/[tutorial].res +++ b/pages/resources/[tutorial].res @@ -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 = { @@ -86,7 +77,7 @@ let getStaticProps = ctx => { let getStaticPaths: Next.GetStaticPaths.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/")