diff options
author | Lauris BH <lauris@nix.lv> | 2019-10-31 03:06:25 +0200 |
---|---|---|
committer | zeripath <art27@cantab.net> | 2019-10-31 01:06:25 +0000 |
commit | 086a46994a9f59ba06bfacdf2041817ef2f6671c (patch) | |
tree | 1a54a4b9f74d2a1c8a3827f24dec2fb77b8a3554 /modules/markup/markdown | |
parent | 690a8ec502ff2e162b6db9cfe1f0555cf6b37323 (diff) | |
download | gitea-086a46994a9f59ba06bfacdf2041817ef2f6671c.tar.gz gitea-086a46994a9f59ba06bfacdf2041817ef2f6671c.zip |
Rewrite markdown rendering to blackfriday v2 and rewrite orgmode rendering to go-org (#8560)
* Rewrite markdown rendering to blackfriday v2.0
* Fix style
* Fix go mod with golang 1.13
* Fix blackfriday v2 import
* Inital orgmode renderer migration to go-org
* Vendor go-org dependency
* Ignore errors :/
* Update go-org to latest version
* Update test
* Fix go-org test
* Remove unneeded code
* Fix comments
* Fix markdown test
* Fix blackfriday regression rendering HTML block
Diffstat (limited to 'modules/markup/markdown')
-rw-r--r-- | modules/markup/markdown/markdown.go | 209 | ||||
-rw-r--r-- | modules/markup/markdown/markdown_test.go | 8 |
2 files changed, 111 insertions, 106 deletions
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index d9fc768891..ff78d7ea3a 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -7,13 +7,14 @@ package markdown import ( "bytes" + "io" "strings" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "github.com/russross/blackfriday" + "github.com/russross/blackfriday/v2" ) // Renderer is a extended version of underlying render object. @@ -25,134 +26,138 @@ type Renderer struct { var byteMailto = []byte("mailto:") -// Link defines how formal links should be processed to produce corresponding HTML elements. -func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - // special case: this is not a link, a hash link or a mailto:, so it's a - // relative URL - if len(link) > 0 && !markup.IsLink(link) && - link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { - lnk := string(link) - if r.IsWiki { - lnk = util.URLJoin("wiki", lnk) - } - mLink := util.URLJoin(r.URLPrefix, lnk) - link = []byte(mLink) - } - - if len(content) > 10 && string(content[0:9]) == "<a href=\"" && bytes.Contains(content[9:], []byte("<img")) { - // Image with link case: markdown `[![]()]()` - // If the content is an image, then we change the original href around it - // which points to itself to a new address "link" - rightQuote := bytes.Index(content[9:], []byte("\"")) - content = bytes.Replace(content, content[9:9+rightQuote], link, 1) - out.Write(content) - } else { - r.Renderer.Link(out, link, title, content) - } +var htmlEscaper = [256][]byte{ + '&': []byte("&"), + '<': []byte("<"), + '>': []byte(">"), + '"': []byte("""), } -// List renders markdown bullet or digit lists to HTML -func (r *Renderer) List(out *bytes.Buffer, text func() bool, flags int) { - marker := out.Len() - if out.Len() > 0 { - out.WriteByte('\n') - } - - if flags&blackfriday.LIST_TYPE_DEFINITION != 0 { - out.WriteString("<dl>") - } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 { - out.WriteString("<ol class='ui list'>") - } else { - out.WriteString("<ul class='ui list'>") - } - if !text() { - out.Truncate(marker) - return +func escapeHTML(w io.Writer, s []byte) { + var start, end int + for end < len(s) { + escSeq := htmlEscaper[s[end]] + if escSeq != nil { + _, _ = w.Write(s[start:end]) + _, _ = w.Write(escSeq) + start = end + 1 + } + end++ } - if flags&blackfriday.LIST_TYPE_DEFINITION != 0 { - out.WriteString("</dl>\n") - } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 { - out.WriteString("</ol>\n") - } else { - out.WriteString("</ul>\n") + if start < len(s) && end <= len(s) { + _, _ = w.Write(s[start:end]) } } -// ListItem defines how list items should be processed to produce corresponding HTML elements. -func (r *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) { - // Detect procedures to draw checkboxes. - prefix := "" - if bytes.HasPrefix(text, []byte("<p>")) { - prefix = "<p>" - } - switch { - case bytes.HasPrefix(text, []byte(prefix+"[ ] ")): - text = append([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...) - if prefix != "" { - text = bytes.Replace(text, []byte(prefix), []byte{}, 1) +// RenderNode is a default renderer of a single node of a syntax tree. For +// block nodes it will be called twice: first time with entering=true, second +// time with entering=false, so that it could know when it's working on an open +// tag and when on close. It writes the result to w. +// +// The return value is a way to tell the calling walker to adjust its walk +// pattern: e.g. it can terminate the traversal by returning Terminate. Or it +// can ask the walker to skip a subtree of this node by returning SkipChildren. +// The typical behavior is to return GoToNext, which asks for the usual +// traversal to the next node. +func (r *Renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { + switch node.Type { + case blackfriday.Image: + prefix := r.URLPrefix + if r.IsWiki { + prefix = util.URLJoin(prefix, "wiki", "raw") } - case bytes.HasPrefix(text, []byte(prefix+"[x] ")): - text = append([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...) - if prefix != "" { - text = bytes.Replace(text, []byte(prefix), []byte{}, 1) + prefix = strings.Replace(prefix, "/src/", "/media/", 1) + link := node.LinkData.Destination + if len(link) > 0 && !markup.IsLink(link) { + lnk := string(link) + lnk = util.URLJoin(prefix, lnk) + lnk = strings.Replace(lnk, " ", "+", -1) + link = []byte(lnk) + } + node.LinkData.Destination = link + // Render link around image only if parent is not link already + if node.Parent != nil && node.Parent.Type != blackfriday.Link { + if entering { + _, _ = w.Write([]byte(`<a href="`)) + escapeHTML(w, link) + _, _ = w.Write([]byte(`">`)) + return r.Renderer.RenderNode(w, node, entering) + } + s := r.Renderer.RenderNode(w, node, entering) + _, _ = w.Write([]byte(`</a>`)) + return s + } + return r.Renderer.RenderNode(w, node, entering) + case blackfriday.Link: + // special case: this is not a link, a hash link or a mailto:, so it's a + // relative URL + link := node.LinkData.Destination + if len(link) > 0 && !markup.IsLink(link) && + link[0] != '#' && !bytes.HasPrefix(link, byteMailto) && + node.LinkData.Footnote == nil { + lnk := string(link) + if r.IsWiki { + lnk = util.URLJoin("wiki", lnk) + } + link = []byte(util.URLJoin(r.URLPrefix, lnk)) + } + node.LinkData.Destination = link + return r.Renderer.RenderNode(w, node, entering) + case blackfriday.Text: + isListItem := false + for n := node.Parent; n != nil; n = n.Parent { + if n.Type == blackfriday.Item { + isListItem = true + break + } + } + if isListItem { + text := node.Literal + switch { + case bytes.HasPrefix(text, []byte("[ ] ")): + _, _ = w.Write([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`)) + text = text[3:] + case bytes.HasPrefix(text, []byte("[x] ")): + _, _ = w.Write([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`)) + text = text[3:] + } + node.Literal = text } } - r.Renderer.ListItem(out, text, flags) -} - -// Image defines how images should be processed to produce corresponding HTML elements. -func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { - prefix := r.URLPrefix - if r.IsWiki { - prefix = util.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - if len(link) > 0 && !markup.IsLink(link) { - lnk := string(link) - lnk = util.URLJoin(prefix, lnk) - lnk = strings.Replace(lnk, " ", "+", -1) - link = []byte(lnk) - } - - // Put a link around it pointing to itself by default - out.WriteString(`<a href="`) - out.Write(link) - out.WriteString(`">`) - r.Renderer.Image(out, link, title, alt) - out.WriteString("</a>") + return r.Renderer.RenderNode(w, node, entering) } const ( blackfridayExtensions = 0 | - blackfriday.EXTENSION_NO_INTRA_EMPHASIS | - blackfriday.EXTENSION_TABLES | - blackfriday.EXTENSION_FENCED_CODE | - blackfriday.EXTENSION_STRIKETHROUGH | - blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK | - blackfriday.EXTENSION_DEFINITION_LISTS | - blackfriday.EXTENSION_FOOTNOTES | - blackfriday.EXTENSION_HEADER_IDS | - blackfriday.EXTENSION_AUTO_HEADER_IDS + blackfriday.NoIntraEmphasis | + blackfriday.Tables | + blackfriday.FencedCode | + blackfriday.Strikethrough | + blackfriday.NoEmptyLineBeforeBlock | + blackfriday.DefinitionLists | + blackfriday.Footnotes | + blackfriday.HeadingIDs | + blackfriday.AutoHeadingIDs blackfridayHTMLFlags = 0 | - blackfriday.HTML_SKIP_STYLE | - blackfriday.HTML_OMIT_CONTENTS | - blackfriday.HTML_USE_SMARTYPANTS + blackfriday.Smartypants ) // RenderRaw renders Markdown to HTML without handling special links. func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { renderer := &Renderer{ - Renderer: blackfriday.HtmlRenderer(blackfridayHTMLFlags, "", ""), + Renderer: blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ + Flags: blackfridayHTMLFlags, + }), URLPrefix: urlPrefix, IsWiki: wikiMarkdown, } exts := blackfridayExtensions if setting.Markdown.EnableHardLineBreak { - exts |= blackfriday.EXTENSION_HARD_LINE_BREAK + exts |= blackfriday.HardLineBreak } - body = blackfriday.Markdown(body, renderer, exts) + body = blackfriday.Run(body, blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(exts)) return markup.SanitizeBytes(body) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 669b49367e..b29f870ce5 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -166,13 +166,13 @@ func testAnswers(baseURLContent, baseURLImages string) []string { <h3 id="footnotes">Footnotes</h3> <p>Here is a simple footnote,<sup id="fnref:1"><a href="#fn:1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:bignote"><a href="#fn:bignote" rel="nofollow">2</a></sup></p> + <div> <hr/> <ol> -<li id="fn:1">This is the first footnote. -</li> +<li id="fn:1">This is the first footnote.</li> <li id="fn:bignote"><p>Here is one with multiple paragraphs and code.</p> @@ -180,9 +180,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string { <p><code>{ my code }</code></p> -<p>Add as many paragraphs as you like.</p> -</li> +<p>Add as many paragraphs as you like.</p></li> </ol> + </div> `, } |