tableofcontents.go (3742B)
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 tableofcontents 15 16 import ( 17 "strings" 18 ) 19 20 // Headings holds the top level headings. 21 type Headings []Heading 22 23 // Heading holds the data about a heading and its children. 24 type Heading struct { 25 ID string 26 Text string 27 28 Headings Headings 29 } 30 31 // IsZero is true when no ID or Text is set. 32 func (h Heading) IsZero() bool { 33 return h.ID == "" && h.Text == "" 34 } 35 36 // Root implements AddAt, which can be used to build the 37 // data structure for the ToC. 38 type Root struct { 39 Headings Headings 40 } 41 42 // AddAt adds the heading into the given location. 43 func (toc *Root) AddAt(h Heading, row, level int) { 44 for i := len(toc.Headings); i <= row; i++ { 45 toc.Headings = append(toc.Headings, Heading{}) 46 } 47 48 if level == 0 { 49 toc.Headings[row] = h 50 return 51 } 52 53 heading := &toc.Headings[row] 54 55 for i := 1; i < level; i++ { 56 if len(heading.Headings) == 0 { 57 heading.Headings = append(heading.Headings, Heading{}) 58 } 59 heading = &heading.Headings[len(heading.Headings)-1] 60 } 61 heading.Headings = append(heading.Headings, h) 62 } 63 64 // ToHTML renders the ToC as HTML. 65 func (toc Root) ToHTML(startLevel, stopLevel int, ordered bool) string { 66 b := &tocBuilder{ 67 s: strings.Builder{}, 68 h: toc.Headings, 69 startLevel: startLevel, 70 stopLevel: stopLevel, 71 ordered: ordered, 72 } 73 b.Build() 74 return b.s.String() 75 } 76 77 type tocBuilder struct { 78 s strings.Builder 79 h Headings 80 81 startLevel int 82 stopLevel int 83 ordered bool 84 } 85 86 func (b *tocBuilder) Build() { 87 b.writeNav(b.h) 88 } 89 90 func (b *tocBuilder) writeNav(h Headings) { 91 b.s.WriteString("<nav id=\"TableOfContents\">") 92 b.writeHeadings(1, 0, b.h) 93 b.s.WriteString("</nav>") 94 } 95 96 func (b *tocBuilder) writeHeadings(level, indent int, h Headings) { 97 if level < b.startLevel { 98 for _, h := range h { 99 b.writeHeadings(level+1, indent, h.Headings) 100 } 101 return 102 } 103 104 if b.stopLevel != -1 && level > b.stopLevel { 105 return 106 } 107 108 hasChildren := len(h) > 0 109 110 if hasChildren { 111 b.s.WriteString("\n") 112 b.indent(indent + 1) 113 if b.ordered { 114 b.s.WriteString("<ol>\n") 115 } else { 116 b.s.WriteString("<ul>\n") 117 } 118 } 119 120 for _, h := range h { 121 b.writeHeading(level+1, indent+2, h) 122 } 123 124 if hasChildren { 125 b.indent(indent + 1) 126 if b.ordered { 127 b.s.WriteString("</ol>") 128 } else { 129 b.s.WriteString("</ul>") 130 } 131 b.s.WriteString("\n") 132 b.indent(indent) 133 } 134 } 135 136 func (b *tocBuilder) writeHeading(level, indent int, h Heading) { 137 b.indent(indent) 138 b.s.WriteString("<li>") 139 if !h.IsZero() { 140 b.s.WriteString("<a href=\"#" + h.ID + "\">" + h.Text + "</a>") 141 } 142 b.writeHeadings(level, indent, h.Headings) 143 b.s.WriteString("</li>\n") 144 } 145 146 func (b *tocBuilder) indent(n int) { 147 for i := 0; i < n; i++ { 148 b.s.WriteString(" ") 149 } 150 } 151 152 // DefaultConfig is the default ToC configuration. 153 var DefaultConfig = Config{ 154 StartLevel: 2, 155 EndLevel: 3, 156 Ordered: false, 157 } 158 159 type Config struct { 160 // Heading start level to include in the table of contents, starting 161 // at h1 (inclusive). 162 StartLevel int 163 164 // Heading end level, inclusive, to include in the table of contents. 165 // Default is 3, a value of -1 will include everything. 166 EndLevel int 167 168 // Whether to produce a ordered list or not. 169 Ordered bool 170 }