2023-06-10 13:06:02 +07:00
|
|
|
import { QuartzTransformerPlugin } from "../types"
|
|
|
|
import { Root } from "mdast"
|
|
|
|
import { visit } from "unist-util-visit"
|
|
|
|
import { toString } from "mdast-util-to-string"
|
2023-08-23 12:41:50 +07:00
|
|
|
import Slugger from "github-slugger"
|
2023-11-16 11:13:28 +07:00
|
|
|
import { wikilinkRegex } from "./ofm"
|
2023-06-10 13:06:02 +07:00
|
|
|
|
|
|
|
export interface Options {
|
2023-07-23 07:27:41 +07:00
|
|
|
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
|
|
|
minEntries: 1
|
2023-06-10 13:06:02 +07:00
|
|
|
showByDefault: boolean
|
2023-11-05 02:11:42 +07:00
|
|
|
collapseByDefault: boolean
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
const defaultOptions: Options = {
|
|
|
|
maxDepth: 3,
|
|
|
|
minEntries: 1,
|
|
|
|
showByDefault: true,
|
2023-11-05 02:11:42 +07:00
|
|
|
collapseByDefault: false,
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
interface TocEntry {
|
2023-07-23 07:27:41 +07:00
|
|
|
depth: number
|
|
|
|
text: string
|
2023-07-16 13:02:12 +07:00
|
|
|
slug: string // this is just the anchor (#some-slug), not the canonical slug
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
|
2023-11-16 11:13:28 +07:00
|
|
|
const regexMdLinks = new RegExp(/\[([^\[]+)\](\(.*\))/, "g")
|
2023-07-23 07:27:41 +07:00
|
|
|
export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
|
|
|
userOpts,
|
|
|
|
) => {
|
2023-06-12 13:26:43 +07:00
|
|
|
const opts = { ...defaultOptions, ...userOpts }
|
|
|
|
return {
|
|
|
|
name: "TableOfContents",
|
|
|
|
markdownPlugins() {
|
2023-07-23 07:27:41 +07:00
|
|
|
return [
|
|
|
|
() => {
|
|
|
|
return async (tree: Root, file) => {
|
|
|
|
const display = file.data.frontmatter?.enableToc ?? opts.showByDefault
|
|
|
|
if (display) {
|
2023-08-23 12:41:50 +07:00
|
|
|
const slugAnchor = new Slugger()
|
2023-07-23 07:27:41 +07:00
|
|
|
const toc: TocEntry[] = []
|
|
|
|
let highestDepth: number = opts.maxDepth
|
|
|
|
visit(tree, "heading", (node) => {
|
|
|
|
if (node.depth <= opts.maxDepth) {
|
2023-11-16 11:13:28 +07:00
|
|
|
let text = toString(node)
|
|
|
|
|
|
|
|
// strip link formatting from toc entries
|
|
|
|
text = text.replace(wikilinkRegex, (_, rawFp, __, rawAlias) => {
|
|
|
|
const fp = rawFp?.trim() ?? ""
|
|
|
|
const alias = rawAlias?.slice(1).trim()
|
|
|
|
return alias ?? fp
|
|
|
|
})
|
|
|
|
text = text.replace(regexMdLinks, "$1")
|
|
|
|
|
2023-07-23 07:27:41 +07:00
|
|
|
highestDepth = Math.min(highestDepth, node.depth)
|
|
|
|
toc.push({
|
|
|
|
depth: node.depth,
|
|
|
|
text,
|
2023-08-23 12:41:50 +07:00
|
|
|
slug: slugAnchor.slug(text),
|
2023-07-23 07:27:41 +07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2023-06-10 13:06:02 +07:00
|
|
|
|
2023-07-23 07:27:41 +07:00
|
|
|
if (toc.length > opts.minEntries) {
|
|
|
|
file.data.toc = toc.map((entry) => ({
|
|
|
|
...entry,
|
|
|
|
depth: entry.depth - highestDepth,
|
|
|
|
}))
|
2023-11-05 02:11:42 +07:00
|
|
|
file.data.collapseToc = opts.collapseByDefault
|
2023-07-23 07:27:41 +07:00
|
|
|
}
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
}
|
2023-07-23 07:27:41 +07:00
|
|
|
},
|
|
|
|
]
|
2023-06-12 13:26:43 +07:00
|
|
|
},
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-23 07:27:41 +07:00
|
|
|
declare module "vfile" {
|
2023-06-10 13:06:02 +07:00
|
|
|
interface DataMap {
|
|
|
|
toc: TocEntry[]
|
2023-11-05 02:11:42 +07:00
|
|
|
collapseToc: boolean
|
2023-06-10 13:06:02 +07:00
|
|
|
}
|
|
|
|
}
|