From a3dc8bed05f91abdbec79a88402336b57736a4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Mon, 6 Mar 2023 16:23:06 +0100 Subject: [PATCH] Replace blackfriday with goldmark This change replaces the unmaintained blackfriday markdown renderer with the goldmark renderer. The goal is to have wider support for more markdown extensions and ideally have a better solution for mermaid transformation via extensions as well. --- go.mod | 2 +- go.sum | 4 +- pkg/confluence/api.go | 2 +- pkg/mark/markdown.go | 448 ++++++++++++++++++++------------ pkg/mark/markdown_test.go | 4 - pkg/mark/testdata/codes.html | 1 - pkg/mark/testdata/header.html | 6 - pkg/mark/testdata/links.html | 14 +- pkg/mark/testdata/lists.html | 10 +- pkg/mark/testdata/newlines.html | 6 - pkg/mark/testdata/quotes.html | 11 +- pkg/mark/testdata/quotes.md | 5 + pkg/mark/testdata/table.html | 1 - pkg/mark/testdata/tags.html | 3 +- pkg/mark/testdata/tags.md | 3 + 15 files changed, 303 insertions(+), 217 deletions(-) diff --git a/go.mod b/go.mod index 5c98b997..fd8cbdc1 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.19 require ( github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 - github.com/kovetskiy/blackfriday/v2 v2.3.0 github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69 github.com/kovetskiy/ko v1.6.1 github.com/kovetskiy/lorg v1.2.0 @@ -12,6 +11,7 @@ require ( github.com/reconquest/pkg v1.3.0 github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 github.com/stretchr/testify v1.8.1 + github.com/yuin/goldmark v1.5.4 golang.org/x/tools v0.7.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 56e730c6..db6100c9 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/kovetskiy/blackfriday/v2 v2.3.0 h1:KKABLPopQ2+DWKtM/ifx0RijGz09mNlCuEcZy5KvZVA= -github.com/kovetskiy/blackfriday/v2 v2.3.0/go.mod h1:ES7tjNJdnHp1h8dib5cmoa//rgvQeYrtzGzGM/Kozk4= github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69 h1:vn82v0gKhTTm67znr7nxYBNW4mJ8zfY7dywZivUy3tY= github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69/go.mod h1:t7LFI5v8Q5+nl9sqId9PS0C9H9F4c5d4XlhkLve1MCM= github.com/kovetskiy/ko v1.6.1 h1:EO5v6CrW6x6vzxo7CKbN0r+foIRjz06U6wVSgxUVqMc= @@ -41,6 +39,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 h1:BhVaeQJc3xalHGONn215FylzuxdQBIT3d/aRjDg4nXQ= github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= diff --git a/pkg/confluence/api.go b/pkg/confluence/api.go index d777ecfd..d4d7be40 100644 --- a/pkg/confluence/api.go +++ b/pkg/confluence/api.go @@ -555,7 +555,7 @@ func (api *API) UpdatePage(page *PageInfo, newContent string, minorEdit bool, ne "labels": labels, // Fix to set full-width as has changed on Confluence APIs again. // https://jira.atlassian.com/browse/CONFCLOUD-65447 - // + // "properties": map[string]interface{}{ "content-appearance-published": map[string]interface{}{ "value": appearance, diff --git a/pkg/mark/markdown.go b/pkg/mark/markdown.go index 1122c92d..52c8123d 100644 --- a/pkg/mark/markdown.go +++ b/pkg/mark/markdown.go @@ -1,14 +1,21 @@ package mark import ( + "bytes" "fmt" - "io" "regexp" "strings" - bf "github.com/kovetskiy/blackfriday/v2" "github.com/kovetskiy/mark/pkg/mark/stdlib" "github.com/reconquest/pkg/log" + "github.com/yuin/goldmark" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" ) var reBlockDetails = regexp.MustCompile( @@ -17,20 +24,69 @@ var reBlockDetails = regexp.MustCompile( `^(?:(\w*)|-)\s*\b(\S.*?\S?)??\s*(?:\btitle\s+(\S.*\S?))?$`, ) -type BlockQuoteLevelMap map[*bf.Node]int +// Define BlockQuoteType enum +type BlockQuoteType int -func (m BlockQuoteLevelMap) Level(node *bf.Node) int { +const ( + Info BlockQuoteType = iota + Note + Warn + None +) + +func (t BlockQuoteType) String() string { + return []string{"info", "note", "warn", "none"}[t] +} + +type BlockQuoteLevelMap map[ast.Node]int + +func (m BlockQuoteLevelMap) Level(node ast.Node) int { return m[node] } +// Renderer renders anchor [Node]s. type ConfluenceRenderer struct { - bf.Renderer - + html.Config Stdlib *stdlib.Lib LevelMap BlockQuoteLevelMap } +// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer +func NewConfluenceRenderer(stdlib *stdlib.Lib, opts ...html.Option) renderer.NodeRenderer { + return &ConfluenceRenderer{ + Config: html.NewConfig(), + Stdlib: stdlib, + LevelMap: nil, + } +} + +// RegisterFuncs implements NodeRenderer.RegisterFuncs . +func (r *ConfluenceRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + // blocks + // reg.Register(ast.KindDocument, r.renderNode) + // reg.Register(ast.KindHeading, r.renderNode) + reg.Register(ast.KindBlockquote, r.renderBlockQuote) + reg.Register(ast.KindCodeBlock, r.renderCodeBlock) + reg.Register(ast.KindFencedCodeBlock, r.renderCodeBlock) + // reg.Register(ast.KindHTMLBlock, r.renderNode) + // reg.Register(ast.KindList, r.renderNode) + // reg.Register(ast.KindListItem, r.renderNode) + // reg.Register(ast.KindParagraph, r.renderNode) + // reg.Register(ast.KindTextBlock, r.renderNode) + // reg.Register(ast.KindThematicBreak, r.renderNode) + + // inlines + // reg.Register(ast.KindAutoLink, r.renderNode) + // reg.Register(ast.KindCodeSpan, r.renderNode) + // reg.Register(ast.KindEmphasis, r.renderNode) + // reg.Register(ast.KindImage, r.renderNode) + reg.Register(ast.KindLink, r.renderLink) + // reg.Register(ast.KindRawHTML, r.renderNode) + // reg.Register(ast.KindText, r.renderNode) + // reg.Register(ast.KindString, r.renderNode) +} + func ParseLanguage(lang string) string { // lang takes the following form: language? "collapse"? ("title"? *)? // let's split it by spaces @@ -62,26 +118,13 @@ func ParseTitle(lang string) string { return "" } -// Define BlockQuoteType enum -type BlockQuoteType int - -const ( - Info BlockQuoteType = iota - Note - Warn - None -) - -func (t BlockQuoteType) String() string { - return []string{"info", "note", "warn", "none"}[t] -} - -func ClasifyingBlockQuote(literal string) BlockQuoteType { +// ClassifyingBlockQuote compares a string against a set of patterns and returns a BlockQuoteType +func ClassifyingBlockQuote(literal string) BlockQuoteType { infoPattern := regexp.MustCompile(`info|Info|INFO`) notePattern := regexp.MustCompile(`note|Note|NOTE`) warnPattern := regexp.MustCompile(`warn|Warn|WARN`) - var t BlockQuoteType = None + var t = None switch { case infoPattern.MatchString(literal): t = Info @@ -93,191 +136,259 @@ func ClasifyingBlockQuote(literal string) BlockQuoteType { return t } -func ParseBlockQuoteType(node *bf.Node) BlockQuoteType { - var t BlockQuoteType = None +// ParseBlockQuoteType parses the first line of a blockquote and returns its type +func ParseBlockQuoteType(node ast.Node, source []byte) BlockQuoteType { + var t = None countParagraphs := 0 - node.Walk(func(node *bf.Node, entering bool) bf.WalkStatus { + _ = ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { - if node.Type == bf.Paragraph && entering { + if node.Kind() == ast.KindParagraph && entering { countParagraphs += 1 } // Type of block quote should be defined on the first blockquote line - if node.Type == bf.Text && countParagraphs < 2 { - t = ClasifyingBlockQuote(string(node.Literal)) - } else if countParagraphs > 1 { - return bf.Terminate + if countParagraphs < 2 && entering { + if node.Kind() == ast.KindText { + n := node.(*ast.Text) + t = ClassifyingBlockQuote(string(n.Text(source))) + countParagraphs += 1 + } + if node.Kind() == ast.KindHTMLBlock { + + n := node.(*ast.HTMLBlock) + for i := 0; i < n.BaseBlock.Lines().Len(); i++ { + line := n.BaseBlock.Lines().At(i) + t = ClassifyingBlockQuote(string(line.Value(source))) + if t != None { + break + } + } + countParagraphs += 1 + } + } else if countParagraphs > 1 && entering { + return ast.WalkStop, nil } - return bf.GoToNext + return ast.WalkContinue, nil }) + return t } -func GenerateBlockQuoteLevel(someNode *bf.Node) BlockQuoteLevelMap { +// GenerateBlockQuoteLevel walks a given node and returns a map of blockquote levels +func GenerateBlockQuoteLevel(someNode ast.Node) BlockQuoteLevelMap { // We define state variable that track BlockQuote level while we walk the tree blockQuoteLevel := 0 - blockQuoteLevelMap := make(map[*bf.Node]int) + blockQuoteLevelMap := make(map[ast.Node]int) rootNode := someNode - for rootNode.Parent != nil { - rootNode = rootNode.Parent + for rootNode.Parent() != nil { + rootNode = rootNode.Parent() } - rootNode.Walk(func(node *bf.Node, entering bool) bf.WalkStatus { - if node.Type == bf.BlockQuote && entering { + _ = ast.Walk(rootNode, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if node.Kind() == ast.KindBlockquote && entering { blockQuoteLevelMap[node] = blockQuoteLevel blockQuoteLevel += 1 } - if node.Type == bf.BlockQuote && !entering { + if node.Kind() == ast.KindBlockquote && !entering { blockQuoteLevel -= 1 } - return bf.GoToNext + return ast.WalkContinue, nil }) return blockQuoteLevelMap } -func (renderer ConfluenceRenderer) RenderNode( - writer io.Writer, - node *bf.Node, - entering bool, -) bf.WalkStatus { +// renderBlockQuote will render a BlockQuote +func (r *ConfluenceRenderer) renderBlockQuote(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { // Initialize BlockQuote level map - if renderer.LevelMap == nil { - renderer.LevelMap = GenerateBlockQuoteLevel(node) + if r.LevelMap == nil { + r.LevelMap = GenerateBlockQuoteLevel(node) } - if node.Type == bf.CodeBlock { - - groups := reBlockDetails.FindStringSubmatch(string(node.Info)) - linenumbers := false - firstline := 0 - theme := "" - collapse := false - lang := "" - var options []string - title := "" - if len(groups) > 0 { - lang, options, title = groups[1], strings.Fields(groups[2]), groups[3] - for _, option := range options { - if option == "collapse" { - collapse = true - continue - } - if option == "nocollapse" { - collapse = false - continue - } - var i int - if _, err := fmt.Sscanf(option, "%d", &i); err == nil { - linenumbers = i > 0 - firstline = i - continue - } - theme = option - } + quoteType := ParseBlockQuoteType(node, source) + quoteLevel := r.LevelMap.Level(node) + + if quoteLevel == 0 && entering && quoteType != None { + prefix := fmt.Sprintf("true\n", quoteType) + if _, err := writer.Write([]byte(prefix)); err != nil { + return ast.WalkStop, err } - err := renderer.Stdlib.Templates.ExecuteTemplate( - writer, - "ac:code", - struct { - Language string - Collapse bool - Title string - Theme string - Linenumbers bool - Firstline int - Text string - }{ - lang, - collapse, - title, - theme, - linenumbers, - firstline, - strings.TrimSuffix(string(node.Literal), "\n"), - }, - ) - if err != nil { - panic(err) + return ast.WalkContinue, nil + } + if quoteLevel == 0 && !entering && quoteType != None { + suffix := "\n" + if _, err := writer.Write([]byte(suffix)); err != nil { + return ast.WalkStop, err } + return ast.WalkContinue, nil + } + return r.goldmarkRenderBlockquote(writer, source, node, entering) +} - return bf.GoToNext +// goldmarkRenderBlockquote is the default renderBlockquote implementation from https://github.com/yuin/goldmark/blob/9d6f314b99ca23037c93d76f248be7b37de6220a/renderer/html/html.go#L286 +func (r *ConfluenceRenderer) goldmarkRenderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + if n.Attributes() != nil { + _, _ = w.WriteString("') + } else { + _, _ = w.WriteString("
\n") + } + } else { + _, _ = w.WriteString("
\n") } - if node.Type == bf.Link && string(node.Destination[0:3]) == "ac:" { + return ast.WalkContinue, nil +} + +// renderLink renders links specifically for confluence +func (r *ConfluenceRenderer) renderLink(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if string(node.(*ast.Link).Destination[0:3]) == "ac:" { if entering { _, err := writer.Write([]byte("")) if err != nil { - panic(err) + return ast.WalkStop, err } - return bf.SkipChildren + return ast.WalkSkipChildren, nil } - return bf.GoToNext } - if node.Type == bf.BlockQuote { - quoteType := ParseBlockQuoteType(node) - quoteLevel := renderer.LevelMap.Level(node) - - re := regexp.MustCompile(`[\n\t]`) - - if quoteLevel == 0 && entering && quoteType != None { - if _, err := writer.Write([]byte(re.ReplaceAllString(fmt.Sprintf(` - - true - - `, quoteType), ""))); err != nil { - panic(err) - } - return bf.GoToNext + return r.goldmarkRenderLink(writer, source, node, entering) +} + +// goldmarkRenderLink is the default renderLink implementation from https://github.com/yuin/goldmark/blob/9d6f314b99ca23037c93d76f248be7b37de6220a/renderer/html/html.go#L552 +func (r *ConfluenceRenderer) goldmarkRenderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*ast.Link) + if entering { + _, _ = w.WriteString(" - - `, ""))); err != nil { - panic(err) + if n.Attributes() != nil { + html.RenderAttributes(w, n, html.LinkAttributeFilter) + } + _ = w.WriteByte('>') + } else { + _, _ = w.WriteString("") + } + return ast.WalkContinue, nil +} + +// renderCodeBlock renders a (Fenced)CodeBlock +func (r *ConfluenceRenderer) renderCodeBlock(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + + if !entering { + return ast.WalkContinue, nil + } + var info []byte + nodeFencedCodeBlock := node.(*ast.FencedCodeBlock) + if nodeFencedCodeBlock.Info != nil { + segment := nodeFencedCodeBlock.Info.Segment + info = segment.Value(source) + } + groups := reBlockDetails.FindStringSubmatch(string(info)) + linenumbers := false + firstline := 0 + theme := "" + collapse := false + lang := "" + var options []string + title := "" + if len(groups) > 0 { + lang, options, title = groups[1], strings.Fields(groups[2]), groups[3] + for _, option := range options { + if option == "collapse" { + collapse = true + continue + } + if option == "nocollapse" { + collapse = false + continue + } + var i int + if _, err := fmt.Sscanf(option, "%d", &i); err == nil { + linenumbers = i > 0 + firstline = i + continue } - return bf.GoToNext + theme = option } + } - return renderer.Renderer.RenderNode(writer, node, entering) + + var lval []byte + + lines := node.Lines().Len() + for i := 0; i < lines; i++ { + line := node.Lines().At(i) + lval = append(lval, line.Value(source)...) + } + err := r.Stdlib.Templates.ExecuteTemplate( + writer, + "ac:code", + struct { + Language string + Collapse bool + Title string + Theme string + Linenumbers bool + Firstline int + Text string + }{ + lang, + collapse, + title, + theme, + linenumbers, + firstline, + strings.TrimSuffix(string(lval), "\n"), + }, + ) + if err != nil { + return ast.WalkStop, err + } + + return ast.WalkContinue, nil } // compileMarkdown will replace tags like with escaped -// equivalent, because bf markdown parser replaces that tags with +// equivalent, because goldmark markdown parser replaces that tags with // ac:rich-text-body because of the autolink // rule. -func CompileMarkdown( - markdown []byte, - stdlib *stdlib.Lib, -) string { +func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string { log.Tracef(nil, "rendering markdown:\n%s", string(markdown)) colon := regexp.MustCompile(`---bf-COLON---`) @@ -289,42 +400,33 @@ func CompileMarkdown( []byte(`<$1`+colon.String()+`$2>`), ) - renderer := ConfluenceRenderer{ - Renderer: bf.NewHTMLRenderer( - bf.HTMLRendererParameters{ - Flags: bf.UseXHTML | - bf.Smartypants | - bf.SmartypantsFractions | - bf.SmartypantsDashes | - bf.SmartypantsLatexDashes, - }, + converter := goldmark.New( + goldmark.WithExtensions( + extension.GFM, + extension.Footnote, + extension.DefinitionList, + extension.Typographer, ), - Stdlib: stdlib, - LevelMap: nil, - } - - html := bf.Run( - markdown, - bf.WithRenderer(renderer), - bf.WithExtensions( - bf.NoIntraEmphasis| - bf.Tables| - bf.FencedCode| - bf.Autolink| - bf.LaxHTMLBlocks| - bf.Strikethrough| - bf.SpaceHeadings| - bf.HeadingIDs| - bf.AutoHeadingIDs| - bf.Titleblock| - bf.BackslashLineBreak| - bf.DefinitionLists| - bf.NoEmptyLineBeforeBlock| - bf.Footnotes, + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), ), - ) + goldmark.WithRendererOptions( + html.WithXHTML(), + html.WithUnsafe(), + )) + + converter.Renderer().AddOptions(renderer.WithNodeRenderers( + util.Prioritized(NewConfluenceRenderer(stdlib), 100), + )) + + var buf bytes.Buffer + err := converter.Convert(markdown, &buf) + + if err != nil { + panic(err) + } - html = colon.ReplaceAll(html, []byte(`:`)) + html := colon.ReplaceAll(buf.Bytes(), []byte(`:`)) log.Tracef(nil, "rendered markdown to html:\n%s", string(html)) diff --git a/pkg/mark/markdown_test.go b/pkg/mark/markdown_test.go index 9819386a..d48ebbc6 100644 --- a/pkg/mark/markdown_test.go +++ b/pkg/mark/markdown_test.go @@ -10,10 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -const ( - NL = "\n" -) - func TestCompileMarkdown(t *testing.T) { test := assert.New(t) diff --git a/pkg/mark/testdata/codes.html b/pkg/mark/testdata/codes.html index ef0f80cd..c3d724dd 100644 --- a/pkg/mark/testdata/codes.html +++ b/pkg/mark/testdata/codes.html @@ -20,7 +20,6 @@ false -

text text 2

diff --git a/pkg/mark/testdata/header.html b/pkg/mark/testdata/header.html index 89d0a104..566c4ae1 100644 --- a/pkg/mark/testdata/header.html +++ b/pkg/mark/testdata/header.html @@ -1,13 +1,7 @@

a

-

b

-

c

-

d

-
e
-

f

-

g

diff --git a/pkg/mark/testdata/links.html b/pkg/mark/testdata/links.html index 4a3293ea..3b1f468b 100644 --- a/pkg/mark/testdata/links.html +++ b/pkg/mark/testdata/links.html @@ -1,15 +1,11 @@

Use https://example.com

-

Use aaa

- -

Use footnotes link 1

- -
- +

Use footnotes link 1

+

-
    -
  1. a footnote link
  2. +
  3. +

    a footnote link ↩︎

    +
-
diff --git a/pkg/mark/testdata/lists.html b/pkg/mark/testdata/lists.html index 25fbcd10..af27c543 100644 --- a/pkg/mark/testdata/lists.html +++ b/pkg/mark/testdata/lists.html @@ -2,20 +2,18 @@
  • dash 1-1
  • dash 1-2
  • dash 1-3 -
    • dash 1-3-1
    • dash 1-3-2
    • dash 1-3-3 -
      • dash 1-3-3-1
      • -
    • -
  • - + + + +

    text

    -
    • a
    • b
    • diff --git a/pkg/mark/testdata/newlines.html b/pkg/mark/testdata/newlines.html index c425cc6b..b97e5101 100644 --- a/pkg/mark/testdata/newlines.html +++ b/pkg/mark/testdata/newlines.html @@ -1,16 +1,10 @@

      one-1 one-2

      -

      two-1

      -

      two-2

      -

      three-1

      -

      three-2

      -

      space-1 space-2

      -

      2space-1
      2space-2

      diff --git a/pkg/mark/testdata/quotes.html b/pkg/mark/testdata/quotes.html index 1eb35961..acb61d6b 100644 --- a/pkg/mark/testdata/quotes.html +++ b/pkg/mark/testdata/quotes.html @@ -1,32 +1,31 @@

      Main Heading

      -

      First Heading

      true

      NOTES:

      -
      1. Note number one
      2. Note number two
      -

      a b

      -

      Warn (Should not be picked as blockquote type)

      Second Heading

      true

      Warn

      -
      • Warn bullet 1
      • Warn bullet 2
      +

      Third Heading

      +true + +

      Test

      +

      Simple Blockquote

      -

      This paragraph is a simple blockquote

      diff --git a/pkg/mark/testdata/quotes.md b/pkg/mark/testdata/quotes.md index b9837ae4..f6b99583 100644 --- a/pkg/mark/testdata/quotes.md +++ b/pkg/mark/testdata/quotes.md @@ -19,6 +19,11 @@ > * Warn bullet 1 > * Warn bullet 2 + +## Third Heading +> +> Test + ## Simple Blockquote > This paragraph is a simple blockquote diff --git a/pkg/mark/testdata/table.html b/pkg/mark/testdata/table.html index 8129391a..9b4f80ac 100644 --- a/pkg/mark/testdata/table.html +++ b/pkg/mark/testdata/table.html @@ -5,7 +5,6 @@ HEADER2 - row1 diff --git a/pkg/mark/testdata/tags.html b/pkg/mark/testdata/tags.html index c9ab4f8b..a5161dbf 100644 --- a/pkg/mark/testdata/tags.html +++ b/pkg/mark/testdata/tags.html @@ -1,5 +1,6 @@

      bold bold

      -

      vitalik vitalik

      +

      strikethrough +strikethrough

      diff --git a/pkg/mark/testdata/tags.md b/pkg/mark/testdata/tags.md index 3dc28c9d..502c6d60 100644 --- a/pkg/mark/testdata/tags.md +++ b/pkg/mark/testdata/tags.md @@ -3,3 +3,6 @@ vitalik *vitalik* + +strikethrough +~~strikethrough~~