| @@ -14,6 +14,7 @@ import ( | |||
| "regexp" | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/russross/blackfriday" | |||
| "golang.org/x/net/html" | |||
| @@ -99,13 +100,26 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte, | |||
| options.Renderer.Link(out, link, title, content) | |||
| } | |||
| var ( | |||
| svgSuffix = []byte(".svg") | |||
| svgSuffixWithMark = []byte(".svg?") | |||
| ) | |||
| func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { | |||
| prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1) | |||
| if len(link) > 0 && !isLink(link) { | |||
| if link[0] != '/' { | |||
| prefix += "/" | |||
| if len(link) > 0 { | |||
| if isLink(link) { | |||
| // External link with .svg suffix usually means CI status. | |||
| if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) { | |||
| options.Renderer.Image(out, link, title, alt) | |||
| return | |||
| } | |||
| } else { | |||
| if link[0] != '/' { | |||
| prefix += "/" | |||
| } | |||
| link = []byte(prefix + string(link)) | |||
| } | |||
| link = []byte(prefix + string(link)) | |||
| } | |||
| out.WriteString(`<a href="`) | |||
| @@ -236,12 +250,16 @@ var ( | |||
| rightAngleBracket = []byte(">") | |||
| ) | |||
| var noEndTags = []string{"img", "input", "br", "hr"} | |||
| // PostProcessMarkdown treats different types of HTML differently, | |||
| // and only renders special links for plain text blocks. | |||
| func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte { | |||
| var startTag string | |||
| startTags := make([]string, 0, 5) | |||
| var buf bytes.Buffer | |||
| tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml)) | |||
| OUTER_LOOP: | |||
| for html.ErrorToken != tokenizer.Next() { | |||
| token := tokenizer.Token() | |||
| switch token.Type { | |||
| @@ -249,26 +267,32 @@ func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte { | |||
| buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix)) | |||
| case html.StartTagToken: | |||
| startTag = token.Data | |||
| buf.WriteString(token.String()) | |||
| tagName := token.Data | |||
| // If this is an excluded tag, we skip processing all output until a close tag is encountered. | |||
| if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) { | |||
| for html.ErrorToken != tokenizer.Next() { | |||
| token = tokenizer.Token() | |||
| // Copy the token to the output verbatim | |||
| buf.WriteString(token.String()) | |||
| // If this is the close tag, we are done | |||
| if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) { | |||
| if token.Type == html.EndTagToken && strings.EqualFold(tagName, token.Data) { | |||
| break | |||
| } | |||
| } | |||
| continue OUTER_LOOP | |||
| } | |||
| if !com.IsSliceContainsStr(noEndTags, token.Data) { | |||
| startTags = append(startTags, token.Data) | |||
| } | |||
| case html.EndTagToken: | |||
| buf.Write(leftAngleBracket) | |||
| buf.WriteString(startTag) | |||
| buf.WriteString(startTags[len(startTags)-1]) | |||
| buf.Write(rightAngleBracket) | |||
| startTags = startTags[:len(startTags)-1] | |||
| default: | |||
| buf.WriteString(token.String()) | |||
| } | |||