Browse Source

markdown: fix links for image nested inside a link (#2636)

pull/4240/head
Unknwon 8 years ago
parent
commit
d7954014a4
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
  1. 112
      modules/markdown/markdown.go
  2. 8
      templates/repo/settings/options.tmpl

112
modules/markdown/markdown.go

@ -192,42 +192,11 @@ func (options *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
// Note: this section is for purpose of increase performance and // Note: this section is for purpose of increase performance and
// reduce memory allocation at runtime since they are constant literals. // reduce memory allocation at runtime since they are constant literals.
var ( var (
svgSuffix = []byte(".svg") pound = []byte("#")
svgSuffixWithMark = []byte(".svg?") space = " "
spaceBytes = []byte(" ") spaceEncoded = "%20"
spaceEncodedBytes = []byte("%20")
pound = []byte("#")
space = " "
spaceEncoded = "%20"
) )
// 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 := strings.Replace(r.urlPrefix, "/src/", "/raw/", 1)
if len(link) > 0 {
if isLink(link) {
// External link with .svg suffix usually means CI status.
// TODO: define a keyword to allow non-svg images render as external link.
if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
r.Renderer.Image(out, link, title, alt)
return
}
} else {
if link[0] != '/' {
prefix += "/"
}
link = bytes.Replace([]byte((prefix + string(link))), spaceBytes, spaceEncodedBytes, -1)
fmt.Println(333, string(link))
}
}
out.WriteString(`<a href="`)
out.Write(link)
out.WriteString(`">`)
r.Renderer.Image(out, link, title, alt)
out.WriteString("</a>")
}
// cutoutVerbosePrefix cutouts URL prefix including sub-path to // cutoutVerbosePrefix cutouts URL prefix including sub-path to
// return a clean unified string of request URL path. // return a clean unified string of request URL path.
func cutoutVerbosePrefix(prefix string) string { func cutoutVerbosePrefix(prefix string) string {
@ -353,14 +322,63 @@ var (
rightAngleBracket = []byte(">") rightAngleBracket = []byte(">")
) )
var noEndTags = []string{"img", "input", "br", "hr"} var noEndTags = []string{"input", "br", "hr", "img"}
// wrapImgWithLink warps link to standalone <img> tags.
func wrapImgWithLink(urlPrefix string, buf *bytes.Buffer, token html.Token) {
var src, alt string
// Extract "src" and "alt" attributes
for i := range token.Attr {
switch token.Attr[i].Key {
case "src":
src = token.Attr[i].Val
case "alt":
alt = token.Attr[i].Val
}
}
// Skip in case the "src" is empty
if len(src) == 0 {
buf.WriteString(token.String())
return
}
buf.WriteString(`<a href="`)
buf.WriteString(src)
buf.WriteString(`">`)
// Prepend repository base URL for internal links
if !isLink([]byte(src)) {
urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1)
if src[0] != '/' {
urlPrefix += "/"
}
src = strings.Replace(urlPrefix+string(src), " ", "%20", -1)
buf.WriteString(`<img src="`)
buf.WriteString(src)
buf.WriteString(`"`)
if len(alt) > 0 {
buf.WriteString(` alt="`)
buf.WriteString(alt)
buf.WriteString(`"`)
}
buf.WriteString(`>`)
} else {
buf.WriteString(token.String())
}
buf.WriteString(`</a>`)
}
// PostProcess treats different types of HTML differently, // PostProcess treats different types of HTML differently,
// and only renders special links for plain text blocks. // and only renders special links for plain text blocks.
func PostProcess(rawHtml []byte, urlPrefix string, metas map[string]string) []byte { func PostProcess(rawHTML []byte, urlPrefix string, metas map[string]string) []byte {
startTags := make([]string, 0, 5) startTags := make([]string, 0, 5)
var buf bytes.Buffer buf := bytes.NewBuffer(nil)
tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml)) tokenizer := html.NewTokenizer(bytes.NewReader(rawHTML))
OUTER_LOOP: OUTER_LOOP:
for html.ErrorToken != tokenizer.Next() { for html.ErrorToken != tokenizer.Next() {
@ -370,8 +388,14 @@ OUTER_LOOP:
buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix, metas)) buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix, metas))
case html.StartTagToken: case html.StartTagToken:
buf.WriteString(token.String())
tagName := token.Data tagName := token.Data
if tagName == "img" {
wrapImgWithLink(urlPrefix, buf, token)
continue OUTER_LOOP
}
buf.WriteString(token.String())
// If this is an excluded tag, we skip processing all output until a close tag is encountered. // 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) { if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) {
stackNum := 1 stackNum := 1
@ -381,14 +405,14 @@ OUTER_LOOP:
// Copy the token to the output verbatim // Copy the token to the output verbatim
buf.WriteString(token.String()) buf.WriteString(token.String())
if token.Type == html.StartTagToken { // Stack number doesn't increate for tags without end tags.
if token.Type == html.StartTagToken && !com.IsSliceContainsStr(noEndTags, token.Data) {
stackNum++ stackNum++
} }
// If this is the close tag to the outer-most, we are done // If this is the close tag to the outer-most, we are done
if token.Type == html.EndTagToken { if token.Type == html.EndTagToken {
stackNum-- stackNum--
if stackNum <= 0 && strings.EqualFold(tagName, token.Data) { if stackNum <= 0 && strings.EqualFold(tagName, token.Data) {
break break
} }
@ -397,8 +421,8 @@ OUTER_LOOP:
continue OUTER_LOOP continue OUTER_LOOP
} }
if !com.IsSliceContainsStr(noEndTags, token.Data) { if !com.IsSliceContainsStr(noEndTags, tagName) {
startTags = append(startTags, token.Data) startTags = append(startTags, tagName)
} }
case html.EndTagToken: case html.EndTagToken:
@ -422,7 +446,7 @@ OUTER_LOOP:
// If we are not at the end of the input, then some other parsing error has occurred, // If we are not at the end of the input, then some other parsing error has occurred,
// so return the input verbatim. // so return the input verbatim.
return rawHtml return rawHTML
} }
// Render renders Markdown to HTML with special links. // Render renders Markdown to HTML with special links.

8
templates/repo/settings/options.tmpl

@ -306,11 +306,11 @@
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required> <input id="repo_name" name="repo_name" autocomplete="off" required>
</div> </div>
<div class="required field"> <div class="required field">
<label for="new_owner_name">{{.i18n.Tr "repo.settings.transfer_owner"}}</label> <label for="new_owner_name">{{.i18n.Tr "repo.settings.transfer_owner"}}</label>
<input id="new_owner_name" name="new_owner_name" required> <input id="new_owner_name" name="new_owner_name" autocomplete="off" required>
</div> </div>
<div class="text right actions"> <div class="text right actions">
@ -344,7 +344,7 @@
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required> <input id="repo_name" name="repo_name" autocomplete="off" required>
</div> </div>
<div class="text right actions"> <div class="text right actions">
@ -376,7 +376,7 @@
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required> <input id="repo_name" name="repo_name" autocomplete="off" required>
</div> </div>
<div class="text right actions"> <div class="text right actions">

Loading…
Cancel
Save