1
0
mirror of https://github.com/danog/blackfriday.git synced 2024-11-26 20:14:43 +01:00

Merge pull request #294 from Ambrevar/v2TOC2Renderer

v2: Move TOC generation to the HTML Renderer
This commit is contained in:
Vytautas Šaltenis 2016-08-09 22:06:04 +03:00 committed by GitHub
commit c9f76b530b
3 changed files with 124 additions and 64 deletions

View File

@ -1530,6 +1530,71 @@ func TestTOC(t *testing.T) {
<h2 id="toc_1">Subtitle</h2> <h2 id="toc_1">Subtitle</h2>
<h1 id="toc_2">Title2</h1> <h1 id="toc_2">Title2</h1>
`,
"## Subtitle\n\n# Title",
`<nav>
<ul>
<li>
<ul>
<li><a href="#toc_0">Subtitle</a></li>
</ul></li>
<li><a href="#toc_1">Title</a></li>
</ul>
</nav>
<h2 id="toc_0">Subtitle</h2>
<h1 id="toc_1">Title</h1>
`,
"# Title 1\n\n## Subtitle 1\n\n### Subsubtitle 1\n\n# Title 2\n\n### Subsubtitle 2",
`<nav>
<ul>
<li><a href="#toc_0">Title 1</a>
<ul>
<li><a href="#toc_1">Subtitle 1</a>
<ul>
<li><a href="#toc_2">Subsubtitle 1</a></li>
</ul></li>
</ul></li>
<li><a href="#toc_3">Title 2</a>
<ul>
<li>
<ul>
<li><a href="#toc_4">Subsubtitle 2</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="toc_0">Title 1</h1>
<h2 id="toc_1">Subtitle 1</h2>
<h3 id="toc_2">Subsubtitle 1</h3>
<h1 id="toc_3">Title 2</h1>
<h3 id="toc_4">Subsubtitle 2</h3>
`,
"# Title with `code`",
`<nav>
<ul>
<li><a href="#toc_0">Title with <code>code</code></a></li>
</ul>
</nav>
<h1 id="toc_0">Title with <code>code</code></h1>
`, `,
// Trigger empty TOC // Trigger empty TOC

59
html.go
View File

@ -745,6 +745,59 @@ func (r *HTMLRenderer) writeDocumentHeader(w *bytes.Buffer, sr *SPRenderer) {
w.WriteString("<body>\n\n") w.WriteString("<body>\n\n")
} }
func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
buf := bytes.Buffer{}
inHeader := false
tocLevel := 0
headerCount := 0
ast.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Header && !node.HeaderData.IsTitleblock {
inHeader = entering
if entering {
node.HeaderID = fmt.Sprintf("toc_%d", headerCount)
if node.Level == tocLevel {
buf.WriteString("</li>\n\n<li>")
} else if node.Level < tocLevel {
for node.Level < tocLevel {
tocLevel--
buf.WriteString("</li>\n</ul>")
}
buf.WriteString("</li>\n\n<li>")
} else {
for node.Level > tocLevel {
tocLevel++
buf.WriteString("\n<ul>\n<li>")
}
}
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headerCount)
headerCount++
} else {
buf.WriteString("</a>")
}
return GoToNext
}
if inHeader {
return r.RenderNode(&buf, node, entering)
}
return GoToNext
})
for ; tocLevel > 0; tocLevel-- {
buf.WriteString("</li>\n</ul>")
}
if buf.Len() > 0 {
w.WriteString("<nav>\n")
w.Write(buf.Bytes())
w.WriteString("\n\n</nav>\n")
}
}
func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) { func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) {
if r.Flags&CompletePage == 0 { if r.Flags&CompletePage == 0 {
return return
@ -770,6 +823,12 @@ func (r *HTMLRenderer) Render(ast *Node) []byte {
}) })
var buff bytes.Buffer var buff bytes.Buffer
r.writeDocumentHeader(&buff, sr) r.writeDocumentHeader(&buff, sr)
if r.Extensions&TOC != 0 || r.Extensions&OmitContents != 0 {
r.writeTOC(&buff, ast)
if r.Extensions&OmitContents != 0 {
return buff.Bytes()
}
}
ast.Walk(func(node *Node, entering bool) WalkStatus { ast.Walk(func(node *Node, entering bool) WalkStatus {
return r.RenderNode(&buff, node, entering) return r.RenderNode(&buff, node, entering)
}) })

View File

@ -415,73 +415,9 @@ func Parse(input []byte, opts Options) *Node {
return GoToNext return GoToNext
}) })
p.parseRefsToAST() p.parseRefsToAST()
p.generateTOC()
return p.doc return p.doc
} }
func (p *parser) generateTOC() {
if p.flags&TOC == 0 && p.flags&OmitContents == 0 {
return
}
navNode := NewNode(HTMLBlock)
navNode.Literal = []byte("<nav>")
navNode.open = false
var topList *Node
var listNode *Node
var lastItem *Node
headerCount := 0
currentLevel := 0
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
if entering && node.Type == Header {
if node.Level > currentLevel {
currentLevel++
newList := NewNode(List)
if lastItem != nil {
lastItem.appendChild(newList)
listNode = newList
} else {
listNode = newList
topList = listNode
}
}
if node.Level < currentLevel {
finalizeList(listNode)
lastItem = listNode.Parent
listNode = lastItem.Parent
}
node.HeaderID = fmt.Sprintf("toc_%d", headerCount)
headerCount++
lastItem = NewNode(Item)
listNode.appendChild(lastItem)
anchorNode := NewNode(Link)
anchorNode.Destination = []byte("#" + node.HeaderID)
lastItem.appendChild(anchorNode)
anchorNode.appendChild(text(node.FirstChild.Literal))
}
return GoToNext
})
firstChild := p.doc.FirstChild
// Insert TOC only if there is anything to insert
if topList != nil {
finalizeList(topList)
firstChild.insertBefore(navNode)
firstChild.insertBefore(topList)
navCloseNode := NewNode(HTMLBlock)
navCloseNode.Literal = []byte("</nav>")
navCloseNode.open = false
firstChild.insertBefore(navCloseNode)
}
// Drop everything after the TOC if OmitContents was requested
if p.flags&OmitContents != 0 {
for firstChild != nil {
next := firstChild.Next
firstChild.unlink()
firstChild = next
}
}
}
func (p *parser) parseRefsToAST() { func (p *parser) parseRefsToAST() {
if p.flags&Footnotes == 0 || len(p.notes) == 0 { if p.flags&Footnotes == 0 || len(p.notes) == 0 {
return return