mark/pkg/mark/markdown.go

149 lines
3.2 KiB
Go
Raw Normal View History

2019-04-14 13:53:44 +03:00
package mark
import (
"io"
2019-04-14 13:53:44 +03:00
"regexp"
"strings"
2019-04-14 13:53:44 +03:00
2019-08-02 22:58:08 +03:00
"github.com/kovetskiy/mark/pkg/mark/stdlib"
2020-11-03 17:12:51 +03:00
"github.com/reconquest/pkg/log"
bf "github.com/kovetskiy/blackfriday/v2"
2019-04-14 13:53:44 +03:00
)
type ConfluenceRenderer struct {
bf.Renderer
2019-08-02 22:58:08 +03:00
Stdlib *stdlib.Lib
2019-04-14 13:53:44 +03:00
}
func ParseLanguage(lang string) string {
// lang takes the following form: language? "collapse"? ("title"? <any string>*)?
// let's split it by spaces
paramlist := strings.Fields(lang)
// get the word in question, aka the first one
first := lang
if len(paramlist) > 0 {
first = paramlist[0]
}
if first == "collapse" || first == "title" {
// collapsing or including a title without a language
return ""
}
// the default case with language being the first one
return first
}
func ParseTitle(lang string) string {
index := strings.Index(lang, "title")
if index >= 0 {
// it's found, check if title is given and return it
start := index + 6
if len(lang) > start {
return lang[start:]
}
}
return ""
}
func (renderer ConfluenceRenderer) RenderNode(
writer io.Writer,
node *bf.Node,
entering bool,
) bf.WalkStatus {
if node.Type == bf.CodeBlock {
lang := string(node.Info)
2021-02-04 12:18:28 +03:00
renderer.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:code",
struct {
Language string
2021-02-04 12:18:28 +03:00
Collapse bool
Title string
Text string
}{
ParseLanguage(lang),
2021-02-04 12:18:28 +03:00
strings.Contains(lang, "collapse"),
ParseTitle(lang),
2021-02-04 12:18:28 +03:00
strings.TrimSuffix(string(node.Literal), "\n"),
},
)
return bf.GoToNext
}
return renderer.Renderer.RenderNode(writer, node, entering)
2019-04-14 13:53:44 +03:00
}
// compileMarkdown will replace tags like <ac:rich-tech-body> with escaped
// equivalent, because bf markdown parser replaces that tags with
2019-04-14 13:53:44 +03:00
// <a href="ac:rich-text-body">ac:rich-text-body</a> for whatever reason.
2019-04-20 10:24:30 +03:00
func CompileMarkdown(
markdown []byte,
2019-08-02 22:58:08 +03:00
stdlib *stdlib.Lib,
) string {
2019-04-20 10:24:30 +03:00
log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
colon := regexp.MustCompile(`---bf-COLON---`)
2019-04-14 13:53:44 +03:00
2019-08-02 22:58:08 +03:00
tags := regexp.MustCompile(`<(/?\S+?):(\S+?)>`)
2019-04-14 13:53:44 +03:00
markdown = tags.ReplaceAll(
markdown,
[]byte(`<$1`+colon.String()+`$2>`),
)
renderer := ConfluenceRenderer{
Renderer: bf.NewHTMLRenderer(
bf.HTMLRendererParameters{
Flags: bf.UseXHTML |
bf.Smartypants |
bf.SmartypantsFractions |
bf.SmartypantsDashes |
bf.SmartypantsLatexDashes,
},
2019-04-14 13:53:44 +03:00
),
2019-08-02 22:58:08 +03:00
Stdlib: stdlib,
2019-04-14 13:53:44 +03:00
}
html := bf.Run(
2019-04-14 13:53:44 +03:00
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,
),
2019-04-14 13:53:44 +03:00
)
html = colon.ReplaceAll(html, []byte(`:`))
2019-04-20 10:24:30 +03:00
log.Tracef(nil, "rendered markdown to html:\n%s", string(html))
2019-08-02 22:58:08 +03:00
return string(html)
2019-04-14 13:53:44 +03:00
}
// DropDocumentLeadingH1 will drop leading H1 headings to prevent
// duplication of or visual conflict with page titles.
// NOTE: This is intended only to operate on the whole markdown document.
// Operating on individual lines will clear them if the begin with `#`.
func DropDocumentLeadingH1(
markdown []byte,
) []byte {
h1 := regexp.MustCompile(`^#[^#].*\n`)
markdown = h1.ReplaceAll(markdown, []byte(""))
return markdown
}