hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }