mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
Merge pull request #302 from russross/v2-move-footnotest-to-html
v2: move footnotes to html
This commit is contained in:
commit
b91b5719eb
13
block.go
13
block.go
|
@ -1380,7 +1380,10 @@ func (p *parser) paragraph(data []byte) int {
|
|||
// line: index of 1st char of current line
|
||||
// i: index of cursor/end of current line
|
||||
var prev, line, i int
|
||||
|
||||
tabSize := TabSizeDefault
|
||||
if p.flags&TabSizeEight != 0 {
|
||||
tabSize = TabSizeDouble
|
||||
}
|
||||
// keep going until we find something to mark the end of the paragraph
|
||||
for i < len(data) {
|
||||
// mark the beginning of the current line
|
||||
|
@ -1388,6 +1391,14 @@ func (p *parser) paragraph(data []byte) int {
|
|||
current := data[i:]
|
||||
line = i
|
||||
|
||||
// did we find a reference or a footnote? If so, end a paragraph
|
||||
// preceding it and report that we have consumed up to the end of that
|
||||
// reference:
|
||||
if refEnd := isReference(p, current, tabSize); refEnd > 0 {
|
||||
p.renderParagraph(data[:i])
|
||||
return i + refEnd
|
||||
}
|
||||
|
||||
// did we find a blank line marking the end of the paragraph?
|
||||
if n := p.isEmpty(current); n > 0 {
|
||||
// did this blank line followed by a definition list item?
|
||||
|
|
12
html.go
12
html.go
|
@ -589,10 +589,11 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
|||
tagName = "dl"
|
||||
}
|
||||
if entering {
|
||||
// var start = node.listStart;
|
||||
// if (start !== null && start !== 1) {
|
||||
// attrs.push(['start', start.toString()]);
|
||||
// }
|
||||
if node.IsFootnotesList {
|
||||
r.out(w, []byte("\n<div class=\"footnotes\">\n\n"))
|
||||
r.out(w, tag("hr", attrs, r.Flags&UseXHTML != 0))
|
||||
r.cr(w)
|
||||
}
|
||||
r.cr(w)
|
||||
if node.Parent.Type == Item && node.Parent.Parent.Tight {
|
||||
r.cr(w)
|
||||
|
@ -611,6 +612,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
|||
if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
|
||||
r.cr(w)
|
||||
}
|
||||
if node.IsFootnotesList {
|
||||
r.out(w, []byte("\n</div>\n"))
|
||||
}
|
||||
}
|
||||
case Item:
|
||||
tagName := "li"
|
||||
|
|
|
@ -922,6 +922,12 @@ what happens here
|
|||
|
||||
</div>
|
||||
`,
|
||||
|
||||
`This text does not reference a footnote.
|
||||
|
||||
[^footnote]: But it has a footnote! And it gets omitted.
|
||||
`,
|
||||
"<p>This text does not reference a footnote.</p>\n",
|
||||
}
|
||||
|
||||
func TestFootnotes(t *testing.T) {
|
||||
|
|
89
markdown.go
89
markdown.go
|
@ -75,7 +75,7 @@ const (
|
|||
ListTypeTerm
|
||||
|
||||
ListItemContainsBlock
|
||||
ListItemBeginningOfList
|
||||
ListItemBeginningOfList // TODO: figure out if this is of any use now
|
||||
ListItemEndOfList
|
||||
)
|
||||
|
||||
|
@ -387,8 +387,7 @@ func Parse(input []byte, opts Options) *Node {
|
|||
p.notes = make([]*reference, 0)
|
||||
}
|
||||
|
||||
first := firstPass(p, input)
|
||||
secondPass(p, first)
|
||||
p.block(preprocess(p, input))
|
||||
// Walk the tree and finish up some of unfinished blocks
|
||||
for p.tip != nil {
|
||||
p.finalize(p.tip)
|
||||
|
@ -410,9 +409,8 @@ func (p *parser) parseRefsToAST() {
|
|||
return
|
||||
}
|
||||
p.tip = p.doc
|
||||
finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte(`<div class="footnotes">`)))
|
||||
p.addBlock(HorizontalRule, nil)
|
||||
block := p.addBlock(List, nil)
|
||||
block.IsFootnotesList = true
|
||||
block.ListFlags = ListTypeOrdered
|
||||
flags := ListItemBeginningOfList
|
||||
// Note: this loop is intentionally explicit, not range-form. This is
|
||||
|
@ -422,7 +420,7 @@ func (p *parser) parseRefsToAST() {
|
|||
for i := 0; i < len(p.notes); i++ {
|
||||
ref := p.notes[i]
|
||||
block := p.addBlock(Item, nil)
|
||||
block.ListFlags = ListTypeOrdered
|
||||
block.ListFlags = flags | ListTypeOrdered
|
||||
block.RefLink = ref.link
|
||||
if ref.hasBlock {
|
||||
flags |= ListItemContainsBlock
|
||||
|
@ -435,7 +433,6 @@ func (p *parser) parseRefsToAST() {
|
|||
above := block.Parent
|
||||
finalizeList(block)
|
||||
p.tip = above
|
||||
finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte("</div>")))
|
||||
block.Walk(func(node *Node, entering bool) WalkStatus {
|
||||
if node.Type == Paragraph || node.Type == Header {
|
||||
p.inline(node, node.content)
|
||||
|
@ -445,12 +442,11 @@ func (p *parser) parseRefsToAST() {
|
|||
})
|
||||
}
|
||||
|
||||
// first pass:
|
||||
// preprocess does a preparatory first pass over the input:
|
||||
// - normalize newlines
|
||||
// - extract references (outside of fenced code blocks)
|
||||
// - expand tabs (outside of fenced code blocks)
|
||||
// - copy everything else
|
||||
func firstPass(p *parser, input []byte) []byte {
|
||||
func preprocess(p *parser, input []byte) []byte {
|
||||
var out bytes.Buffer
|
||||
tabSize := TabSizeDefault
|
||||
if p.flags&TabSizeEight != 0 {
|
||||
|
@ -479,9 +475,6 @@ func firstPass(p *parser, input []byte) []byte {
|
|||
if end > beg {
|
||||
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
|
||||
out.Write(input[beg:end])
|
||||
} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
|
||||
beg += refEnd
|
||||
continue
|
||||
} else {
|
||||
expandTabs(&out, input[beg:end], tabSize)
|
||||
}
|
||||
|
@ -506,29 +499,6 @@ func firstPass(p *parser, input []byte) []byte {
|
|||
return out.Bytes()
|
||||
}
|
||||
|
||||
// second pass: actual rendering
|
||||
func secondPass(p *parser, input []byte) {
|
||||
p.block(input)
|
||||
|
||||
if p.flags&Footnotes != 0 && len(p.notes) > 0 {
|
||||
flags := ListItemBeginningOfList
|
||||
for i := 0; i < len(p.notes); i++ {
|
||||
ref := p.notes[i]
|
||||
if ref.hasBlock {
|
||||
flags |= ListItemContainsBlock
|
||||
p.block(ref.title)
|
||||
} else {
|
||||
p.inline(nil, ref.title)
|
||||
}
|
||||
flags &^= ListItemBeginningOfList | ListItemContainsBlock
|
||||
}
|
||||
}
|
||||
|
||||
if p.nesting != 0 {
|
||||
panic("Nesting level did not end at zero")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Link references
|
||||
//
|
||||
|
@ -558,13 +528,50 @@ func secondPass(p *parser, input []byte) {
|
|||
//
|
||||
// are not yet supported.
|
||||
|
||||
// References are parsed and stored in this struct.
|
||||
// reference holds all information necessary for a reference-style links or
|
||||
// footnotes.
|
||||
//
|
||||
// Consider this markdown with reference-style links:
|
||||
//
|
||||
// [link][ref]
|
||||
//
|
||||
// [ref]: /url/ "tooltip title"
|
||||
//
|
||||
// It will be ultimately converted to this HTML:
|
||||
//
|
||||
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||
//
|
||||
// And a reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["ref"] = &reference{
|
||||
// link: "/url/",
|
||||
// title: "tooltip title",
|
||||
// }
|
||||
//
|
||||
// Alternatively, reference can contain information about a footnote. Consider
|
||||
// this markdown:
|
||||
//
|
||||
// Text needing a footnote.[^a]
|
||||
//
|
||||
// [^a]: This is the note
|
||||
//
|
||||
// A reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["a"] = &reference{
|
||||
// link: "a",
|
||||
// title: "This is the note",
|
||||
// noteID: <some positive int>,
|
||||
// }
|
||||
//
|
||||
// TODO: As you can see, it begs for splitting into two dedicated structures
|
||||
// for refs and for footnotes.
|
||||
type reference struct {
|
||||
link []byte
|
||||
title []byte
|
||||
noteID int // 0 if not a footnote ref
|
||||
hasBlock bool
|
||||
text []byte
|
||||
|
||||
text []byte // only gets populated by refOverride feature with Reference.Text
|
||||
}
|
||||
|
||||
func (r *reference) String() string {
|
||||
|
@ -610,7 +617,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
|||
return 0
|
||||
}
|
||||
idEnd := i
|
||||
|
||||
// footnotes can have empty ID, like this: [^], but a reference can not be
|
||||
// empty like this: []. Break early if it's not a footnote and there's no ID
|
||||
if noteID == 0 && idOffset == idEnd {
|
||||
return 0
|
||||
}
|
||||
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||
i++
|
||||
if i >= len(data) || data[i] != ':' {
|
||||
|
|
19
node.go
19
node.go
|
@ -69,20 +69,21 @@ func (t NodeType) String() string {
|
|||
return nodeTypeNames[t]
|
||||
}
|
||||
|
||||
// ListData contains fields relevant to a List node type.
|
||||
// ListData contains fields relevant to a List and Item node type.
|
||||
type ListData struct {
|
||||
ListFlags ListType
|
||||
Tight bool // Skip <p>s around list item data if true
|
||||
BulletChar byte // '*', '+' or '-' in bullet lists
|
||||
Delimiter byte // '.' or ')' after the number in ordered lists
|
||||
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
|
||||
ListFlags ListType
|
||||
Tight bool // Skip <p>s around list item data if true
|
||||
BulletChar byte // '*', '+' or '-' in bullet lists
|
||||
Delimiter byte // '.' or ')' after the number in ordered lists
|
||||
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
|
||||
IsFootnotesList bool // This is a list of footnotes
|
||||
}
|
||||
|
||||
// LinkData contains fields relevant to a Link node type.
|
||||
type LinkData struct {
|
||||
Destination []byte
|
||||
Title []byte
|
||||
NoteID int
|
||||
Destination []byte // Destination is what goes into a href
|
||||
Title []byte // Title is the tooltip thing that goes in a title attribute
|
||||
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
|
||||
}
|
||||
|
||||
// CodeBlockData contains fields relevant to a CodeBlock node type.
|
||||
|
|
Loading…
Reference in New Issue
Block a user