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:
commit
c9f76b530b
@ -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
59
html.go
@ -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)
|
||||||
})
|
})
|
||||||
|
64
markdown.go
64
markdown.go
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user