github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/markup/tableofcontents/tableofcontents.go (about)

     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  }