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

Generate markdown TOC #296

Merged
26 commits merged into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1ff33f1
Update LIVE_README.md
Apr 15, 2021
e1c5c8b
add remark dependency
Apr 16, 2021
6a07398
Merge branch 'kw1/generate-toc-spike' of github.com:solvuu-inc/next.o…
Apr 16, 2021
210640a
minor
Apr 16, 2021
271a5a4
start bindings for remark/unified
Apr 20, 2021
dfdaf78
add utility libraries for use in mdast heading traversal
Apr 20, 2021
b2ca37e
start hacking around es6/cjs issues with using unist-util-visit
Apr 20, 2021
d972c33
rough, homegrown, partial implementation of extracting toc
Apr 20, 2021
c4f060f
shift towards immutable accumulators
Apr 20, 2021
7353cd9
more explicit state, minor improvements
Apr 21, 2021
01cf6cb
displaying parsed contents without ids
Apr 21, 2021
ec05bde
remove placeholder manual table of contents data
Apr 21, 2021
de41d7c
fix bugs
Apr 21, 2021
ba1f635
add slug generation and use in generating toc, markdown output not up…
Apr 21, 2021
ff6f422
close, but slugs aren't being used by mdxjs yet
Apr 21, 2021
9f31126
slugs injected into markdokwn body as well, all basic functionality w…
Apr 21, 2021
f527dd4
removing jsconfig.json caused "ModuleNotFoundError: Module not found:…
Apr 21, 2021
d803bc2
revert trivial commit
Apr 21, 2021
b4350aa
remove dead code
Apr 21, 2021
adf56fb
remove debug print
Apr 21, 2021
ce79402
rendering with rehype-stringify, good riddance to bloated mdxjs
Apr 21, 2021
b66f084
remove mdxjs related and cleanup deps
Apr 21, 2021
4396384
move unified bindings into their own module
Apr 21, 2021
facc493
move table of contents extraction plugin into its own module
Apr 21, 2021
a15461f
fix comments, cleanup MarkdownPageBody render
Apr 21, 2021
b2a8339
documentation
Apr 21, 2021
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
48 changes: 48 additions & 0 deletions bindings/Unified.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module MarkdownTableOfContents = {
type rec toc = {
label: string,
id: string,
children: list<toc>,
}

type t = list<toc>
}

type processor

@module("unified") external unified: unit => processor = "default"

type node = {\"type": string, depth: option<int>}

type data = {id: string}

type headingnode = {
depth: int,
data: data,
}

external asHeadingNode: node => headingnode = "%identity"

type rootnode = {children: array<node>}

type vfile = {mutable toc: MarkdownTableOfContents.t, contents: string}

type transformer = (rootnode, vfile) => unit

type attacher = unit => transformer

@send external use: (processor, attacher) => processor = "use"

@send external process: (processor, string) => Js.Promise.t<vfile> = "process"

@module("remark-slug") external remarkSlug: attacher = "default"

@module("remark-parse") external remarkParse: attacher = "default"

@module("remark-rehype") external remark2rehype: attacher = "default"

@module("rehype-stringify") external rehypeStringify: attacher = "default"

module MdastUtilToString = {
@module("mdast-util-to-string") external toString: headingnode => string = "default"
}
48 changes: 48 additions & 0 deletions bindings/Unified.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module MarkdownTableOfContents: {
type rec toc = {
label: string,
id: string,
children: list<toc>,
}

type t = list<toc>
}

type processor

let unified: unit => processor

type node = {\"type": string, depth: option<int>}

type data = {id: string}

type headingnode = {
depth: int,
data: data,
}

let asHeadingNode: node => headingnode

type rootnode = {children: array<node>}

type vfile = {mutable toc: MarkdownTableOfContents.t, contents: string}

type transformer = (rootnode, vfile) => unit

type attacher = unit => transformer

let use: (processor, attacher) => processor

let process: (processor, string) => Js.Promise.t<vfile>

let remarkSlug: attacher

let remarkParse: attacher

let remark2rehype: attacher

let rehypeStringify: attacher

module MdastUtilToString: {
let toString: headingnode => string
}
62 changes: 62 additions & 0 deletions common/MdastToc.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// TODO: define scanleft (https://github.com/reazen/relude/blob/e733128d0df8022448398a44c80cba6f28443b94/src/list/Relude_List_Base.re#L487)
// and use it below

