- commit
- c4b0a8a
- parent
- 02808fb
- author
- ChloƩ Vulquin
- date
- 2024-03-21 22:07:41 +0000 UTC
md/toc: make it nice, flexible, and fast
1 files changed,
+64,
-18
1@@ -80,6 +80,28 @@ func toBool(obj interface{}) (bool, error) {
2 }
3 }
4
5+// N = maxdepth - 1
6+// -1: disable, 0: enable (no max), 1: enable (only top-level ##)
7+func toToc(obj interface{}) (int, error) {
8+ if obj == nil {
9+ return -1, nil
10+ }
11+ switch val := obj.(type) {
12+ case bool:
13+ if val {
14+ return 0, nil
15+ }
16+ return -1, nil
17+ case int:
18+ if val < -1 {
19+ val = -1
20+ }
21+ return val, nil
22+ default:
23+ return -1, fmt.Errorf("incorrect type for value: %T, should be bool or int", val)
24+ }
25+}
26+
27 func toLinks(orderedMetaData yaml.MapSlice) ([]Link, error) {
28 var navData interface{}
29 for i := 0; i < len(orderedMetaData); i++ {
30@@ -216,24 +238,6 @@ func ParseText(text string) (*ParsedText, error) {
31 doc := md.Parser().Parse(gtext.NewReader(btext), parser.WithContext(context))
32 metaData := meta.Get(context)
33
34- showToc, err := toBool(metaData["toc"])
35- if err != nil {
36- return &parsed, fmt.Errorf("front-matter field (%s): %w", "toc", err)
37- }
38- // we need to add an extension after parsing frontmatter
39- if showToc {
40- extenders = append(extenders, &toc.Extender{
41- Title: "toc",
42- TitleDepth: 2,
43- Compact: true,
44- TitleID: "toc",
45- ListID: "toc-list",
46- })
47- md = CreateGoldmark(extenders...)
48- context := parser.NewContext()
49- doc = md.Parser().Parse(gtext.NewReader(btext), parser.WithContext(context))
50- }
51-
52 // title:
53 // 1. if specified in frontmatter, use that
54 title, err := toString(metaData["title"])
55@@ -248,6 +252,15 @@ func ParseText(text string) (*ParsedText, error) {
56 // this is implicit since it's already ""
57 parsed.MetaData.Title = title
58
59+ // only handle toc after the title is extracted (if it's getting extracted)
60+ mtoc, err := toToc(metaData["toc"])
61+ if err != nil {
62+ return &parsed, fmt.Errorf("front-matter field (%s): %w", "toc", err)
63+ }
64+ if mtoc >= 0 {
65+ AstToc(doc, btext, mtoc)
66+ }
67+
68 description, err := toString(metaData["description"])
69 if err != nil {
70 return &parsed, fmt.Errorf("front-matter field (%s): %w", "description", err)
71@@ -400,3 +413,36 @@ func AstTitle(doc ast.Node, src []byte, clean bool) string {
72 }
73 return out
74 }
75+
76+func AstToc(doc ast.Node, src []byte, mtoc int) {
77+ var tree *toc.TOC
78+ if mtoc >= 0 {
79+ var err error
80+ if mtoc > 0 {
81+ tree, err = toc.Inspect(doc, src, toc.Compact(true), toc.MinDepth(2), toc.MaxDepth(mtoc + 1))
82+ } else {
83+ tree, err = toc.Inspect(doc, src, toc.Compact(true), toc.MinDepth(2))
84+ }
85+ if err != nil {
86+ return // TODO: further processing?
87+ }
88+ if tree == nil {
89+ return
90+ }
91+ }
92+ list := toc.RenderList(tree)
93+ if list == nil {
94+ return // no headings
95+ }
96+
97+ list.SetAttributeString("id", []byte("toc-list"))
98+
99+ // generate # toc
100+ heading := ast.NewHeading(2)
101+ heading.SetAttributeString("id", []byte("toc"))
102+ heading.AppendChild(heading, ast.NewString([]byte("toc")))
103+
104+ // insert
105+ doc.InsertBefore(doc, doc.FirstChild(), list)
106+ doc.InsertBefore(doc, doc.FirstChild(), heading)
107+}