Support captions
This adds Mmark support for captions under quotes block or fenced code blocks. This can be used by adding: `Caption: <text>` under the block. All text up to the next empty line is read. This adds ast.Caption, which is a noop in the html/renderer because the elements need to walk this node outside of the main ast. The node can only hold span elements (p.inline()). Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
parent
4a5b716e6a
commit
77587eb931
|
@ -276,11 +276,13 @@ implements the following extensions:
|
|||
```
|
||||
Will convert into `<h1 id="id3" class="myclass" fontsize="tiny">Header 1</h1>`.
|
||||
|
||||
* **Mmark Special Heading**. A heading stating with `#.`, this makes it a special header. This can
|
||||
* **Mmark special heading**. A heading stating with `#.`, this makes it a special header. This can
|
||||
be used to typeset Abstract or Prefaces.
|
||||
|
||||
* **Mmark document divisions**, allow front-, main- or backmatter.
|
||||
|
||||
* **Mmark captions**, allow captions under code blocks and block quotes, by using `Caption: <text>`
|
||||
|
||||
## Todo
|
||||
|
||||
* port https://github.com/russross/blackfriday/issues/348
|
||||
|
|
|
@ -146,6 +146,8 @@ type DocumentMatter struct {
|
|||
// BlockQuote represents markdown block quote node
|
||||
type BlockQuote struct {
|
||||
Container
|
||||
|
||||
Caption Node // If enabled (MmarkCaption) holds the caption.
|
||||
}
|
||||
|
||||
// Aside represents an markdown aside node.
|
||||
|
@ -261,7 +263,7 @@ type CodeBlock struct {
|
|||
FenceLength int
|
||||
FenceOffset int
|
||||
|
||||
Caption []byte // If enabled (MmarkCaption) holds the caption.
|
||||
Caption Node // If enabled (MmarkCaption) holds the caption.
|
||||
}
|
||||
|
||||
// Softbreak represents markdown softbreak node
|
||||
|
@ -313,6 +315,11 @@ type TableRow struct {
|
|||
Container
|
||||
}
|
||||
|
||||
// Caption represents a figure, code or quote caption
|
||||
type Caption struct {
|
||||
Container
|
||||
}
|
||||
|
||||
func removeNodeFromArray(a []Node, node Node) []Node {
|
||||
n := len(a)
|
||||
for i := 0; i < n; i++ {
|
||||
|
|
|
@ -761,20 +761,58 @@ func (r *Renderer) listItem(w io.Writer, listItem *ast.ListItem, entering bool)
|
|||
}
|
||||
|
||||
func (r *Renderer) codeBlock(w io.Writer, codeBlock *ast.CodeBlock) {
|
||||
r.cr(w)
|
||||
var attrs []string
|
||||
|
||||
// if a caption has been given, wrap in <figure> and add <figcaption>.
|
||||
if codeBlock.Caption != nil {
|
||||
r.outs(w, "<figure>")
|
||||
}
|
||||
attrs = appendLanguageAttr(attrs, codeBlock.Info)
|
||||
attrs = append(attrs, blockAttrs(codeBlock)...)
|
||||
r.cr(w)
|
||||
r.outs(w, "<pre>")
|
||||
r.outTag(w, "<code", attrs)
|
||||
EscapeHTML(w, codeBlock.Literal)
|
||||
r.outs(w, "</code>")
|
||||
r.outs(w, "</pre>")
|
||||
|
||||
if codeBlock.Caption != nil {
|
||||
r.outs(w, "<figcaption>")
|
||||
ast.WalkFunc(codeBlock.Caption, func(node ast.Node, entering bool) ast.WalkStatus {
|
||||
return r.RenderNode(w, node, entering)
|
||||
})
|
||||
r.outs(w, "</figcaption></figure>")
|
||||
}
|
||||
if !isListItem(codeBlock.Parent) {
|
||||
r.cr(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) blockQuote(w io.Writer, quote *ast.BlockQuote, entering bool) {
|
||||
if entering {
|
||||
// if a caption has been given, wrap in <figure> and add <figcaption>.
|
||||
if quote.Caption != nil {
|
||||
r.outs(w, "<figure>")
|
||||
}
|
||||
tag := tagWithAttributes("<blockquote", blockAttrs(quote))
|
||||
|
||||
r.cr(w)
|
||||
r.outs(w, tag)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
r.outs(w, "</blockquote>")
|
||||
if quote.Caption != nil {
|
||||
r.outs(w, "<figcaption>")
|
||||
ast.WalkFunc(quote.Caption, func(node ast.Node, entering bool) ast.WalkStatus {
|
||||
return r.RenderNode(w, node, entering)
|
||||
})
|
||||
r.outs(w, "</figcaption></figure>")
|
||||
}
|
||||
r.cr(w)
|
||||
}
|
||||
|
||||
func (r *Renderer) tableCell(w io.Writer, tableCell *ast.TableCell, entering bool) {
|
||||
if !entering {
|
||||
r.outOneOf(w, tableCell.IsHeader, "</th>", "</td>")
|
||||
|
@ -853,8 +891,7 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal
|
|||
case *ast.Del:
|
||||
r.outOneOf(w, entering, "<del>", "</del>")
|
||||
case *ast.BlockQuote:
|
||||
tag := tagWithAttributes("<blockquote", blockAttrs(node))
|
||||
r.outOneOfCr(w, entering, tag, "</blockquote>")
|
||||
r.blockQuote(w, node, entering)
|
||||
case *ast.Aside:
|
||||
tag := tagWithAttributes("<aside", blockAttrs(node))
|
||||
r.outOneOfCr(w, entering, tag, "</aside>")
|
||||
|
@ -871,6 +908,8 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal
|
|||
r.codeBlock(w, node)
|
||||
case *ast.Document:
|
||||
// do nothing
|
||||
case *ast.Caption:
|
||||
// do nothing
|
||||
case *ast.Paragraph:
|
||||
r.paragraph(w, node, entering)
|
||||
case *ast.HTMLSpan:
|
||||
|
|
|
@ -22,7 +22,7 @@ func TestMmark(t *testing.T) {
|
|||
t.Fatalf("odd test tuples: %d", len(testdata))
|
||||
}
|
||||
|
||||
ext := parser.CommonExtensions | parser.Attributes | parser.OrderedListStart | parser.MmarkSpecialHeading | parser.MmarkMatters
|
||||
ext := parser.CommonExtensions | parser.Attributes | parser.OrderedListStart | parser.MmarkSpecialHeading | parser.MmarkMatters | parser.MmarkCaptions
|
||||
for i := 0; i < len(testdata); i += 2 {
|
||||
p := parser.NewWithExtensions(ext)
|
||||
|
||||
|
@ -31,8 +31,14 @@ func TestMmark(t *testing.T) {
|
|||
|
||||
got := ToHTML([]byte(input), p, nil)
|
||||
|
||||
// make whitespace more visible
|
||||
got = bytes.Replace(got, []byte(" "), []byte("_"), -1)
|
||||
want = bytes.Replace(want, []byte(" "), []byte("_"), -1)
|
||||
got = bytes.Replace(got, []byte("\n"), []byte("_\n"), -1)
|
||||
want = bytes.Replace(want, []byte("\n"), []byte("_\n"), -1)
|
||||
|
||||
if bytes.Compare(got, want) != 0 {
|
||||
t.Errorf("want %s, got %s, for input %q", want, got, input)
|
||||
t.Errorf("want (%d bytes) %s, got (%d bytes) %s, for input %q", len(want), want, len(got), got, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -897,14 +897,17 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
|
|||
}
|
||||
beg = end
|
||||
}
|
||||
if p.extensions | MmarkCaptions {
|
||||
|
||||
}
|
||||
|
||||
if doRender {
|
||||
codeBlock := &ast.CodeBlock{
|
||||
IsFenced: true,
|
||||
}
|
||||
if p.extensions|MmarkCaptions != 0 {
|
||||
caption, consumed := p.caption([]byte("Caption: "), data[beg:])
|
||||
codeBlock.Caption = caption
|
||||
beg += consumed
|
||||
}
|
||||
|
||||
// TODO: get rid of temp buffer
|
||||
codeBlock.Content = work.Bytes()
|
||||
p.addBlock(codeBlock)
|
||||
|
@ -1169,7 +1172,8 @@ func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool {
|
|||
|
||||
// parse a blockquote fragment
|
||||
func (p *Parser) quote(data []byte) int {
|
||||
block := p.addBlock(&ast.BlockQuote{})
|
||||
quote := &ast.BlockQuote{}
|
||||
block := p.addBlock(quote)
|
||||
var raw bytes.Buffer
|
||||
beg, end := 0, 0
|
||||
for beg < len(data) {
|
||||
|
@ -1200,6 +1204,13 @@ func (p *Parser) quote(data []byte) int {
|
|||
}
|
||||
p.block(raw.Bytes())
|
||||
p.finalize(block)
|
||||
|
||||
if p.extensions|MmarkCaptions != 0 {
|
||||
caption, consumed := p.caption([]byte("Caption: "), data[end:])
|
||||
quote.Caption = caption
|
||||
end += consumed
|
||||
}
|
||||
|
||||
return end
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
)
|
||||
|
||||
func (p *Parser) caption(startwith, data []byte) (ast.Node, int) {
|
||||
if !bytes.HasPrefix(data, startwith) {
|
||||
return nil, 0
|
||||
}
|
||||
j := len(startwith)
|
||||
data = data[j:]
|
||||
end := p.linesUntilEmpty(data)
|
||||
|
||||
node := &ast.Caption{}
|
||||
p.inline(node, data[:end])
|
||||
|
||||
return node, end + j
|
||||
}
|
||||
|
||||
// linesUntilEmpty scans lines up to the first empty line.
|
||||
func (p *Parser) linesUntilEmpty(data []byte) int {
|
||||
line, i := 0, 0
|
||||
|
|
|
@ -16,3 +16,27 @@
|
|||
<h1>Section in matter</h1>
|
||||
</section>
|
||||
<section matter="main"></section>
|
||||
---
|
||||
# Test Code Captions
|
||||
~~~ go
|
||||
println("hi")
|
||||
~~~
|
||||
Caption: This *is* a
|
||||
caption.
|
||||
---
|
||||
<h1>Test Code Captions</h1>
|
||||
|
||||
<figure><pre><code class="language-go">println("hi")
|
||||
</code></pre><figcaption>This <em>is</em> a
|
||||
caption.</figcaption></figure>
|
||||
---
|
||||
# Test Quote Captions
|
||||
> To be, or not to be
|
||||
|
||||
Caption: Shakespeare.
|
||||
---
|
||||
<h1>Test Quote Captions</h1>
|
||||
<figure>
|
||||
<blockquote>
|
||||
<p>To be, or not to be</p>
|
||||
</blockquote><figcaption>Shakespeare.</figcaption></figure>
|
||||
|
|
Loading…
Reference in New Issue