// TODO: factor out the core algorithm from other concerns, and
// write unit tests for the core algorithm.

let transformer = (rootnode: Unified.rootnode, file: Unified.vfile) => {
let rec collect = (nodes, inProgress) => {
switch nodes {
| list{} =>
switch inProgress {
| None => list{}
| Some(_, entry) => list{entry}
}
| list{h: Unified.headingnode, ...tail} =>
let d = h.depth
if d >= 2 || d <= 3 {
let entry = {
Unified.MarkdownTableOfContents.label: Unified.MdastUtilToString.toString(h),
id: h.data.id,
children: list{},
}
switch inProgress {
| None => collect(tail, Some(d, entry))
| Some(lastRootDepth, inProgress) if d <= lastRootDepth => list{
inProgress,
...collect(tail, Some(d, entry)),
}
| Some(lastRootDepth, inProgress) =>
let inProgress = {
...inProgress,
children: Belt.List.concat(inProgress.children, list{entry}),
}
collect(tail, Some(lastRootDepth, inProgress))
}
} else {
// TODO: Should we guard against unusual jumps in depth here?
// No, instead validate the list of headings
// in an earlier pass and produce new type with
// stronger guarantees about heading sequences.
collect(tail, inProgress)
}
}
}
let headings = collect(
Array.to_list(
Belt.Array.keepMap(rootnode.children, ch =>
switch ch {
| {\"type": "heading", depth: Some(_)} => Some(Unified.asHeadingNode(ch))
| _ => None
}
),
),
None,
)

file.toc = headings
}

let plugin = () => {
transformer
}
1 change: 1 addition & 0 deletions common/MdastToc.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let plugin: Unified.attacher
60 changes: 0 additions & 60 deletions common/Mdx.res

This file was deleted.

56 changes: 0 additions & 56 deletions common/Mdx.resi

This file was deleted.

21 changes: 0 additions & 21 deletions common/NextMdxRemote.res

This file was deleted.

14 changes: 0 additions & 14 deletions common/NextMdxRemote.resi

This file was deleted.

44 changes: 17 additions & 27 deletions components/MarkdownPage.res
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,17 @@ let s = React.string

module MarkdownPageBody = {
@react.component
let make = (~margins, ~children) =>
<div className={margins ++ ` prose prose-yellow prose-lg text-gray-500 mx-auto`}>
children
</div>
let make = (~margins, ~renderedMarkdown) =>
<div
className={margins ++ ` prose prose-yellow prose-lg text-gray-500 mx-auto`}
dangerouslySetInnerHTML={{"__html": renderedMarkdown}}
/>
}

module TableOfContents = {
// TODO: define general heading tree type and recursively traverse when rendering
type subHeading = {
subName: string,
subHeadingId: string,
}

type heading = {
name: string,
headingId: string,
subHeadings: array<subHeading>,
}

type t = {
contents: string,
headings: array<heading>,
toc: Unified.MarkdownTableOfContents.t,
}

@react.component
Expand All @@ -32,29 +21,30 @@ module TableOfContents = {
className="hidden lg:sticky lg:self-start lg:top-2 lg:flex lg:flex-col lg:col-span-2 border-r border-gray-200 pt-5 pb-4 overflow-y-auto">
<div className="px-4"> <span className="text-lg"> {s(content.contents)} </span> </div>
<div className="mt-5 ">
// TODO: implement a completely general recursive traversal a toc forest
<nav className="px-2 space-y-1" ariaLabel="Sidebar">
{content.headings
|> Js.Array.mapi((hdg, idx) =>
{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.headingId}
href={"#" ++ hdg.id}
className="block text-gray-600 hover:text-gray-900 pr-2 py-2 text-sm font-medium">
{s(hdg.name)}
{s(hdg.label)}
</a>
{hdg.subHeadings
|> Js.Array.mapi((sub, idx) =>
{hdg.children
->Belt.List.mapWithIndex((idx, sub) =>
<a
href={"#" ++ sub.subHeadingId}
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.subName)}
{s(sub.label)}
</a>
)
|> React.array}
->Belt.List.toArray |> React.array}
</div>
)
|> React.array}
->Belt.List.toArray |> React.array}
</nav>
</div>
</div>
Expand Down
Loading