toc.go (2919B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package goldmark 15 16 import ( 17 "bytes" 18 19 "github.com/gohugoio/hugo/markup/tableofcontents" 20 21 "github.com/yuin/goldmark" 22 "github.com/yuin/goldmark/ast" 23 "github.com/yuin/goldmark/parser" 24 "github.com/yuin/goldmark/renderer" 25 "github.com/yuin/goldmark/text" 26 "github.com/yuin/goldmark/util" 27 ) 28 29 var ( 30 tocResultKey = parser.NewContextKey() 31 tocEnableKey = parser.NewContextKey() 32 ) 33 34 type tocTransformer struct { 35 r renderer.Renderer 36 } 37 38 func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parser.Context) { 39 if b, ok := pc.Get(tocEnableKey).(bool); !ok || !b { 40 return 41 } 42 43 var ( 44 toc tableofcontents.Root 45 tocHeading tableofcontents.Heading 46 level int 47 row = -1 48 inHeading bool 49 headingText bytes.Buffer 50 ) 51 52 ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 53 s := ast.WalkStatus(ast.WalkContinue) 54 if n.Kind() == ast.KindHeading { 55 if inHeading && !entering { 56 tocHeading.Text = headingText.String() 57 headingText.Reset() 58 toc.AddAt(tocHeading, row, level-1) 59 tocHeading = tableofcontents.Heading{} 60 inHeading = false 61 return s, nil 62 } 63 64 inHeading = true 65 } 66 67 if !(inHeading && entering) { 68 return s, nil 69 } 70 71 switch n.Kind() { 72 case ast.KindHeading: 73 heading := n.(*ast.Heading) 74 level = heading.Level 75 76 if level == 1 || row == -1 { 77 row++ 78 } 79 80 id, found := heading.AttributeString("id") 81 if found { 82 tocHeading.ID = string(id.([]byte)) 83 } 84 case 85 ast.KindCodeSpan, 86 ast.KindLink, 87 ast.KindImage, 88 ast.KindEmphasis: 89 err := t.r.Render(&headingText, reader.Source(), n) 90 if err != nil { 91 return s, err 92 } 93 94 return ast.WalkSkipChildren, nil 95 case 96 ast.KindAutoLink, 97 ast.KindRawHTML, 98 ast.KindText, 99 ast.KindString: 100 err := t.r.Render(&headingText, reader.Source(), n) 101 if err != nil { 102 return s, err 103 } 104 } 105 106 return s, nil 107 }) 108 109 pc.Set(tocResultKey, toc) 110 } 111 112 type tocExtension struct { 113 options []renderer.Option 114 } 115 116 func newTocExtension(options []renderer.Option) goldmark.Extender { 117 return &tocExtension{ 118 options: options, 119 } 120 } 121 122 func (e *tocExtension) Extend(m goldmark.Markdown) { 123 r := goldmark.DefaultRenderer() 124 r.AddOptions(e.options...) 125 m.Parser().AddOptions(parser.WithASTTransformers(util.Prioritized(&tocTransformer{ 126 r: r, 127 }, 10))) 128 }